]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
fs-util: rename path_is_safe() → path_is_normalized()
[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 if (!path_is_normalized(p))
386 return -EINVAL;
387
388 /* Truncate trailing slashes */
389 delete_trailing_chars(p, "/");
390
391 /* Truncate leading slashes */
392 p = skip_leading_chars(p, "/");
393
394 s = unit_name_escape(p);
395 }
396 if (!s)
397 return -ENOMEM;
398
399 *ret = s;
400 return 0;
401 }
402
403 int unit_name_path_unescape(const char *f, char **ret) {
404 char *s;
405 int r;
406
407 assert(f);
408
409 if (isempty(f))
410 return -EINVAL;
411
412 if (streq(f, "-")) {
413 s = strdup("/");
414 if (!s)
415 return -ENOMEM;
416 } else {
417 char *w;
418
419 r = unit_name_unescape(f, &w);
420 if (r < 0)
421 return r;
422
423 /* Don't accept trailing or leading slashes */
424 if (startswith(w, "/") || endswith(w, "/")) {
425 free(w);
426 return -EINVAL;
427 }
428
429 /* Prefix a slash again */
430 s = strappend("/", w);
431 free(w);
432 if (!s)
433 return -ENOMEM;
434
435 if (!path_is_normalized(s)) {
436 free(s);
437 return -EINVAL;
438 }
439 }
440
441 if (ret)
442 *ret = s;
443 else
444 free(s);
445
446 return 0;
447 }
448
449 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
450 const char *p, *e;
451 char *s;
452 size_t a, b;
453
454 assert(f);
455 assert(i);
456 assert(ret);
457
458 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
459 return -EINVAL;
460 if (!unit_instance_is_valid(i))
461 return -EINVAL;
462
463 assert_se(p = strchr(f, '@'));
464 assert_se(e = strrchr(f, '.'));
465
466 a = p - f;
467 b = strlen(i);
468
469 s = new(char, a + 1 + b + strlen(e) + 1);
470 if (!s)
471 return -ENOMEM;
472
473 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
474
475 *ret = s;
476 return 0;
477 }
478
479 int unit_name_template(const char *f, char **ret) {
480 const char *p, *e;
481 char *s;
482 size_t a;
483
484 assert(f);
485 assert(ret);
486
487 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
488 return -EINVAL;
489
490 assert_se(p = strchr(f, '@'));
491 assert_se(e = strrchr(f, '.'));
492
493 a = p - f;
494
495 s = new(char, a + 1 + strlen(e) + 1);
496 if (!s)
497 return -ENOMEM;
498
499 strcpy(mempcpy(s, f, a + 1), e);
500
501 *ret = s;
502 return 0;
503 }
504
505 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
506 _cleanup_free_ char *p = NULL;
507 char *s = NULL;
508 int r;
509
510 assert(path);
511 assert(suffix);
512 assert(ret);
513
514 if (!unit_suffix_is_valid(suffix))
515 return -EINVAL;
516
517 r = unit_name_path_escape(path, &p);
518 if (r < 0)
519 return r;
520
521 s = strappend(p, suffix);
522 if (!s)
523 return -ENOMEM;
524
525 *ret = s;
526 return 0;
527 }
528
529 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
530 _cleanup_free_ char *p = NULL;
531 char *s;
532 int r;
533
534 assert(prefix);
535 assert(path);
536 assert(suffix);
537 assert(ret);
538
539 if (!unit_prefix_is_valid(prefix))
540 return -EINVAL;
541
542 if (!unit_suffix_is_valid(suffix))
543 return -EINVAL;
544
545 r = unit_name_path_escape(path, &p);
546 if (r < 0)
547 return r;
548
549 s = strjoin(prefix, "@", p, suffix);
550 if (!s)
551 return -ENOMEM;
552
553 *ret = s;
554 return 0;
555 }
556
557 int unit_name_to_path(const char *name, char **ret) {
558 _cleanup_free_ char *prefix = NULL;
559 int r;
560
561 assert(name);
562
563 r = unit_name_to_prefix(name, &prefix);
564 if (r < 0)
565 return r;
566
567 return unit_name_path_unescape(prefix, ret);
568 }
569
570 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
571 const char *valid_chars;
572
573 assert(f);
574 assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
575 assert(t);
576
577 /* We'll only escape the obvious characters here, to play
578 * safe. */
579
580 valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
581
582 for (; *f; f++) {
583 if (*f == '/')
584 *(t++) = '-';
585 else if (!strchr(valid_chars, *f))
586 t = do_escape_char(*f, t);
587 else
588 *(t++) = *f;
589 }
590
591 return t;
592 }
593
594 /**
595 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
596 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
597 * except that @suffix is appended if a valid unit suffix is not present.
598 *
599 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
600 */
601 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
602 char *s, *t;
603 int r;
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 (allow_globs == UNIT_NAME_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 t = do_escape_mangle(name, allow_globs, s);
646 *t = 0;
647
648 /* 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
649 * valid glob. */
650 if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0)
651 strcpy(t, suffix);
652
653 *ret = s;
654 return 1;
655
656 good:
657 s = strdup(name);
658 if (!s)
659 return -ENOMEM;
660
661 *ret = s;
662 return 0;
663 }
664
665 int slice_build_parent_slice(const char *slice, char **ret) {
666 char *s, *dash;
667 int r;
668
669 assert(slice);
670 assert(ret);
671
672 if (!slice_name_is_valid(slice))
673 return -EINVAL;
674
675 if (streq(slice, "-.slice")) {
676 *ret = NULL;
677 return 0;
678 }
679
680 s = strdup(slice);
681 if (!s)
682 return -ENOMEM;
683
684 dash = strrchr(s, '-');
685 if (dash)
686 strcpy(dash, ".slice");
687 else {
688 r = free_and_strdup(&s, "-.slice");
689 if (r < 0) {
690 free(s);
691 return r;
692 }
693 }
694
695 *ret = s;
696 return 1;
697 }
698
699 int slice_build_subslice(const char *slice, const char*name, char **ret) {
700 char *subslice;
701
702 assert(slice);
703 assert(name);
704 assert(ret);
705
706 if (!slice_name_is_valid(slice))
707 return -EINVAL;
708
709 if (!unit_prefix_is_valid(name))
710 return -EINVAL;
711
712 if (streq(slice, "-.slice"))
713 subslice = strappend(name, ".slice");
714 else {
715 char *e;
716
717 assert_se(e = endswith(slice, ".slice"));
718
719 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
720 if (!subslice)
721 return -ENOMEM;
722
723 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
724 }
725
726 *ret = subslice;
727 return 0;
728 }
729
730 bool slice_name_is_valid(const char *name) {
731 const char *p, *e;
732 bool dash = false;
733
734 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
735 return false;
736
737 if (streq(name, "-.slice"))
738 return true;
739
740 e = endswith(name, ".slice");
741 if (!e)
742 return false;
743
744 for (p = name; p < e; p++) {
745
746 if (*p == '-') {
747
748 /* Don't allow initial dash */
749 if (p == name)
750 return false;
751
752 /* Don't allow multiple dashes */
753 if (dash)
754 return false;
755
756 dash = true;
757 } else
758 dash = false;
759 }
760
761 /* Don't allow trailing hash */
762 if (dash)
763 return false;
764
765 return true;
766 }