chiark / gitweb /
Prep v236 : Add missing SPDX-License-Identifier (2/9) src/basic
[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   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stddef.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "alloc-util.h"
28 //#include "glob-util.h"
29 #include "hexdecoct.h"
30 #include "path-util.h"
31 #include "string-util.h"
32 #include "strv.h"
33 #include "unit-name.h"
34
35 /* Characters valid in a unit name. */
36 #define VALID_CHARS                             \
37         DIGITS                                  \
38         LETTERS                                 \
39         ":-_.\\"
40
41 /* The same, but also permits the single @ character that may appear */
42 #define VALID_CHARS_WITH_AT                     \
43         "@"                                     \
44         VALID_CHARS
45
46 /* All chars valid in a unit name glob */
47 #define VALID_CHARS_GLOB                        \
48         VALID_CHARS_WITH_AT                     \
49         "[]!-*?"
50
51 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
52         const char *e, *i, *at;
53
54         assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
55
56         if (_unlikely_(flags == 0))
57                 return false;
58
59         if (isempty(n))
60                 return false;
61
62         if (strlen(n) >= UNIT_NAME_MAX)
63                 return false;
64
65         e = strrchr(n, '.');
66         if (!e || e == n)
67                 return false;
68
69         if (unit_type_from_string(e + 1) < 0)
70                 return false;
71
72         for (i = n, at = NULL; i < e; i++) {
73
74                 if (*i == '@' && !at)
75                         at = i;
76
77                 if (!strchr("@" VALID_CHARS, *i))
78                         return false;
79         }
80
81         if (at == n)
82                 return false;
83
84         if (flags & UNIT_NAME_PLAIN)
85                 if (!at)
86                         return true;
87
88         if (flags & UNIT_NAME_INSTANCE)
89                 if (at && e > at + 1)
90                         return true;
91
92         if (flags & UNIT_NAME_TEMPLATE)
93                 if (at && e == at + 1)
94                         return true;
95
96         return false;
97 }
98
99 bool unit_prefix_is_valid(const char *p) {
100
101         /* We don't allow additional @ in the prefix string */
102
103         if (isempty(p))
104                 return false;
105
106         return in_charset(p, VALID_CHARS);
107 }
108
109 bool unit_instance_is_valid(const char *i) {
110
111         /* The max length depends on the length of the string, so we
112          * don't really check this here. */
113
114         if (isempty(i))
115                 return false;
116
117         /* We allow additional @ in the instance string, we do not
118          * allow them in the prefix! */
119
120         return in_charset(i, "@" VALID_CHARS);
121 }
122
123 bool unit_suffix_is_valid(const char *s) {
124         if (isempty(s))
125                 return false;
126
127         if (s[0] != '.')
128                 return false;
129
130         if (unit_type_from_string(s + 1) < 0)
131                 return false;
132
133         return true;
134 }
135
136 #if 0 /// UNNEEDED by elogind
137 int unit_name_to_prefix(const char *n, char **ret) {
138         const char *p;
139         char *s;
140
141         assert(n);
142         assert(ret);
143
144         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
145                 return -EINVAL;
146
147         p = strchr(n, '@');
148         if (!p)
149                 p = strrchr(n, '.');
150
151         assert_se(p);
152
153         s = strndup(n, p - n);
154         if (!s)
155                 return -ENOMEM;
156
157         *ret = s;
158         return 0;
159 }
160
161 int unit_name_to_instance(const char *n, char **instance) {
162         const char *p, *d;
163         char *i;
164
165         assert(n);
166         assert(instance);
167
168         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
169                 return -EINVAL;
170
171         /* Everything past the first @ and before the last . is the instance */
172         p = strchr(n, '@');
173         if (!p) {
174                 *instance = NULL;
175                 return 0;
176         }
177
178         p++;
179
180         d = strrchr(p, '.');
181         if (!d)
182                 return -EINVAL;
183
184         i = strndup(p, d-p);
185         if (!i)
186                 return -ENOMEM;
187
188         *instance = i;
189         return 1;
190 }
191
192 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
193         const char *d;
194         char *s;
195
196         assert(n);
197         assert(ret);
198
199         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
200                 return -EINVAL;
201
202         d = strrchr(n, '.');
203         if (!d)
204                 return -EINVAL;
205
206         s = strndup(n, d - n);
207         if (!s)
208                 return -ENOMEM;
209
210         *ret = s;
211         return 0;
212 }
213 #endif // 0
214
215 UnitType unit_name_to_type(const char *n) {
216         const char *e;
217
218         assert(n);
219
220         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
221                 return _UNIT_TYPE_INVALID;
222
223         assert_se(e = strrchr(n, '.'));
224
225         return unit_type_from_string(e + 1);
226 }
227
228 #if 0 /// UNNEEDED by elogind
229 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
230         char *e, *s;
231         size_t a, b;
232
233         assert(n);
234         assert(suffix);
235         assert(ret);
236
237         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
238                 return -EINVAL;
239
240         if (!unit_suffix_is_valid(suffix))
241                 return -EINVAL;
242
243         assert_se(e = strrchr(n, '.'));
244
245         a = e - n;
246         b = strlen(suffix);
247
248         s = new(char, a + b + 1);
249         if (!s)
250                 return -ENOMEM;
251
252         strcpy(mempcpy(s, n, a), suffix);
253         *ret = s;
254
255         return 0;
256 }
257 #endif // 0
258
259 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
260         char *s;
261
262         assert(prefix);
263         assert(suffix);
264         assert(ret);
265
266         if (!unit_prefix_is_valid(prefix))
267                 return -EINVAL;
268
269         if (instance && !unit_instance_is_valid(instance))
270                 return -EINVAL;
271
272         if (!unit_suffix_is_valid(suffix))
273                 return -EINVAL;
274
275         if (!instance)
276                 s = strappend(prefix, suffix);
277         else
278                 s = strjoin(prefix, "@", instance, suffix);
279         if (!s)
280                 return -ENOMEM;
281
282         *ret = s;
283         return 0;
284 }
285
286 #if 0 /// UNNEEDED by elogind
287 static char *do_escape_char(char c, char *t) {
288         assert(t);
289
290         *(t++) = '\\';
291         *(t++) = 'x';
292         *(t++) = hexchar(c >> 4);
293         *(t++) = hexchar(c);
294
295         return t;
296 }
297
298 static char *do_escape(const char *f, char *t) {
299         assert(f);
300         assert(t);
301
302         /* do not create units with a leading '.', like for "/.dotdir" mount points */
303         if (*f == '.') {
304                 t = do_escape_char(*f, t);
305                 f++;
306         }
307
308         for (; *f; f++) {
309                 if (*f == '/')
310                         *(t++) = '-';
311                 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
312                         t = do_escape_char(*f, t);
313                 else
314                         *(t++) = *f;
315         }
316
317         return t;
318 }
319
320 char *unit_name_escape(const char *f) {
321         char *r, *t;
322
323         assert(f);
324
325         r = new(char, strlen(f)*4+1);
326         if (!r)
327                 return NULL;
328
329         t = do_escape(f, r);
330         *t = 0;
331
332         return r;
333 }
334
335 int unit_name_unescape(const char *f, char **ret) {
336         _cleanup_free_ char *r = NULL;
337         char *t;
338
339         assert(f);
340
341         r = strdup(f);
342         if (!r)
343                 return -ENOMEM;
344
345         for (t = r; *f; f++) {
346                 if (*f == '-')
347                         *(t++) = '/';
348                 else if (*f == '\\') {
349                         int a, b;
350
351                         if (f[1] != 'x')
352                                 return -EINVAL;
353
354                         a = unhexchar(f[2]);
355                         if (a < 0)
356                                 return -EINVAL;
357
358                         b = unhexchar(f[3]);
359                         if (b < 0)
360                                 return -EINVAL;
361
362                         *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
363                         f += 3;
364                 } else
365                         *(t++) = *f;
366         }
367
368         *t = 0;
369
370         *ret = r;
371         r = NULL;
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_kill_slashes(p);
387
388         if (STR_IN_SET(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 char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
577         const char *valid_chars;
578
579         assert(f);
580         assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
581         assert(t);
582
583         /* We'll only escape the obvious characters here, to play
584          * safe. */
585
586         valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
587
588         for (; *f; f++) {
589                 if (*f == '/')
590                         *(t++) = '-';
591                 else if (!strchr(valid_chars, *f))
592                         t = do_escape_char(*f, t);
593                 else
594                         *(t++) = *f;
595         }
596
597         return t;
598 }
599
600 /**
601  *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
602  *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
603  *  except that @suffix is appended if a valid unit suffix is not present.
604  *
605  *  If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
606  */
607 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
608         char *s, *t;
609         int r;
610
611         assert(name);
612         assert(suffix);
613         assert(ret);
614
615         if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
616                 return -EINVAL;
617
618         if (!unit_suffix_is_valid(suffix))
619                 return -EINVAL;
620
621         /* Already a fully valid unit name? If so, no mangling is necessary... */
622         if (unit_name_is_valid(name, UNIT_NAME_ANY))
623                 goto good;
624
625         /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
626         if (allow_globs == UNIT_NAME_GLOB &&
627             string_is_glob(name) &&
628             in_charset(name, VALID_CHARS_GLOB))
629                 goto good;
630
631         if (is_device_path(name)) {
632                 r = unit_name_from_path(name, ".device", ret);
633                 if (r >= 0)
634                         return 1;
635                 if (r != -EINVAL)
636                         return r;
637         }
638
639         if (path_is_absolute(name)) {
640                 r = unit_name_from_path(name, ".mount", ret);
641                 if (r >= 0)
642                         return 1;
643                 if (r != -EINVAL)
644                         return r;
645         }
646
647         s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
648         if (!s)
649                 return -ENOMEM;
650
651         t = do_escape_mangle(name, allow_globs, s);
652         *t = 0;
653
654         /* 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
655          * valid glob. */
656         if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0)
657                 strcpy(t, suffix);
658
659         *ret = s;
660         return 1;
661
662 good:
663         s = strdup(name);
664         if (!s)
665                 return -ENOMEM;
666
667         *ret = s;
668         return 0;
669 }
670
671 int slice_build_parent_slice(const char *slice, char **ret) {
672         char *s, *dash;
673         int r;
674
675         assert(slice);
676         assert(ret);
677
678         if (!slice_name_is_valid(slice))
679                 return -EINVAL;
680
681         if (streq(slice, "-.slice")) {
682                 *ret = NULL;
683                 return 0;
684         }
685
686         s = strdup(slice);
687         if (!s)
688                 return -ENOMEM;
689
690         dash = strrchr(s, '-');
691         if (dash)
692                 strcpy(dash, ".slice");
693         else {
694                 r = free_and_strdup(&s, "-.slice");
695                 if (r < 0) {
696                         free(s);
697                         return r;
698                 }
699         }
700
701         *ret = s;
702         return 1;
703 }
704 #endif // 0
705
706 int slice_build_subslice(const char *slice, const char*name, char **ret) {
707         char *subslice;
708
709         assert(slice);
710         assert(name);
711         assert(ret);
712
713         if (!slice_name_is_valid(slice))
714                 return -EINVAL;
715
716         if (!unit_prefix_is_valid(name))
717                 return -EINVAL;
718
719         if (streq(slice, "-.slice"))
720                 subslice = strappend(name, ".slice");
721         else {
722                 char *e;
723
724                 assert_se(e = endswith(slice, ".slice"));
725
726                 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
727                 if (!subslice)
728                         return -ENOMEM;
729
730                 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
731         }
732
733         *ret = subslice;
734         return 0;
735 }
736
737 bool slice_name_is_valid(const char *name) {
738         const char *p, *e;
739         bool dash = false;
740
741         if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
742                 return false;
743
744         if (streq(name, "-.slice"))
745                 return true;
746
747         e = endswith(name, ".slice");
748         if (!e)
749                 return false;
750
751         for (p = name; p < e; p++) {
752
753                 if (*p == '-') {
754
755                         /* Don't allow initial dash */
756                         if (p == name)
757                                 return false;
758
759                         /* Don't allow multiple dashes */
760                         if (dash)
761                                 return false;
762
763                         dash = true;
764                 } else
765                         dash = false;
766         }
767
768         /* Don't allow trailing hash */
769         if (dash)
770                 return false;
771
772         return true;
773 }
774 #if 0 /// UNNEEDED by elogind
775 #endif // 0