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