1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <stddef.h>
5 #include <stdint.h>
6 #include <stdlib.h>
7 
8 #include "sd-id128.h"
9 
10 #include "alloc-util.h"
11 #include "glob-util.h"
12 #include "hexdecoct.h"
13 #include "memory-util.h"
14 #include "path-util.h"
15 #include "random-util.h"
16 #include "sparse-endian.h"
17 #include "special.h"
18 #include "stdio-util.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 #define LONG_UNIT_NAME_HASH_KEY SD_ID128_MAKE(ec,f2,37,fb,58,32,4a,32,84,9f,06,9b,0d,21,eb,9a)
40 #define UNIT_NAME_HASH_LENGTH_CHARS 16
41 
unit_name_is_valid(const char * n,UnitNameFlags flags)42 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
43         const char *e, *i, *at;
44 
45         assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
46 
47         if (_unlikely_(flags == 0))
48                 return false;
49 
50         if (isempty(n))
51                 return false;
52 
53         if (strlen(n) >= UNIT_NAME_MAX)
54                 return false;
55 
56         e = strrchr(n, '.');
57         if (!e || e == n)
58                 return false;
59 
60         if (unit_type_from_string(e + 1) < 0)
61                 return false;
62 
63         for (i = n, at = NULL; i < e; i++) {
64 
65                 if (*i == '@' && !at)
66                         at = i;
67 
68                 if (!strchr(VALID_CHARS_WITH_AT, *i))
69                         return false;
70         }
71 
72         if (at == n)
73                 return false;
74 
75         if (flags & UNIT_NAME_PLAIN)
76                 if (!at)
77                         return true;
78 
79         if (flags & UNIT_NAME_INSTANCE)
80                 if (at && e > at + 1)
81                         return true;
82 
83         if (flags & UNIT_NAME_TEMPLATE)
84                 if (at && e == at + 1)
85                         return true;
86 
87         return false;
88 }
89 
unit_prefix_is_valid(const char * p)90 bool unit_prefix_is_valid(const char *p) {
91 
92         /* We don't allow additional @ in the prefix string */
93 
94         if (isempty(p))
95                 return false;
96 
97         return in_charset(p, VALID_CHARS);
98 }
99 
unit_instance_is_valid(const char * i)100 bool unit_instance_is_valid(const char *i) {
101 
102         /* The max length depends on the length of the string, so we
103          * don't really check this here. */
104 
105         if (isempty(i))
106                 return false;
107 
108         /* We allow additional @ in the instance string, we do not
109          * allow them in the prefix! */
110 
111         return in_charset(i, "@" VALID_CHARS);
112 }
113 
unit_suffix_is_valid(const char * s)114 bool unit_suffix_is_valid(const char *s) {
115         if (isempty(s))
116                 return false;
117 
118         if (s[0] != '.')
119                 return false;
120 
121         if (unit_type_from_string(s + 1) < 0)
122                 return false;
123 
124         return true;
125 }
126 
unit_name_to_prefix(const char * n,char ** ret)127 int unit_name_to_prefix(const char *n, char **ret) {
128         const char *p;
129         char *s;
130 
131         assert(n);
132         assert(ret);
133 
134         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
135                 return -EINVAL;
136 
137         p = strchr(n, '@');
138         if (!p)
139                 p = strrchr(n, '.');
140 
141         assert_se(p);
142 
143         s = strndup(n, p - n);
144         if (!s)
145                 return -ENOMEM;
146 
147         *ret = s;
148         return 0;
149 }
150 
unit_name_to_instance(const char * n,char ** ret)151 UnitNameFlags unit_name_to_instance(const char *n, char **ret) {
152         const char *p, *d;
153 
154         assert(n);
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                 if (ret)
163                         *ret = NULL;
164                 return UNIT_NAME_PLAIN;
165         }
166 
167         p++;
168 
169         d = strrchr(p, '.');
170         if (!d)
171                 return -EINVAL;
172 
173         if (ret) {
174                 char *i = strndup(p, d-p);
175                 if (!i)
176                         return -ENOMEM;
177 
178                 *ret = i;
179         }
180         return d > p ? UNIT_NAME_INSTANCE : UNIT_NAME_TEMPLATE;
181 }
182 
unit_name_to_prefix_and_instance(const char * n,char ** ret)183 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
184         const char *d;
185         char *s;
186 
187         assert(n);
188         assert(ret);
189 
190         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
191                 return -EINVAL;
192 
193         d = strrchr(n, '.');
194         if (!d)
195                 return -EINVAL;
196 
197         s = strndup(n, d - n);
198         if (!s)
199                 return -ENOMEM;
200 
201         *ret = s;
202         return 0;
203 }
204 
unit_name_to_type(const char * n)205 UnitType unit_name_to_type(const char *n) {
206         const char *e;
207 
208         assert(n);
209 
210         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
211                 return _UNIT_TYPE_INVALID;
212 
213         assert_se(e = strrchr(n, '.'));
214 
215         return unit_type_from_string(e + 1);
216 }
217 
unit_name_change_suffix(const char * n,const char * suffix,char ** ret)218 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
219         _cleanup_free_ char *s = NULL;
220         size_t a, b;
221         char *e;
222 
223         assert(n);
224         assert(suffix);
225         assert(ret);
226 
227         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
228                 return -EINVAL;
229 
230         if (!unit_suffix_is_valid(suffix))
231                 return -EINVAL;
232 
233         assert_se(e = strrchr(n, '.'));
234 
235         a = e - n;
236         b = strlen(suffix);
237 
238         s = new(char, a + b + 1);
239         if (!s)
240                 return -ENOMEM;
241 
242         strcpy(mempcpy(s, n, a), suffix);
243 
244         /* Make sure the name is still valid (i.e. didn't grow too large due to longer suffix) */
245         if (!unit_name_is_valid(s, UNIT_NAME_ANY))
246                 return -EINVAL;
247 
248         *ret = TAKE_PTR(s);
249         return 0;
250 }
251 
unit_name_build(const char * prefix,const char * instance,const char * suffix,char ** ret)252 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
253         UnitType type;
254 
255         assert(prefix);
256         assert(suffix);
257         assert(ret);
258 
259         if (suffix[0] != '.')
260                 return -EINVAL;
261 
262         type = unit_type_from_string(suffix + 1);
263         if (type < 0)
264                 return type;
265 
266         return unit_name_build_from_type(prefix, instance, type, ret);
267 }
268 
unit_name_build_from_type(const char * prefix,const char * instance,UnitType type,char ** ret)269 int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
270         _cleanup_free_ char *s = NULL;
271         const char *ut;
272 
273         assert(prefix);
274         assert(type >= 0);
275         assert(type < _UNIT_TYPE_MAX);
276         assert(ret);
277 
278         if (!unit_prefix_is_valid(prefix))
279                 return -EINVAL;
280 
281         ut = unit_type_to_string(type);
282 
283         if (instance) {
284                 if (!unit_instance_is_valid(instance))
285                         return -EINVAL;
286 
287                 s = strjoin(prefix, "@", instance, ".", ut);
288         } else
289                 s = strjoin(prefix, ".", ut);
290         if (!s)
291                 return -ENOMEM;
292 
293         /* Verify that this didn't grow too large (or otherwise is invalid) */
294         if (!unit_name_is_valid(s, instance ? UNIT_NAME_INSTANCE : UNIT_NAME_PLAIN))
295                 return -EINVAL;
296 
297         *ret = TAKE_PTR(s);
298         return 0;
299 }
300 
do_escape_char(char c,char * t)301 static char *do_escape_char(char c, char *t) {
302         assert(t);
303 
304         *(t++) = '\\';
305         *(t++) = 'x';
306         *(t++) = hexchar(c >> 4);
307         *(t++) = hexchar(c);
308 
309         return t;
310 }
311 
do_escape(const char * f,char * t)312 static char *do_escape(const char *f, char *t) {
313         assert(f);
314         assert(t);
315 
316         /* do not create units with a leading '.', like for "/.dotdir" mount points */
317         if (*f == '.') {
318                 t = do_escape_char(*f, t);
319                 f++;
320         }
321 
322         for (; *f; f++) {
323                 if (*f == '/')
324                         *(t++) = '-';
325                 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
326                         t = do_escape_char(*f, t);
327                 else
328                         *(t++) = *f;
329         }
330 
331         return t;
332 }
333 
unit_name_escape(const char * f)334 char *unit_name_escape(const char *f) {
335         char *r, *t;
336 
337         assert(f);
338 
339         r = new(char, strlen(f)*4+1);
340         if (!r)
341                 return NULL;
342 
343         t = do_escape(f, r);
344         *t = 0;
345 
346         return r;
347 }
348 
unit_name_unescape(const char * f,char ** ret)349 int unit_name_unescape(const char *f, char **ret) {
350         _cleanup_free_ char *r = NULL;
351         char *t;
352 
353         assert(f);
354 
355         r = strdup(f);
356         if (!r)
357                 return -ENOMEM;
358 
359         for (t = r; *f; f++) {
360                 if (*f == '-')
361                         *(t++) = '/';
362                 else if (*f == '\\') {
363                         int a, b;
364 
365                         if (f[1] != 'x')
366                                 return -EINVAL;
367 
368                         a = unhexchar(f[2]);
369                         if (a < 0)
370                                 return -EINVAL;
371 
372                         b = unhexchar(f[3]);
373                         if (b < 0)
374                                 return -EINVAL;
375 
376                         *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
377                         f += 3;
378                 } else
379                         *(t++) = *f;
380         }
381 
382         *t = 0;
383 
384         *ret = TAKE_PTR(r);
385 
386         return 0;
387 }
388 
unit_name_path_escape(const char * f,char ** ret)389 int unit_name_path_escape(const char *f, char **ret) {
390         _cleanup_free_ char *p = NULL;
391         char *s;
392 
393         assert(f);
394         assert(ret);
395 
396         p = strdup(f);
397         if (!p)
398                 return -ENOMEM;
399 
400         path_simplify(p);
401 
402         if (empty_or_root(p))
403                 s = strdup("-");
404         else {
405                 if (!path_is_normalized(p))
406                         return -EINVAL;
407 
408                 /* Truncate trailing slashes and skip leading slashes */
409                 delete_trailing_chars(p, "/");
410                 s = unit_name_escape(skip_leading_chars(p, "/"));
411         }
412         if (!s)
413                 return -ENOMEM;
414 
415         *ret = s;
416         return 0;
417 }
418 
unit_name_path_unescape(const char * f,char ** ret)419 int unit_name_path_unescape(const char *f, char **ret) {
420         _cleanup_free_ char *s = NULL;
421         int r;
422 
423         assert(f);
424 
425         if (isempty(f))
426                 return -EINVAL;
427 
428         if (streq(f, "-")) {
429                 s = strdup("/");
430                 if (!s)
431                         return -ENOMEM;
432         } else {
433                 _cleanup_free_ char *w = NULL;
434 
435                 r = unit_name_unescape(f, &w);
436                 if (r < 0)
437                         return r;
438 
439                 /* Don't accept trailing or leading slashes */
440                 if (startswith(w, "/") || endswith(w, "/"))
441                         return -EINVAL;
442 
443                 /* Prefix a slash again */
444                 s = strjoin("/", w);
445                 if (!s)
446                         return -ENOMEM;
447 
448                 if (!path_is_normalized(s))
449                         return -EINVAL;
450         }
451 
452         if (ret)
453                 *ret = TAKE_PTR(s);
454 
455         return 0;
456 }
457 
unit_name_replace_instance(const char * f,const char * i,char ** ret)458 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
459         _cleanup_free_ char *s = NULL;
460         const char *p, *e;
461         size_t a, b;
462 
463         assert(f);
464         assert(i);
465         assert(ret);
466 
467         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
468                 return -EINVAL;
469         if (!unit_instance_is_valid(i))
470                 return -EINVAL;
471 
472         assert_se(p = strchr(f, '@'));
473         assert_se(e = strrchr(f, '.'));
474 
475         a = p - f;
476         b = strlen(i);
477 
478         s = new(char, a + 1 + b + strlen(e) + 1);
479         if (!s)
480                 return -ENOMEM;
481 
482         strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
483 
484         /* Make sure the resulting name still is valid, i.e. didn't grow too large */
485         if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
486                 return -EINVAL;
487 
488         *ret = TAKE_PTR(s);
489         return 0;
490 }
491 
unit_name_template(const char * f,char ** ret)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 
unit_name_is_hashed(const char * name)518 bool unit_name_is_hashed(const char *name) {
519         char *s;
520 
521         if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
522                 return false;
523 
524         assert_se(s = strrchr(name, '.'));
525 
526         if (s - name < UNIT_NAME_HASH_LENGTH_CHARS + 1)
527                 return false;
528 
529         s -= UNIT_NAME_HASH_LENGTH_CHARS;
530         if (s[-1] != '_')
531                 return false;
532 
533         for (size_t i = 0; i < UNIT_NAME_HASH_LENGTH_CHARS; i++)
534                 if (!strchr(LOWERCASE_HEXDIGITS, s[i]))
535                         return false;
536 
537         return true;
538 }
539 
unit_name_hash_long(const char * name,char ** ret)540 int unit_name_hash_long(const char *name, char **ret) {
541         _cleanup_free_ char *n = NULL, *hash = NULL;
542         char *suffix;
543         le64_t h;
544         size_t len;
545 
546         if (strlen(name) < UNIT_NAME_MAX)
547                 return -EMSGSIZE;
548 
549         suffix = strrchr(name, '.');
550         if (!suffix)
551                 return -EINVAL;
552 
553         if (unit_type_from_string(suffix+1) < 0)
554                 return -EINVAL;
555 
556         h = htole64(siphash24_string(name, LONG_UNIT_NAME_HASH_KEY.bytes));
557 
558         hash = hexmem(&h, sizeof(h));
559         if (!hash)
560                 return -ENOMEM;
561 
562         assert_se(strlen(hash) == UNIT_NAME_HASH_LENGTH_CHARS);
563 
564         len = UNIT_NAME_MAX - 1 - strlen(suffix+1) - UNIT_NAME_HASH_LENGTH_CHARS - 2;
565         assert(len > 0 && len < UNIT_NAME_MAX);
566 
567         n = strndup(name, len);
568         if (!n)
569                 return -ENOMEM;
570 
571         if (!strextend(&n, "_", hash, suffix))
572                 return -ENOMEM;
573         assert_se(unit_name_is_valid(n, UNIT_NAME_PLAIN));
574 
575         *ret = TAKE_PTR(n);
576 
577         return 0;
578 }
579 
unit_name_from_path(const char * path,const char * suffix,char ** ret)580 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
581         _cleanup_free_ char *p = NULL, *s = NULL;
582         int r;
583 
584         assert(path);
585         assert(suffix);
586         assert(ret);
587 
588         if (!unit_suffix_is_valid(suffix))
589                 return -EINVAL;
590 
591         r = unit_name_path_escape(path, &p);
592         if (r < 0)
593                 return r;
594 
595         s = strjoin(p, suffix);
596         if (!s)
597                 return -ENOMEM;
598 
599         if (strlen(s) >= UNIT_NAME_MAX) {
600                 _cleanup_free_ char *n = NULL;
601 
602                 log_debug("Unit name \"%s\" too long, falling back to hashed unit name.", s);
603 
604                 r = unit_name_hash_long(s, &n);
605                 if (r < 0)
606                         return r;
607 
608                 free_and_replace(s, n);
609         }
610 
611         /* Refuse if this for some other reason didn't result in a valid name */
612         if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
613                 return -EINVAL;
614 
615         *ret = TAKE_PTR(s);
616         return 0;
617 }
618 
unit_name_from_path_instance(const char * prefix,const char * path,const char * suffix,char ** ret)619 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
620         _cleanup_free_ char *p = NULL, *s = NULL;
621         int r;
622 
623         assert(prefix);
624         assert(path);
625         assert(suffix);
626         assert(ret);
627 
628         if (!unit_prefix_is_valid(prefix))
629                 return -EINVAL;
630 
631         if (!unit_suffix_is_valid(suffix))
632                 return -EINVAL;
633 
634         r = unit_name_path_escape(path, &p);
635         if (r < 0)
636                 return r;
637 
638         s = strjoin(prefix, "@", p, suffix);
639         if (!s)
640                 return -ENOMEM;
641 
642         if (strlen(s) >= UNIT_NAME_MAX) /* Return a slightly more descriptive error for this specific condition */
643                 return -ENAMETOOLONG;
644 
645         /* Refuse if this for some other reason didn't result in a valid name */
646         if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
647                 return -EINVAL;
648 
649         *ret = TAKE_PTR(s);
650         return 0;
651 }
652 
unit_name_to_path(const char * name,char ** ret)653 int unit_name_to_path(const char *name, char **ret) {
654         _cleanup_free_ char *prefix = NULL;
655         int r;
656 
657         assert(name);
658 
659         r = unit_name_to_prefix(name, &prefix);
660         if (r < 0)
661                 return r;
662 
663         if (unit_name_is_hashed(name))
664                 return -ENAMETOOLONG;
665 
666         return unit_name_path_unescape(prefix, ret);
667 }
668 
do_escape_mangle(const char * f,bool allow_globs,char * t)669 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
670         const char *valid_chars;
671         bool mangled = false;
672 
673         assert(f);
674         assert(t);
675 
676         /* We'll only escape the obvious characters here, to play safe.
677          *
678          * Returns true if any characters were mangled, false otherwise.
679          */
680 
681         valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
682 
683         for (; *f; f++)
684                 if (*f == '/') {
685                         *(t++) = '-';
686                         mangled = true;
687                 } else if (!strchr(valid_chars, *f)) {
688                         t = do_escape_char(*f, t);
689                         mangled = true;
690                 } else
691                         *(t++) = *f;
692         *t = 0;
693 
694         return mangled;
695 }
696 
697 /**
698  *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
699  *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
700  *  except that @suffix is appended if a valid unit suffix is not present.
701  *
702  *  If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
703  */
unit_name_mangle_with_suffix(const char * name,const char * operation,UnitNameMangle flags,const char * suffix,char ** ret)704 int unit_name_mangle_with_suffix(const char *name, const char *operation, UnitNameMangle flags, const char *suffix, char **ret) {
705         _cleanup_free_ char *s = NULL;
706         bool mangled, suggest_escape = true;
707         int r;
708 
709         assert(name);
710         assert(suffix);
711         assert(ret);
712 
713         if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
714                 return -EINVAL;
715 
716         if (!unit_suffix_is_valid(suffix))
717                 return -EINVAL;
718 
719         /* Already a fully valid unit name? If so, no mangling is necessary... */
720         if (unit_name_is_valid(name, UNIT_NAME_ANY))
721                 goto good;
722 
723         /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
724         if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
725                 if (flags & UNIT_NAME_MANGLE_GLOB)
726                         goto good;
727                 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
728                          "Glob pattern passed%s%s, but globs are not supported for this.",
729                          operation ? " " : "", strempty(operation));
730                 suggest_escape = false;
731         }
732 
733         if (is_device_path(name)) {
734                 r = unit_name_from_path(name, ".device", ret);
735                 if (r >= 0)
736                         return 1;
737                 if (r != -EINVAL)
738                         return r;
739         }
740 
741         if (path_is_absolute(name)) {
742                 r = unit_name_from_path(name, ".mount", ret);
743                 if (r >= 0)
744                         return 1;
745                 if (r != -EINVAL)
746                         return r;
747         }
748 
749         s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
750         if (!s)
751                 return -ENOMEM;
752 
753         mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
754         if (mangled)
755                 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
756                          "Invalid unit name \"%s\" escaped as \"%s\"%s.",
757                          name, s,
758                          suggest_escape ? " (maybe you should use systemd-escape?)" : "");
759 
760         /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
761          * "foo.*" as a valid glob. */
762         if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
763                 strcat(s, suffix);
764 
765         /* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed,
766          * since globs generally do not qualify as valid unit names) */
767         if (!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) && !unit_name_is_valid(s, UNIT_NAME_ANY))
768                 return -EINVAL;
769 
770         *ret = TAKE_PTR(s);
771         return 1;
772 
773 good:
774         s = strdup(name);
775         if (!s)
776                 return -ENOMEM;
777 
778         *ret = TAKE_PTR(s);
779         return 0;
780 }
781 
slice_build_parent_slice(const char * slice,char ** ret)782 int slice_build_parent_slice(const char *slice, char **ret) {
783         _cleanup_free_ char *s = NULL;
784         char *dash;
785         int r;
786 
787         assert(slice);
788         assert(ret);
789 
790         if (!slice_name_is_valid(slice))
791                 return -EINVAL;
792 
793         if (streq(slice, SPECIAL_ROOT_SLICE)) {
794                 *ret = NULL;
795                 return 0;
796         }
797 
798         s = strdup(slice);
799         if (!s)
800                 return -ENOMEM;
801 
802         dash = strrchr(s, '-');
803         if (dash)
804                 strcpy(dash, ".slice");
805         else {
806                 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
807                 if (r < 0)
808                         return r;
809         }
810 
811         *ret = TAKE_PTR(s);
812         return 1;
813 }
814 
slice_build_subslice(const char * slice,const char * name,char ** ret)815 int slice_build_subslice(const char *slice, const char *name, char **ret) {
816         char *subslice;
817 
818         assert(slice);
819         assert(name);
820         assert(ret);
821 
822         if (!slice_name_is_valid(slice))
823                 return -EINVAL;
824 
825         if (!unit_prefix_is_valid(name))
826                 return -EINVAL;
827 
828         if (streq(slice, SPECIAL_ROOT_SLICE))
829                 subslice = strjoin(name, ".slice");
830         else {
831                 char *e;
832 
833                 assert_se(e = endswith(slice, ".slice"));
834 
835                 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
836                 if (!subslice)
837                         return -ENOMEM;
838 
839                 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
840         }
841 
842         *ret = subslice;
843         return 0;
844 }
845 
slice_name_is_valid(const char * name)846 bool slice_name_is_valid(const char *name) {
847         const char *p, *e;
848         bool dash = false;
849 
850         if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
851                 return false;
852 
853         if (streq(name, SPECIAL_ROOT_SLICE))
854                 return true;
855 
856         e = endswith(name, ".slice");
857         if (!e)
858                 return false;
859 
860         for (p = name; p < e; p++) {
861 
862                 if (*p == '-') {
863 
864                         /* Don't allow initial dash */
865                         if (p == name)
866                                 return false;
867 
868                         /* Don't allow multiple dashes */
869                         if (dash)
870                                 return false;
871 
872                         dash = true;
873                 } else
874                         dash = false;
875         }
876 
877         /* Don't allow trailing hash */
878         if (dash)
879                 return false;
880 
881         return true;
882 }
883 
unit_name_prefix_equal(const char * a,const char * b)884 bool unit_name_prefix_equal(const char *a, const char *b) {
885         const char *p, *q;
886 
887         assert(a);
888         assert(b);
889 
890         if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
891                 return false;
892 
893         p = strchr(a, '@');
894         if (!p)
895                 p = strrchr(a, '.');
896 
897         q = strchr(b, '@');
898         if (!q)
899                 q = strrchr(b, '.');
900 
901         assert(p);
902         assert(q);
903 
904         return memcmp_nn(a, p - a, b, q - b) == 0;
905 }
906