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