]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
Merge pull request #7661 from keszybz/slice-cleanups
[thirdparty/systemd.git] / src / basic / unit-name.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stddef.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "alloc-util.h"
28 #include "glob-util.h"
29 #include "hexdecoct.h"
30 #include "path-util.h"
31 #include "special.h"
32 #include "string-util.h"
33 #include "strv.h"
34 #include "unit-name.h"
35
36 /* Characters valid in a unit name. */
37 #define VALID_CHARS \
38 DIGITS \
39 LETTERS \
40 ":-_.\\"
41
42 /* The same, but also permits the single @ character that may appear */
43 #define VALID_CHARS_WITH_AT \
44 "@" \
45 VALID_CHARS
46
47 /* All chars valid in a unit name glob */
48 #define VALID_CHARS_GLOB \
49 VALID_CHARS_WITH_AT \
50 "[]!-*?"
51
52 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
53 const char *e, *i, *at;
54
55 assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
56
57 if (_unlikely_(flags == 0))
58 return false;
59
60 if (isempty(n))
61 return false;
62
63 if (strlen(n) >= UNIT_NAME_MAX)
64 return false;
65
66 e = strrchr(n, '.');
67 if (!e || e == n)
68 return false;
69
70 if (unit_type_from_string(e + 1) < 0)
71 return false;
72
73 for (i = n, at = NULL; i < e; i++) {
74
75 if (*i == '@' && !at)
76 at = i;
77
78 if (!strchr("@" VALID_CHARS, *i))
79 return false;
80 }
81
82 if (at == n)
83 return false;
84
85 if (flags & UNIT_NAME_PLAIN)
86 if (!at)
87 return true;
88
89 if (flags & UNIT_NAME_INSTANCE)
90 if (at && e > at + 1)
91 return true;
92
93 if (flags & UNIT_NAME_TEMPLATE)
94 if (at && e == at + 1)
95 return true;
96
97 return false;
98 }
99
100 bool unit_prefix_is_valid(const char *p) {
101
102 /* We don't allow additional @ in the prefix string */
103
104 if (isempty(p))
105 return false;
106
107 return in_charset(p, VALID_CHARS);
108 }
109
110 bool unit_instance_is_valid(const char *i) {
111
112 /* The max length depends on the length of the string, so we
113 * don't really check this here. */
114
115 if (isempty(i))
116 return false;
117
118 /* We allow additional @ in the instance string, we do not
119 * allow them in the prefix! */
120
121 return in_charset(i, "@" VALID_CHARS);
122 }
123
124 bool unit_suffix_is_valid(const char *s) {
125 if (isempty(s))
126 return false;
127
128 if (s[0] != '.')
129 return false;
130
131 if (unit_type_from_string(s + 1) < 0)
132 return false;
133
134 return true;
135 }
136
137 int unit_name_to_prefix(const char *n, char **ret) {
138 const char *p;
139 char *s;
140
141 assert(n);
142 assert(ret);
143
144 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
145 return -EINVAL;
146
147 p = strchr(n, '@');
148 if (!p)
149 p = strrchr(n, '.');
150
151 assert_se(p);
152
153 s = strndup(n, p - n);
154 if (!s)
155 return -ENOMEM;
156
157 *ret = s;
158 return 0;
159 }
160
161 int unit_name_to_instance(const char *n, char **instance) {
162 const char *p, *d;
163 char *i;
164
165 assert(n);
166 assert(instance);
167
168 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
169 return -EINVAL;
170
171 /* Everything past the first @ and before the last . is the instance */
172 p = strchr(n, '@');
173 if (!p) {
174 *instance = NULL;
175 return 0;
176 }
177
178 p++;
179
180 d = strrchr(p, '.');
181 if (!d)
182 return -EINVAL;
183
184 i = strndup(p, d-p);
185 if (!i)
186 return -ENOMEM;
187
188 *instance = i;
189 return 1;
190 }
191
192 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
193 const char *d;
194 char *s;
195
196 assert(n);
197 assert(ret);
198
199 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
200 return -EINVAL;
201
202 d = strrchr(n, '.');
203 if (!d)
204 return -EINVAL;
205
206 s = strndup(n, d - n);
207 if (!s)
208 return -ENOMEM;
209
210 *ret = s;
211 return 0;
212 }
213
214 UnitType unit_name_to_type(const char *n) {
215 const char *e;
216
217 assert(n);
218
219 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
220 return _UNIT_TYPE_INVALID;
221
222 assert_se(e = strrchr(n, '.'));
223
224 return unit_type_from_string(e + 1);
225 }
226
227 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
228 char *e, *s;
229 size_t a, b;
230
231 assert(n);
232 assert(suffix);
233 assert(ret);
234
235 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
236 return -EINVAL;
237
238 if (!unit_suffix_is_valid(suffix))
239 return -EINVAL;
240
241 assert_se(e = strrchr(n, '.'));
242
243 a = e - n;
244 b = strlen(suffix);
245
246 s = new(char, a + b + 1);
247 if (!s)
248 return -ENOMEM;
249
250 strcpy(mempcpy(s, n, a), suffix);
251 *ret = s;
252
253 return 0;
254 }
255
256 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
257 char *s;
258
259 assert(prefix);
260 assert(suffix);
261 assert(ret);
262
263 if (!unit_prefix_is_valid(prefix))
264 return -EINVAL;
265
266 if (instance && !unit_instance_is_valid(instance))
267 return -EINVAL;
268
269 if (!unit_suffix_is_valid(suffix))
270 return -EINVAL;
271
272 if (!instance)
273 s = strappend(prefix, suffix);
274 else
275 s = strjoin(prefix, "@", instance, suffix);
276 if (!s)
277 return -ENOMEM;
278
279 *ret = s;
280 return 0;
281 }
282
283 static char *do_escape_char(char c, char *t) {
284 assert(t);
285
286 *(t++) = '\\';
287 *(t++) = 'x';
288 *(t++) = hexchar(c >> 4);
289 *(t++) = hexchar(c);
290
291 return t;
292 }
293
294 static char *do_escape(const char *f, char *t) {
295 assert(f);
296 assert(t);
297
298 /* do not create units with a leading '.', like for "/.dotdir" mount points */
299 if (*f == '.') {
300 t = do_escape_char(*f, t);
301 f++;
302 }
303
304 for (; *f; f++) {
305 if (*f == '/')
306 *(t++) = '-';
307 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
308 t = do_escape_char(*f, t);
309 else
310 *(t++) = *f;
311 }
312
313 return t;
314 }
315
316 char *unit_name_escape(const char *f) {
317 char *r, *t;
318
319 assert(f);
320
321 r = new(char, strlen(f)*4+1);
322 if (!r)
323 return NULL;
324
325 t = do_escape(f, r);
326 *t = 0;
327
328 return r;
329 }
330
331 int unit_name_unescape(const char *f, char **ret) {
332 _cleanup_free_ char *r = NULL;
333 char *t;
334
335 assert(f);
336
337 r = strdup(f);
338 if (!r)
339 return -ENOMEM;
340
341 for (t = r; *f; f++) {
342 if (*f == '-')
343 *(t++) = '/';
344 else if (*f == '\\') {
345 int a, b;
346
347 if (f[1] != 'x')
348 return -EINVAL;
349
350 a = unhexchar(f[2]);
351 if (a < 0)
352 return -EINVAL;
353
354 b = unhexchar(f[3]);
355 if (b < 0)
356 return -EINVAL;
357
358 *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
359 f += 3;
360 } else
361 *(t++) = *f;
362 }
363
364 *t = 0;
365
366 *ret = r;
367 r = NULL;
368
369 return 0;
370 }
371
372 int unit_name_path_escape(const char *f, char **ret) {
373 char *p, *s;
374
375 assert(f);
376 assert(ret);
377
378 p = strdupa(f);
379 if (!p)
380 return -ENOMEM;
381
382 path_kill_slashes(p);
383
384 if (STR_IN_SET(p, "/", ""))
385 s = strdup("-");
386 else {
387 if (!path_is_normalized(p))
388 return -EINVAL;
389
390 /* Truncate trailing slashes */
391 delete_trailing_chars(p, "/");
392
393 /* Truncate leading slashes */
394 p = skip_leading_chars(p, "/");
395
396 s = unit_name_escape(p);
397 }
398 if (!s)
399 return -ENOMEM;
400
401 *ret = s;
402 return 0;
403 }
404
405 int unit_name_path_unescape(const char *f, char **ret) {
406 char *s;
407 int r;
408
409 assert(f);
410
411 if (isempty(f))
412 return -EINVAL;
413
414 if (streq(f, "-")) {
415 s = strdup("/");
416 if (!s)
417 return -ENOMEM;
418 } else {
419 char *w;
420
421 r = unit_name_unescape(f, &w);
422 if (r < 0)
423 return r;
424
425 /* Don't accept trailing or leading slashes */
426 if (startswith(w, "/") || endswith(w, "/")) {
427 free(w);
428 return -EINVAL;
429 }
430
431 /* Prefix a slash again */
432 s = strappend("/", w);
433 free(w);
434 if (!s)
435 return -ENOMEM;
436
437 if (!path_is_normalized(s)) {
438 free(s);
439 return -EINVAL;
440 }
441 }
442
443 if (ret)
444 *ret = s;
445 else
446 free(s);
447
448 return 0;
449 }
450
451 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
452 const char *p, *e;
453 char *s;
454 size_t a, b;
455
456 assert(f);
457 assert(i);
458 assert(ret);
459
460 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
461 return -EINVAL;
462 if (!unit_instance_is_valid(i))
463 return -EINVAL;
464
465 assert_se(p = strchr(f, '@'));
466 assert_se(e = strrchr(f, '.'));
467
468 a = p - f;
469 b = strlen(i);
470
471 s = new(char, a + 1 + b + strlen(e) + 1);
472 if (!s)
473 return -ENOMEM;
474
475 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
476
477 *ret = s;
478 return 0;
479 }
480
481 int unit_name_template(const char *f, char **ret) {
482 const char *p, *e;
483 char *s;
484 size_t a;
485
486 assert(f);
487 assert(ret);
488
489 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
490 return -EINVAL;
491
492 assert_se(p = strchr(f, '@'));
493 assert_se(e = strrchr(f, '.'));
494
495 a = p - f;
496
497 s = new(char, a + 1 + strlen(e) + 1);
498 if (!s)
499 return -ENOMEM;
500
501 strcpy(mempcpy(s, f, a + 1), e);
502
503 *ret = s;
504 return 0;
505 }
506
507 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
508 _cleanup_free_ char *p = NULL;
509 char *s = NULL;
510 int r;
511
512 assert(path);
513 assert(suffix);
514 assert(ret);
515
516 if (!unit_suffix_is_valid(suffix))
517 return -EINVAL;
518
519 r = unit_name_path_escape(path, &p);
520 if (r < 0)
521 return r;
522
523 s = strappend(p, suffix);
524 if (!s)
525 return -ENOMEM;
526
527 *ret = s;
528 return 0;
529 }
530
531 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
532 _cleanup_free_ char *p = NULL;
533 char *s;
534 int r;
535
536 assert(prefix);
537 assert(path);
538 assert(suffix);
539 assert(ret);
540
541 if (!unit_prefix_is_valid(prefix))
542 return -EINVAL;
543
544 if (!unit_suffix_is_valid(suffix))
545 return -EINVAL;
546
547 r = unit_name_path_escape(path, &p);
548 if (r < 0)
549 return r;
550
551 s = strjoin(prefix, "@", p, suffix);
552 if (!s)
553 return -ENOMEM;
554
555 *ret = s;
556 return 0;
557 }
558
559 int unit_name_to_path(const char *name, char **ret) {
560 _cleanup_free_ char *prefix = NULL;
561 int r;
562
563 assert(name);
564
565 r = unit_name_to_prefix(name, &prefix);
566 if (r < 0)
567 return r;
568
569 return unit_name_path_unescape(prefix, ret);
570 }
571
572 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
573 const char *valid_chars;
574
575 assert(f);
576 assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
577 assert(t);
578
579 /* We'll only escape the obvious characters here, to play
580 * safe. */
581
582 valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
583
584 for (; *f; f++) {
585 if (*f == '/')
586 *(t++) = '-';
587 else if (!strchr(valid_chars, *f))
588 t = do_escape_char(*f, t);
589 else
590 *(t++) = *f;
591 }
592
593 return t;
594 }
595
596 /**
597 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
598 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
599 * except that @suffix is appended if a valid unit suffix is not present.
600 *
601 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
602 */
603 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
604 char *s, *t;
605 int r;
606
607 assert(name);
608 assert(suffix);
609 assert(ret);
610
611 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
612 return -EINVAL;
613
614 if (!unit_suffix_is_valid(suffix))
615 return -EINVAL;
616
617 /* Already a fully valid unit name? If so, no mangling is necessary... */
618 if (unit_name_is_valid(name, UNIT_NAME_ANY))
619 goto good;
620
621 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
622 if (allow_globs == UNIT_NAME_GLOB &&
623 string_is_glob(name) &&
624 in_charset(name, VALID_CHARS_GLOB))
625 goto good;
626
627 if (is_device_path(name)) {
628 r = unit_name_from_path(name, ".device", ret);
629 if (r >= 0)
630 return 1;
631 if (r != -EINVAL)
632 return r;
633 }
634
635 if (path_is_absolute(name)) {
636 r = unit_name_from_path(name, ".mount", ret);
637 if (r >= 0)
638 return 1;
639 if (r != -EINVAL)
640 return r;
641 }
642
643 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
644 if (!s)
645 return -ENOMEM;
646
647 t = do_escape_mangle(name, allow_globs, s);
648 *t = 0;
649
650 /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a
651 * valid glob. */
652 if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0)
653 strcpy(t, suffix);
654
655 *ret = s;
656 return 1;
657
658 good:
659 s = strdup(name);
660 if (!s)
661 return -ENOMEM;
662
663 *ret = s;
664 return 0;
665 }
666
667 int slice_build_parent_slice(const char *slice, char **ret) {
668 char *s, *dash;
669 int r;
670
671 assert(slice);
672 assert(ret);
673
674 if (!slice_name_is_valid(slice))
675 return -EINVAL;
676
677 if (streq(slice, SPECIAL_ROOT_SLICE)) {
678 *ret = NULL;
679 return 0;
680 }
681
682 s = strdup(slice);
683 if (!s)
684 return -ENOMEM;
685
686 dash = strrchr(s, '-');
687 if (dash)
688 strcpy(dash, ".slice");
689 else {
690 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
691 if (r < 0) {
692 free(s);
693 return r;
694 }
695 }
696
697 *ret = s;
698 return 1;
699 }
700
701 int slice_build_subslice(const char *slice, const char*name, char **ret) {
702 char *subslice;
703
704 assert(slice);
705 assert(name);
706 assert(ret);
707
708 if (!slice_name_is_valid(slice))
709 return -EINVAL;
710
711 if (!unit_prefix_is_valid(name))
712 return -EINVAL;
713
714 if (streq(slice, SPECIAL_ROOT_SLICE))
715 subslice = strappend(name, ".slice");
716 else {
717 char *e;
718
719 assert_se(e = endswith(slice, ".slice"));
720
721 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
722 if (!subslice)
723 return -ENOMEM;
724
725 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
726 }
727
728 *ret = subslice;
729 return 0;
730 }
731
732 bool slice_name_is_valid(const char *name) {
733 const char *p, *e;
734 bool dash = false;
735
736 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
737 return false;
738
739 if (streq(name, SPECIAL_ROOT_SLICE))
740 return true;
741
742 e = endswith(name, ".slice");
743 if (!e)
744 return false;
745
746 for (p = name; p < e; p++) {
747
748 if (*p == '-') {
749
750 /* Don't allow initial dash */
751 if (p == name)
752 return false;
753
754 /* Don't allow multiple dashes */
755 if (dash)
756 return false;
757
758 dash = true;
759 } else
760 dash = false;
761 }
762
763 /* Don't allow trailing hash */
764 if (dash)
765 return false;
766
767 return true;
768 }