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