]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
tree-wide: drop string.h when string-util.h or friends are included
[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 char *e, *s;
211 size_t a, b;
212
213 assert(n);
214 assert(suffix);
215 assert(ret);
216
217 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
218 return -EINVAL;
219
220 if (!unit_suffix_is_valid(suffix))
221 return -EINVAL;
222
223 assert_se(e = strrchr(n, '.'));
224
225 a = e - n;
226 b = strlen(suffix);
227
228 s = new(char, a + b + 1);
229 if (!s)
230 return -ENOMEM;
231
232 strcpy(mempcpy(s, n, a), suffix);
233 *ret = s;
234
235 return 0;
236 }
237
238 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
239 UnitType type;
240
241 assert(prefix);
242 assert(suffix);
243 assert(ret);
244
245 if (suffix[0] != '.')
246 return -EINVAL;
247
248 type = unit_type_from_string(suffix + 1);
249 if (type < 0)
250 return -EINVAL;
251
252 return unit_name_build_from_type(prefix, instance, type, ret);
253 }
254
255 int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
256 const char *ut;
257 char *s;
258
259 assert(prefix);
260 assert(type >= 0);
261 assert(type < _UNIT_TYPE_MAX);
262 assert(ret);
263
264 if (!unit_prefix_is_valid(prefix))
265 return -EINVAL;
266
267 if (instance && !unit_instance_is_valid(instance))
268 return -EINVAL;
269
270 ut = unit_type_to_string(type);
271
272 if (!instance)
273 s = strjoin(prefix, ".", ut);
274 else
275 s = strjoin(prefix, "@", instance, ".", ut);
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 = TAKE_PTR(r);
367
368 return 0;
369 }
370
371 int unit_name_path_escape(const char *f, char **ret) {
372 char *p, *s;
373
374 assert(f);
375 assert(ret);
376
377 p = strdupa(f);
378 if (!p)
379 return -ENOMEM;
380
381 path_simplify(p, false);
382
383 if (empty_or_root(p))
384 s = strdup("-");
385 else {
386 if (!path_is_normalized(p))
387 return -EINVAL;
388
389 /* Truncate trailing slashes */
390 delete_trailing_chars(p, "/");
391
392 /* Truncate leading slashes */
393 p = skip_leading_chars(p, "/");
394
395 s = unit_name_escape(p);
396 }
397 if (!s)
398 return -ENOMEM;
399
400 *ret = s;
401 return 0;
402 }
403
404 int unit_name_path_unescape(const char *f, char **ret) {
405 _cleanup_free_ char *s = NULL;
406 int r;
407
408 assert(f);
409
410 if (isempty(f))
411 return -EINVAL;
412
413 if (streq(f, "-")) {
414 s = strdup("/");
415 if (!s)
416 return -ENOMEM;
417 } else {
418 _cleanup_free_ char *w = NULL;
419
420 r = unit_name_unescape(f, &w);
421 if (r < 0)
422 return r;
423
424 /* Don't accept trailing or leading slashes */
425 if (startswith(w, "/") || endswith(w, "/"))
426 return -EINVAL;
427
428 /* Prefix a slash again */
429 s = strjoin("/", w);
430 if (!s)
431 return -ENOMEM;
432
433 if (!path_is_normalized(s))
434 return -EINVAL;
435 }
436
437 if (ret)
438 *ret = TAKE_PTR(s);
439
440 return 0;
441 }
442
443 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
444 const char *p, *e;
445 char *s;
446 size_t a, b;
447
448 assert(f);
449 assert(i);
450 assert(ret);
451
452 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
453 return -EINVAL;
454 if (!unit_instance_is_valid(i))
455 return -EINVAL;
456
457 assert_se(p = strchr(f, '@'));
458 assert_se(e = strrchr(f, '.'));
459
460 a = p - f;
461 b = strlen(i);
462
463 s = new(char, a + 1 + b + strlen(e) + 1);
464 if (!s)
465 return -ENOMEM;
466
467 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
468
469 *ret = s;
470 return 0;
471 }
472
473 int unit_name_template(const char *f, char **ret) {
474 const char *p, *e;
475 char *s;
476 size_t a;
477
478 assert(f);
479 assert(ret);
480
481 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
482 return -EINVAL;
483
484 assert_se(p = strchr(f, '@'));
485 assert_se(e = strrchr(f, '.'));
486
487 a = p - f;
488
489 s = new(char, a + 1 + strlen(e) + 1);
490 if (!s)
491 return -ENOMEM;
492
493 strcpy(mempcpy(s, f, a + 1), e);
494
495 *ret = s;
496 return 0;
497 }
498
499 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
500 _cleanup_free_ char *p = NULL;
501 char *s = NULL;
502 int r;
503
504 assert(path);
505 assert(suffix);
506 assert(ret);
507
508 if (!unit_suffix_is_valid(suffix))
509 return -EINVAL;
510
511 r = unit_name_path_escape(path, &p);
512 if (r < 0)
513 return r;
514
515 s = strjoin(p, suffix);
516 if (!s)
517 return -ENOMEM;
518
519 *ret = s;
520 return 0;
521 }
522
523 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
524 _cleanup_free_ char *p = NULL;
525 char *s;
526 int r;
527
528 assert(prefix);
529 assert(path);
530 assert(suffix);
531 assert(ret);
532
533 if (!unit_prefix_is_valid(prefix))
534 return -EINVAL;
535
536 if (!unit_suffix_is_valid(suffix))
537 return -EINVAL;
538
539 r = unit_name_path_escape(path, &p);
540 if (r < 0)
541 return r;
542
543 s = strjoin(prefix, "@", p, suffix);
544 if (!s)
545 return -ENOMEM;
546
547 *ret = s;
548 return 0;
549 }
550
551 int unit_name_to_path(const char *name, char **ret) {
552 _cleanup_free_ char *prefix = NULL;
553 int r;
554
555 assert(name);
556
557 r = unit_name_to_prefix(name, &prefix);
558 if (r < 0)
559 return r;
560
561 return unit_name_path_unescape(prefix, ret);
562 }
563
564 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
565 const char *valid_chars;
566 bool mangled = false;
567
568 assert(f);
569 assert(t);
570
571 /* We'll only escape the obvious characters here, to play safe.
572 *
573 * Returns true if any characters were mangled, false otherwise.
574 */
575
576 valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
577
578 for (; *f; f++)
579 if (*f == '/') {
580 *(t++) = '-';
581 mangled = true;
582 } else if (!strchr(valid_chars, *f)) {
583 t = do_escape_char(*f, t);
584 mangled = true;
585 } else
586 *(t++) = *f;
587 *t = 0;
588
589 return mangled;
590 }
591
592 /**
593 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
594 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
595 * except that @suffix is appended if a valid unit suffix is not present.
596 *
597 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
598 */
599 int unit_name_mangle_with_suffix(const char *name, const char *operation, UnitNameMangle flags, const char *suffix, char **ret) {
600 char *s;
601 int r;
602 bool mangled, suggest_escape = true;
603
604 assert(name);
605 assert(suffix);
606 assert(ret);
607
608 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
609 return -EINVAL;
610
611 if (!unit_suffix_is_valid(suffix))
612 return -EINVAL;
613
614 /* Already a fully valid unit name? If so, no mangling is necessary... */
615 if (unit_name_is_valid(name, UNIT_NAME_ANY))
616 goto good;
617
618 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
619 if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
620 if (flags & UNIT_NAME_MANGLE_GLOB)
621 goto good;
622 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
623 "Glob pattern passed%s%s, but globs are not supported for this.",
624 operation ? " " : "", operation ?: "");
625 suggest_escape = false;
626 }
627
628 if (is_device_path(name)) {
629 r = unit_name_from_path(name, ".device", ret);
630 if (r >= 0)
631 return 1;
632 if (r != -EINVAL)
633 return r;
634 }
635
636 if (path_is_absolute(name)) {
637 r = unit_name_from_path(name, ".mount", ret);
638 if (r >= 0)
639 return 1;
640 if (r != -EINVAL)
641 return r;
642 }
643
644 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
645 if (!s)
646 return -ENOMEM;
647
648 mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
649 if (mangled)
650 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
651 "Invalid unit name \"%s\" escaped as \"%s\"%s.",
652 name, s,
653 suggest_escape ? " (maybe you should use systemd-escape?)" : "");
654
655 /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
656 * "foo.*" as a valid glob. */
657 if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
658 strcat(s, suffix);
659
660 *ret = s;
661 return 1;
662
663 good:
664 s = strdup(name);
665 if (!s)
666 return -ENOMEM;
667
668 *ret = s;
669 return 0;
670 }
671
672 bool service_unit_name_is_valid(const char *name) {
673 _cleanup_free_ char *prefix = NULL, *s = NULL;
674 const char *e, *service_name = name;
675
676 if (!unit_name_is_valid(name, UNIT_NAME_ANY))
677 return false;
678
679 e = endswith(name, ".service");
680 if (!e)
681 return false;
682
683 /* If it's a template or instance, get the prefix as a service name. */
684 if (unit_name_is_valid(name, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) {
685 if (unit_name_to_prefix(name, &prefix) < 0)
686 return false;
687
688 s = strjoin(prefix, ".service");
689 if (!s)
690 return false;
691
692 service_name = s;
693 }
694
695 /* Reject reserved service name(s). */
696 if (streq(service_name, SPECIAL_ROOT_SERVICE))
697 return false;
698
699 return true;
700 }
701
702 int slice_build_parent_slice(const char *slice, char **ret) {
703 char *s, *dash;
704 int r;
705
706 assert(slice);
707 assert(ret);
708
709 if (!slice_name_is_valid(slice))
710 return -EINVAL;
711
712 if (streq(slice, SPECIAL_ROOT_SLICE)) {
713 *ret = NULL;
714 return 0;
715 }
716
717 s = strdup(slice);
718 if (!s)
719 return -ENOMEM;
720
721 dash = strrchr(s, '-');
722 if (dash)
723 strcpy(dash, ".slice");
724 else {
725 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
726 if (r < 0) {
727 free(s);
728 return r;
729 }
730 }
731
732 *ret = s;
733 return 1;
734 }
735
736 int slice_build_subslice(const char *slice, const char *name, char **ret) {
737 char *subslice;
738
739 assert(slice);
740 assert(name);
741 assert(ret);
742
743 if (!slice_name_is_valid(slice))
744 return -EINVAL;
745
746 if (!unit_prefix_is_valid(name))
747 return -EINVAL;
748
749 if (streq(slice, SPECIAL_ROOT_SLICE))
750 subslice = strjoin(name, ".slice");
751 else {
752 char *e;
753
754 assert_se(e = endswith(slice, ".slice"));
755
756 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
757 if (!subslice)
758 return -ENOMEM;
759
760 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
761 }
762
763 *ret = subslice;
764 return 0;
765 }
766
767 bool slice_name_is_valid(const char *name) {
768 const char *p, *e;
769 bool dash = false;
770
771 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
772 return false;
773
774 if (streq(name, SPECIAL_ROOT_SLICE))
775 return true;
776
777 e = endswith(name, ".slice");
778 if (!e)
779 return false;
780
781 for (p = name; p < e; p++) {
782
783 if (*p == '-') {
784
785 /* Don't allow initial dash */
786 if (p == name)
787 return false;
788
789 /* Don't allow multiple dashes */
790 if (dash)
791 return false;
792
793 dash = true;
794 } else
795 dash = false;
796 }
797
798 /* Don't allow trailing hash */
799 if (dash)
800 return false;
801
802 return true;
803 }