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