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