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