]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
basic/unit-name: remove duplicate check
[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 (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 const char *ut;
262 char *s;
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 if (instance && !unit_instance_is_valid(instance))
273 return -EINVAL;
274
275 ut = unit_type_to_string(type);
276
277 if (!instance)
278 s = strjoin(prefix, ".", ut);
279 else
280 s = strjoin(prefix, "@", instance, ".", ut);
281 if (!s)
282 return -ENOMEM;
283
284 *ret = s;
285 return 0;
286 }
287
288 static char *do_escape_char(char c, char *t) {
289 assert(t);
290
291 *(t++) = '\\';
292 *(t++) = 'x';
293 *(t++) = hexchar(c >> 4);
294 *(t++) = hexchar(c);
295
296 return t;
297 }
298
299 static char *do_escape(const char *f, char *t) {
300 assert(f);
301 assert(t);
302
303 /* do not create units with a leading '.', like for "/.dotdir" mount points */
304 if (*f == '.') {
305 t = do_escape_char(*f, t);
306 f++;
307 }
308
309 for (; *f; f++) {
310 if (*f == '/')
311 *(t++) = '-';
312 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
313 t = do_escape_char(*f, t);
314 else
315 *(t++) = *f;
316 }
317
318 return t;
319 }
320
321 char *unit_name_escape(const char *f) {
322 char *r, *t;
323
324 assert(f);
325
326 r = new(char, strlen(f)*4+1);
327 if (!r)
328 return NULL;
329
330 t = do_escape(f, r);
331 *t = 0;
332
333 return r;
334 }
335
336 int unit_name_unescape(const char *f, char **ret) {
337 _cleanup_free_ char *r = NULL;
338 char *t;
339
340 assert(f);
341
342 r = strdup(f);
343 if (!r)
344 return -ENOMEM;
345
346 for (t = r; *f; f++) {
347 if (*f == '-')
348 *(t++) = '/';
349 else if (*f == '\\') {
350 int a, b;
351
352 if (f[1] != 'x')
353 return -EINVAL;
354
355 a = unhexchar(f[2]);
356 if (a < 0)
357 return -EINVAL;
358
359 b = unhexchar(f[3]);
360 if (b < 0)
361 return -EINVAL;
362
363 *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
364 f += 3;
365 } else
366 *(t++) = *f;
367 }
368
369 *t = 0;
370
371 *ret = TAKE_PTR(r);
372
373 return 0;
374 }
375
376 int unit_name_path_escape(const char *f, char **ret) {
377 char *p, *s;
378
379 assert(f);
380 assert(ret);
381
382 p = strdupa(f);
383 if (!p)
384 return -ENOMEM;
385
386 path_kill_slashes(p);
387
388 if (STR_IN_SET(p, "/", ""))
389 s = strdup("-");
390 else {
391 if (!path_is_normalized(p))
392 return -EINVAL;
393
394 /* Truncate trailing slashes */
395 delete_trailing_chars(p, "/");
396
397 /* Truncate leading slashes */
398 p = skip_leading_chars(p, "/");
399
400 s = unit_name_escape(p);
401 }
402 if (!s)
403 return -ENOMEM;
404
405 *ret = s;
406 return 0;
407 }
408
409 int unit_name_path_unescape(const char *f, char **ret) {
410 char *s;
411 int r;
412
413 assert(f);
414
415 if (isempty(f))
416 return -EINVAL;
417
418 if (streq(f, "-")) {
419 s = strdup("/");
420 if (!s)
421 return -ENOMEM;
422 } else {
423 char *w;
424
425 r = unit_name_unescape(f, &w);
426 if (r < 0)
427 return r;
428
429 /* Don't accept trailing or leading slashes */
430 if (startswith(w, "/") || endswith(w, "/")) {
431 free(w);
432 return -EINVAL;
433 }
434
435 /* Prefix a slash again */
436 s = strappend("/", w);
437 free(w);
438 if (!s)
439 return -ENOMEM;
440
441 if (!path_is_normalized(s)) {
442 free(s);
443 return -EINVAL;
444 }
445 }
446
447 if (ret)
448 *ret = s;
449 else
450 free(s);
451
452 return 0;
453 }
454
455 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
456 const char *p, *e;
457 char *s;
458 size_t a, b;
459
460 assert(f);
461 assert(i);
462 assert(ret);
463
464 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
465 return -EINVAL;
466 if (!unit_instance_is_valid(i))
467 return -EINVAL;
468
469 assert_se(p = strchr(f, '@'));
470 assert_se(e = strrchr(f, '.'));
471
472 a = p - f;
473 b = strlen(i);
474
475 s = new(char, a + 1 + b + strlen(e) + 1);
476 if (!s)
477 return -ENOMEM;
478
479 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
480
481 *ret = s;
482 return 0;
483 }
484
485 int unit_name_template(const char *f, char **ret) {
486 const char *p, *e;
487 char *s;
488 size_t a;
489
490 assert(f);
491 assert(ret);
492
493 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
494 return -EINVAL;
495
496 assert_se(p = strchr(f, '@'));
497 assert_se(e = strrchr(f, '.'));
498
499 a = p - f;
500
501 s = new(char, a + 1 + strlen(e) + 1);
502 if (!s)
503 return -ENOMEM;
504
505 strcpy(mempcpy(s, f, a + 1), e);
506
507 *ret = s;
508 return 0;
509 }
510
511 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
512 _cleanup_free_ char *p = NULL;
513 char *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 = strappend(p, suffix);
528 if (!s)
529 return -ENOMEM;
530
531 *ret = s;
532 return 0;
533 }
534
535 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
536 _cleanup_free_ char *p = NULL;
537 char *s;
538 int r;
539
540 assert(prefix);
541 assert(path);
542 assert(suffix);
543 assert(ret);
544
545 if (!unit_prefix_is_valid(prefix))
546 return -EINVAL;
547
548 if (!unit_suffix_is_valid(suffix))
549 return -EINVAL;
550
551 r = unit_name_path_escape(path, &p);
552 if (r < 0)
553 return r;
554
555 s = strjoin(prefix, "@", p, suffix);
556 if (!s)
557 return -ENOMEM;
558
559 *ret = s;
560 return 0;
561 }
562
563 int unit_name_to_path(const char *name, char **ret) {
564 _cleanup_free_ char *prefix = NULL;
565 int r;
566
567 assert(name);
568
569 r = unit_name_to_prefix(name, &prefix);
570 if (r < 0)
571 return r;
572
573 return unit_name_path_unescape(prefix, ret);
574 }
575
576 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
577 const char *valid_chars;
578 bool mangled = false;
579
580 assert(f);
581 assert(t);
582
583 /* We'll only escape the obvious characters here, to play safe.
584 *
585 * Returns true if any characters were mangled, false otherwise.
586 */
587
588 valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
589
590 for (; *f; f++)
591 if (*f == '/') {
592 *(t++) = '-';
593 mangled = true;
594 } else if (!strchr(valid_chars, *f)) {
595 t = do_escape_char(*f, t);
596 mangled = true;
597 } else
598 *(t++) = *f;
599 *t = 0;
600
601 return mangled;
602 }
603
604 /**
605 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
606 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
607 * except that @suffix is appended if a valid unit suffix is not present.
608 *
609 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
610 */
611 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle flags, const char *suffix, char **ret) {
612 char *s;
613 int r;
614 bool mangled;
615
616 assert(name);
617 assert(suffix);
618 assert(ret);
619
620 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
621 return -EINVAL;
622
623 if (!unit_suffix_is_valid(suffix))
624 return -EINVAL;
625
626 /* Already a fully valid unit name? If so, no mangling is necessary... */
627 if (unit_name_is_valid(name, UNIT_NAME_ANY))
628 goto good;
629
630 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
631 if ((flags & UNIT_NAME_MANGLE_GLOB) &&
632 string_is_glob(name) &&
633 in_charset(name, VALID_CHARS_GLOB))
634 goto good;
635
636 if (is_device_path(name)) {
637 r = unit_name_from_path(name, ".device", ret);
638 if (r >= 0)
639 return 1;
640 if (r != -EINVAL)
641 return r;
642 }
643
644 if (path_is_absolute(name)) {
645 r = unit_name_from_path(name, ".mount", ret);
646 if (r >= 0)
647 return 1;
648 if (r != -EINVAL)
649 return r;
650 }
651
652 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
653 if (!s)
654 return -ENOMEM;
655
656 mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
657 if (mangled)
658 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
659 "Invalid unit name \"%s\" was escaped as \"%s\" (maybe you should use systemd-escape?)",
660 name, s);
661
662 /* 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
663 * valid glob. */
664 if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
665 strcat(s, suffix);
666
667 *ret = s;
668 return 1;
669
670 good:
671 s = strdup(name);
672 if (!s)
673 return -ENOMEM;
674
675 *ret = s;
676 return 0;
677 }
678
679 int slice_build_parent_slice(const char *slice, char **ret) {
680 char *s, *dash;
681 int r;
682
683 assert(slice);
684 assert(ret);
685
686 if (!slice_name_is_valid(slice))
687 return -EINVAL;
688
689 if (streq(slice, SPECIAL_ROOT_SLICE)) {
690 *ret = NULL;
691 return 0;
692 }
693
694 s = strdup(slice);
695 if (!s)
696 return -ENOMEM;
697
698 dash = strrchr(s, '-');
699 if (dash)
700 strcpy(dash, ".slice");
701 else {
702 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
703 if (r < 0) {
704 free(s);
705 return r;
706 }
707 }
708
709 *ret = s;
710 return 1;
711 }
712
713 int slice_build_subslice(const char *slice, const char*name, char **ret) {
714 char *subslice;
715
716 assert(slice);
717 assert(name);
718 assert(ret);
719
720 if (!slice_name_is_valid(slice))
721 return -EINVAL;
722
723 if (!unit_prefix_is_valid(name))
724 return -EINVAL;
725
726 if (streq(slice, SPECIAL_ROOT_SLICE))
727 subslice = strappend(name, ".slice");
728 else {
729 char *e;
730
731 assert_se(e = endswith(slice, ".slice"));
732
733 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
734 if (!subslice)
735 return -ENOMEM;
736
737 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
738 }
739
740 *ret = subslice;
741 return 0;
742 }
743
744 bool slice_name_is_valid(const char *name) {
745 const char *p, *e;
746 bool dash = false;
747
748 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
749 return false;
750
751 if (streq(name, SPECIAL_ROOT_SLICE))
752 return true;
753
754 e = endswith(name, ".slice");
755 if (!e)
756 return false;
757
758 for (p = name; p < e; p++) {
759
760 if (*p == '-') {
761
762 /* Don't allow initial dash */
763 if (p == name)
764 return false;
765
766 /* Don't allow multiple dashes */
767 if (dash)
768 return false;
769
770 dash = true;
771 } else
772 dash = false;
773 }
774
775 /* Don't allow trailing hash */
776 if (dash)
777 return false;
778
779 return true;
780 }