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