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