chiark / gitweb /
35e63c17e3e6d7871b88b370c8adc074133e1531
[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 (isempty(suffix))
255                 return -EINVAL;
256         if (suffix[0] != '.')
257                 return -EINVAL;
258
259         type = unit_type_from_string(suffix + 1);
260         if (type < 0)
261                 return -EINVAL;
262
263         return unit_name_build_from_type(prefix, instance, type, ret);
264 }
265
266 int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
267         const char *ut;
268         char *s;
269
270         assert(prefix);
271         assert(type >= 0);
272         assert(type < _UNIT_TYPE_MAX);
273         assert(ret);
274
275         if (!unit_prefix_is_valid(prefix))
276                 return -EINVAL;
277
278         if (instance && !unit_instance_is_valid(instance))
279                 return -EINVAL;
280
281         ut = unit_type_to_string(type);
282
283         if (!instance)
284                 s = strjoin(prefix, ".", ut);
285         else
286                 s = strjoin(prefix, "@", instance, ".", ut);
287         if (!s)
288                 return -ENOMEM;
289
290         *ret = s;
291         return 0;
292 }
293
294 #if 0 /// UNNEEDED by elogind
295 static char *do_escape_char(char c, char *t) {
296         assert(t);
297
298         *(t++) = '\\';
299         *(t++) = 'x';
300         *(t++) = hexchar(c >> 4);
301         *(t++) = hexchar(c);
302
303         return t;
304 }
305
306 static char *do_escape(const char *f, char *t) {
307         assert(f);
308         assert(t);
309
310         /* do not create units with a leading '.', like for "/.dotdir" mount points */
311         if (*f == '.') {
312                 t = do_escape_char(*f, t);
313                 f++;
314         }
315
316         for (; *f; f++) {
317                 if (*f == '/')
318                         *(t++) = '-';
319                 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
320                         t = do_escape_char(*f, t);
321                 else
322                         *(t++) = *f;
323         }
324
325         return t;
326 }
327
328 char *unit_name_escape(const char *f) {
329         char *r, *t;
330
331         assert(f);
332
333         r = new(char, strlen(f)*4+1);
334         if (!r)
335                 return NULL;
336
337         t = do_escape(f, r);
338         *t = 0;
339
340         return r;
341 }
342
343 int unit_name_unescape(const char *f, char **ret) {
344         _cleanup_free_ char *r = NULL;
345         char *t;
346
347         assert(f);
348
349         r = strdup(f);
350         if (!r)
351                 return -ENOMEM;
352
353         for (t = r; *f; f++) {
354                 if (*f == '-')
355                         *(t++) = '/';
356                 else if (*f == '\\') {
357                         int a, b;
358
359                         if (f[1] != 'x')
360                                 return -EINVAL;
361
362                         a = unhexchar(f[2]);
363                         if (a < 0)
364                                 return -EINVAL;
365
366                         b = unhexchar(f[3]);
367                         if (b < 0)
368                                 return -EINVAL;
369
370                         *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
371                         f += 3;
372                 } else
373                         *(t++) = *f;
374         }
375
376         *t = 0;
377
378         *ret = TAKE_PTR(r);
379
380         return 0;
381 }
382
383 int unit_name_path_escape(const char *f, char **ret) {
384         char *p, *s;
385
386         assert(f);
387         assert(ret);
388
389         p = strdupa(f);
390         if (!p)
391                 return -ENOMEM;
392
393         path_kill_slashes(p);
394
395         if (STR_IN_SET(p, "/", ""))
396                 s = strdup("-");
397         else {
398                 if (!path_is_normalized(p))
399                         return -EINVAL;
400
401                 /* Truncate trailing slashes */
402                 delete_trailing_chars(p, "/");
403
404                 /* Truncate leading slashes */
405                 p = skip_leading_chars(p, "/");
406
407                 s = unit_name_escape(p);
408         }
409         if (!s)
410                 return -ENOMEM;
411
412         *ret = s;
413         return 0;
414 }
415
416 int unit_name_path_unescape(const char *f, char **ret) {
417         char *s;
418         int r;
419
420         assert(f);
421
422         if (isempty(f))
423                 return -EINVAL;
424
425         if (streq(f, "-")) {
426                 s = strdup("/");
427                 if (!s)
428                         return -ENOMEM;
429         } else {
430                 char *w;
431
432                 r = unit_name_unescape(f, &w);
433                 if (r < 0)
434                         return r;
435
436                 /* Don't accept trailing or leading slashes */
437                 if (startswith(w, "/") || endswith(w, "/")) {
438                         free(w);
439                         return -EINVAL;
440                 }
441
442                 /* Prefix a slash again */
443                 s = strappend("/", w);
444                 free(w);
445                 if (!s)
446                         return -ENOMEM;
447
448                 if (!path_is_normalized(s)) {
449                         free(s);
450                         return -EINVAL;
451                 }
452         }
453
454         if (ret)
455                 *ret = s;
456         else
457                 free(s);
458
459         return 0;
460 }
461
462 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
463         const char *p, *e;
464         char *s;
465         size_t a, b;
466
467         assert(f);
468         assert(i);
469         assert(ret);
470
471         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
472                 return -EINVAL;
473         if (!unit_instance_is_valid(i))
474                 return -EINVAL;
475
476         assert_se(p = strchr(f, '@'));
477         assert_se(e = strrchr(f, '.'));
478
479         a = p - f;
480         b = strlen(i);
481
482         s = new(char, a + 1 + b + strlen(e) + 1);
483         if (!s)
484                 return -ENOMEM;
485
486         strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
487
488         *ret = s;
489         return 0;
490 }
491
492 int unit_name_template(const char *f, char **ret) {
493         const char *p, *e;
494         char *s;
495         size_t a;
496
497         assert(f);
498         assert(ret);
499
500         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
501                 return -EINVAL;
502
503         assert_se(p = strchr(f, '@'));
504         assert_se(e = strrchr(f, '.'));
505
506         a = p - f;
507
508         s = new(char, a + 1 + strlen(e) + 1);
509         if (!s)
510                 return -ENOMEM;
511
512         strcpy(mempcpy(s, f, a + 1), e);
513
514         *ret = s;
515         return 0;
516 }
517
518 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
519         _cleanup_free_ char *p = NULL;
520         char *s = NULL;
521         int r;
522
523         assert(path);
524         assert(suffix);
525         assert(ret);
526
527         if (!unit_suffix_is_valid(suffix))
528                 return -EINVAL;
529
530         r = unit_name_path_escape(path, &p);
531         if (r < 0)
532                 return r;
533
534         s = strappend(p, suffix);
535         if (!s)
536                 return -ENOMEM;
537
538         *ret = s;
539         return 0;
540 }
541
542 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
543         _cleanup_free_ char *p = NULL;
544         char *s;
545         int r;
546
547         assert(prefix);
548         assert(path);
549         assert(suffix);
550         assert(ret);
551
552         if (!unit_prefix_is_valid(prefix))
553                 return -EINVAL;
554
555         if (!unit_suffix_is_valid(suffix))
556                 return -EINVAL;
557
558         r = unit_name_path_escape(path, &p);
559         if (r < 0)
560                 return r;
561
562         s = strjoin(prefix, "@", p, suffix);
563         if (!s)
564                 return -ENOMEM;
565
566         *ret = s;
567         return 0;
568 }
569
570 int unit_name_to_path(const char *name, char **ret) {
571         _cleanup_free_ char *prefix = NULL;
572         int r;
573
574         assert(name);
575
576         r = unit_name_to_prefix(name, &prefix);
577         if (r < 0)
578                 return r;
579
580         return unit_name_path_unescape(prefix, ret);
581 }
582
583 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
584         const char *valid_chars;
585         bool mangled = false;
586
587         assert(f);
588         assert(t);
589
590         /* We'll only escape the obvious characters here, to play safe.
591          *
592          * Returns true if any characters were mangled, false otherwise.
593          */
594
595         valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
596
597         for (; *f; f++)
598                 if (*f == '/') {
599                         *(t++) = '-';
600                         mangled = true;
601                 } else if (!strchr(valid_chars, *f)) {
602                         t = do_escape_char(*f, t);
603                         mangled = true;
604                 } else
605                         *(t++) = *f;
606         *t = 0;
607
608         return mangled;
609 }
610
611 /**
612  *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
613  *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
614  *  except that @suffix is appended if a valid unit suffix is not present.
615  *
616  *  If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
617  */
618 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle flags, const char *suffix, char **ret) {
619         char *s;
620         int r;
621         bool mangled;
622
623         assert(name);
624         assert(suffix);
625         assert(ret);
626
627         if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
628                 return -EINVAL;
629
630         if (!unit_suffix_is_valid(suffix))
631                 return -EINVAL;
632
633         /* Already a fully valid unit name? If so, no mangling is necessary... */
634         if (unit_name_is_valid(name, UNIT_NAME_ANY))
635                 goto good;
636
637         /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
638         if ((flags & UNIT_NAME_MANGLE_GLOB) &&
639             string_is_glob(name) &&
640             in_charset(name, VALID_CHARS_GLOB))
641                 goto good;
642
643         if (is_device_path(name)) {
644                 r = unit_name_from_path(name, ".device", ret);
645                 if (r >= 0)
646                         return 1;
647                 if (r != -EINVAL)
648                         return r;
649         }
650
651         if (path_is_absolute(name)) {
652                 r = unit_name_from_path(name, ".mount", ret);
653                 if (r >= 0)
654                         return 1;
655                 if (r != -EINVAL)
656                         return r;
657         }
658
659         s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
660         if (!s)
661                 return -ENOMEM;
662
663         mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
664         if (mangled)
665                 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
666                          "Invalid unit name \"%s\" was escaped as \"%s\" (maybe you should use systemd-escape?)",
667                          name, s);
668
669         /* 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
670          * valid glob. */
671         if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
672                 strcat(s, suffix);
673
674         *ret = s;
675         return 1;
676
677 good:
678         s = strdup(name);
679         if (!s)
680                 return -ENOMEM;
681
682         *ret = s;
683         return 0;
684 }
685
686 int slice_build_parent_slice(const char *slice, char **ret) {
687         char *s, *dash;
688         int r;
689
690         assert(slice);
691         assert(ret);
692
693         if (!slice_name_is_valid(slice))
694                 return -EINVAL;
695
696         if (streq(slice, SPECIAL_ROOT_SLICE)) {
697                 *ret = NULL;
698                 return 0;
699         }
700
701         s = strdup(slice);
702         if (!s)
703                 return -ENOMEM;
704
705         dash = strrchr(s, '-');
706         if (dash)
707                 strcpy(dash, ".slice");
708         else {
709                 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
710                 if (r < 0) {
711                         free(s);
712                         return r;
713                 }
714         }
715
716         *ret = s;
717         return 1;
718 }
719 #endif // 0
720
721 int slice_build_subslice(const char *slice, const char*name, char **ret) {
722         char *subslice;
723
724         assert(slice);
725         assert(name);
726         assert(ret);
727
728         if (!slice_name_is_valid(slice))
729                 return -EINVAL;
730
731         if (!unit_prefix_is_valid(name))
732                 return -EINVAL;
733
734         if (streq(slice, SPECIAL_ROOT_SLICE))
735                 subslice = strappend(name, ".slice");
736         else {
737                 char *e;
738
739                 assert_se(e = endswith(slice, ".slice"));
740
741                 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
742                 if (!subslice)
743                         return -ENOMEM;
744
745                 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
746         }
747
748         *ret = subslice;
749         return 0;
750 }
751
752 bool slice_name_is_valid(const char *name) {
753         const char *p, *e;
754         bool dash = false;
755
756         if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
757                 return false;
758
759         if (streq(name, SPECIAL_ROOT_SLICE))
760                 return true;
761
762         e = endswith(name, ".slice");
763         if (!e)
764                 return false;
765
766         for (p = name; p < e; p++) {
767
768                 if (*p == '-') {
769
770                         /* Don't allow initial dash */
771                         if (p == name)
772                                 return false;
773
774                         /* Don't allow multiple dashes */
775                         if (dash)
776                                 return false;
777
778                         dash = true;
779                 } else
780                         dash = false;
781         }
782
783         /* Don't allow trailing hash */
784         if (dash)
785                 return false;
786
787         return true;
788 }
789 #if 0 /// UNNEEDED by elogind
790 #endif // 0