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