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