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