]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
Merge pull request #12390 from poettering/string-file-mkdir
[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 #include <string.h>
8
9 #include "alloc-util.h"
10 #include "glob-util.h"
11 #include "hexdecoct.h"
12 #include "path-util.h"
13 #include "special.h"
14 #include "string-util.h"
15 #include "strv.h"
16 #include "unit-name.h"
17
18 /* Characters valid in a unit name. */
19 #define VALID_CHARS \
20 DIGITS \
21 LETTERS \
22 ":-_.\\"
23
24 /* The same, but also permits the single @ character that may appear */
25 #define VALID_CHARS_WITH_AT \
26 "@" \
27 VALID_CHARS
28
29 /* All chars valid in a unit name glob */
30 #define VALID_CHARS_GLOB \
31 VALID_CHARS_WITH_AT \
32 "[]!-*?"
33
34 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
35 const char *e, *i, *at;
36
37 assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
38
39 if (_unlikely_(flags == 0))
40 return false;
41
42 if (isempty(n))
43 return false;
44
45 if (strlen(n) >= UNIT_NAME_MAX)
46 return false;
47
48 e = strrchr(n, '.');
49 if (!e || e == n)
50 return false;
51
52 if (unit_type_from_string(e + 1) < 0)
53 return false;
54
55 for (i = n, at = NULL; i < e; i++) {
56
57 if (*i == '@' && !at)
58 at = i;
59
60 if (!strchr("@" VALID_CHARS, *i))
61 return false;
62 }
63
64 if (at == n)
65 return false;
66
67 if (flags & UNIT_NAME_PLAIN)
68 if (!at)
69 return true;
70
71 if (flags & UNIT_NAME_INSTANCE)
72 if (at && e > at + 1)
73 return true;
74
75 if (flags & UNIT_NAME_TEMPLATE)
76 if (at && e == at + 1)
77 return true;
78
79 return false;
80 }
81
82 bool unit_prefix_is_valid(const char *p) {
83
84 /* We don't allow additional @ in the prefix string */
85
86 if (isempty(p))
87 return false;
88
89 return in_charset(p, VALID_CHARS);
90 }
91
92 bool unit_instance_is_valid(const char *i) {
93
94 /* The max length depends on the length of the string, so we
95 * don't really check this here. */
96
97 if (isempty(i))
98 return false;
99
100 /* We allow additional @ in the instance string, we do not
101 * allow them in the prefix! */
102
103 return in_charset(i, "@" VALID_CHARS);
104 }
105
106 bool unit_suffix_is_valid(const char *s) {
107 if (isempty(s))
108 return false;
109
110 if (s[0] != '.')
111 return false;
112
113 if (unit_type_from_string(s + 1) < 0)
114 return false;
115
116 return true;
117 }
118
119 int unit_name_to_prefix(const char *n, char **ret) {
120 const char *p;
121 char *s;
122
123 assert(n);
124 assert(ret);
125
126 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
127 return -EINVAL;
128
129 p = strchr(n, '@');
130 if (!p)
131 p = strrchr(n, '.');
132
133 assert_se(p);
134
135 s = strndup(n, p - n);
136 if (!s)
137 return -ENOMEM;
138
139 *ret = s;
140 return 0;
141 }
142
143 int unit_name_to_instance(const char *n, char **instance) {
144 const char *p, *d;
145 char *i;
146
147 assert(n);
148 assert(instance);
149
150 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
151 return -EINVAL;
152
153 /* Everything past the first @ and before the last . is the instance */
154 p = strchr(n, '@');
155 if (!p) {
156 *instance = NULL;
157 return 0;
158 }
159
160 p++;
161
162 d = strrchr(p, '.');
163 if (!d)
164 return -EINVAL;
165
166 i = strndup(p, d-p);
167 if (!i)
168 return -ENOMEM;
169
170 *instance = i;
171 return 1;
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 char *s;
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 char *w;
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 free(w);
427 return -EINVAL;
428 }
429
430 /* Prefix a slash again */
431 s = strappend("/", w);
432 free(w);
433 if (!s)
434 return -ENOMEM;
435
436 if (!path_is_normalized(s)) {
437 free(s);
438 return -EINVAL;
439 }
440 }
441
442 if (ret)
443 *ret = s;
444 else
445 free(s);
446
447 return 0;
448 }
449
450 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
451 const char *p, *e;
452 char *s;
453 size_t a, b;
454
455 assert(f);
456 assert(i);
457 assert(ret);
458
459 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
460 return -EINVAL;
461 if (!unit_instance_is_valid(i))
462 return -EINVAL;
463
464 assert_se(p = strchr(f, '@'));
465 assert_se(e = strrchr(f, '.'));
466
467 a = p - f;
468 b = strlen(i);
469
470 s = new(char, a + 1 + b + strlen(e) + 1);
471 if (!s)
472 return -ENOMEM;
473
474 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
475
476 *ret = s;
477 return 0;
478 }
479
480 int unit_name_template(const char *f, char **ret) {
481 const char *p, *e;
482 char *s;
483 size_t a;
484
485 assert(f);
486 assert(ret);
487
488 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
489 return -EINVAL;
490
491 assert_se(p = strchr(f, '@'));
492 assert_se(e = strrchr(f, '.'));
493
494 a = p - f;
495
496 s = new(char, a + 1 + strlen(e) + 1);
497 if (!s)
498 return -ENOMEM;
499
500 strcpy(mempcpy(s, f, a + 1), e);
501
502 *ret = s;
503 return 0;
504 }
505
506 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
507 _cleanup_free_ char *p = NULL;
508 char *s = NULL;
509 int r;
510
511 assert(path);
512 assert(suffix);
513 assert(ret);
514
515 if (!unit_suffix_is_valid(suffix))
516 return -EINVAL;
517
518 r = unit_name_path_escape(path, &p);
519 if (r < 0)
520 return r;
521
522 s = strappend(p, suffix);
523 if (!s)
524 return -ENOMEM;
525
526 *ret = s;
527 return 0;
528 }
529
530 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
531 _cleanup_free_ char *p = NULL;
532 char *s;
533 int r;
534
535 assert(prefix);
536 assert(path);
537 assert(suffix);
538 assert(ret);
539
540 if (!unit_prefix_is_valid(prefix))
541 return -EINVAL;
542
543 if (!unit_suffix_is_valid(suffix))
544 return -EINVAL;
545
546 r = unit_name_path_escape(path, &p);
547 if (r < 0)
548 return r;
549
550 s = strjoin(prefix, "@", p, suffix);
551 if (!s)
552 return -ENOMEM;
553
554 *ret = s;
555 return 0;
556 }
557
558 int unit_name_to_path(const char *name, char **ret) {
559 _cleanup_free_ char *prefix = NULL;
560 int r;
561
562 assert(name);
563
564 r = unit_name_to_prefix(name, &prefix);
565 if (r < 0)
566 return r;
567
568 return unit_name_path_unescape(prefix, ret);
569 }
570
571 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
572 const char *valid_chars;
573 bool mangled = false;
574
575 assert(f);
576 assert(t);
577
578 /* We'll only escape the obvious characters here, to play safe.
579 *
580 * Returns true if any characters were mangled, false otherwise.
581 */
582
583 valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
584
585 for (; *f; f++)
586 if (*f == '/') {
587 *(t++) = '-';
588 mangled = true;
589 } else if (!strchr(valid_chars, *f)) {
590 t = do_escape_char(*f, t);
591 mangled = true;
592 } else
593 *(t++) = *f;
594 *t = 0;
595
596 return mangled;
597 }
598
599 /**
600 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
601 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
602 * except that @suffix is appended if a valid unit suffix is not present.
603 *
604 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
605 */
606 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle flags, const char *suffix, char **ret) {
607 char *s;
608 int r;
609 bool mangled;
610
611 assert(name);
612 assert(suffix);
613 assert(ret);
614
615 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
616 return -EINVAL;
617
618 if (!unit_suffix_is_valid(suffix))
619 return -EINVAL;
620
621 /* Already a fully valid unit name? If so, no mangling is necessary... */
622 if (unit_name_is_valid(name, UNIT_NAME_ANY))
623 goto good;
624
625 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
626 if ((flags & UNIT_NAME_MANGLE_GLOB) &&
627 string_is_glob(name) &&
628 in_charset(name, VALID_CHARS_GLOB))
629 goto good;
630
631 if (is_device_path(name)) {
632 r = unit_name_from_path(name, ".device", ret);
633 if (r >= 0)
634 return 1;
635 if (r != -EINVAL)
636 return r;
637 }
638
639 if (path_is_absolute(name)) {
640 r = unit_name_from_path(name, ".mount", ret);
641 if (r >= 0)
642 return 1;
643 if (r != -EINVAL)
644 return r;
645 }
646
647 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
648 if (!s)
649 return -ENOMEM;
650
651 mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
652 if (mangled)
653 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
654 "Invalid unit name \"%s\" was escaped as \"%s\" (maybe you should use systemd-escape?)",
655 name, s);
656
657 /* 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
658 * valid glob. */
659 if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
660 strcat(s, suffix);
661
662 *ret = s;
663 return 1;
664
665 good:
666 s = strdup(name);
667 if (!s)
668 return -ENOMEM;
669
670 *ret = s;
671 return 0;
672 }
673
674 int slice_build_parent_slice(const char *slice, char **ret) {
675 char *s, *dash;
676 int r;
677
678 assert(slice);
679 assert(ret);
680
681 if (!slice_name_is_valid(slice))
682 return -EINVAL;
683
684 if (streq(slice, SPECIAL_ROOT_SLICE)) {
685 *ret = NULL;
686 return 0;
687 }
688
689 s = strdup(slice);
690 if (!s)
691 return -ENOMEM;
692
693 dash = strrchr(s, '-');
694 if (dash)
695 strcpy(dash, ".slice");
696 else {
697 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
698 if (r < 0) {
699 free(s);
700 return r;
701 }
702 }
703
704 *ret = s;
705 return 1;
706 }
707
708 int slice_build_subslice(const char *slice, const char *name, char **ret) {
709 char *subslice;
710
711 assert(slice);
712 assert(name);
713 assert(ret);
714
715 if (!slice_name_is_valid(slice))
716 return -EINVAL;
717
718 if (!unit_prefix_is_valid(name))
719 return -EINVAL;
720
721 if (streq(slice, SPECIAL_ROOT_SLICE))
722 subslice = strappend(name, ".slice");
723 else {
724 char *e;
725
726 assert_se(e = endswith(slice, ".slice"));
727
728 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
729 if (!subslice)
730 return -ENOMEM;
731
732 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
733 }
734
735 *ret = subslice;
736 return 0;
737 }
738
739 bool slice_name_is_valid(const char *name) {
740 const char *p, *e;
741 bool dash = false;
742
743 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
744 return false;
745
746 if (streq(name, SPECIAL_ROOT_SLICE))
747 return true;
748
749 e = endswith(name, ".slice");
750 if (!e)
751 return false;
752
753 for (p = name; p < e; p++) {
754
755 if (*p == '-') {
756
757 /* Don't allow initial dash */
758 if (p == name)
759 return false;
760
761 /* Don't allow multiple dashes */
762 if (dash)
763 return false;
764
765 dash = true;
766 } else
767 dash = false;
768 }
769
770 /* Don't allow trailing hash */
771 if (dash)
772 return false;
773
774 return true;
775 }