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