]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
hexdecoct: make unbase64mem and unhexmem always use SIZE_MAX
[thirdparty/systemd.git] / src / basic / unit-name.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
389 int unit_name_path_escape(const char *f, char **ret) {
390 _cleanup_free_ char *p = NULL;
391 char *s;
392 int r;
393
394 assert(f);
395 assert(ret);
396
397 r = path_simplify_alloc(f, &p);
398 if (r < 0)
399 return r;
400
401 if (empty_or_root(p))
402 s = strdup("-");
403 else {
404 if (!path_is_normalized(p))
405 return -EINVAL;
406
407 /* Truncate trailing slashes and skip leading slashes */
408 delete_trailing_chars(p, "/");
409 s = unit_name_escape(skip_leading_chars(p, "/"));
410 }
411 if (!s)
412 return -ENOMEM;
413
414 *ret = s;
415 return 0;
416 }
417
418 int unit_name_path_unescape(const char *f, char **ret) {
419 _cleanup_free_ char *s = NULL;
420 int r;
421
422 assert(f);
423
424 if (isempty(f))
425 return -EINVAL;
426
427 if (streq(f, "-")) {
428 s = strdup("/");
429 if (!s)
430 return -ENOMEM;
431 } else {
432 _cleanup_free_ char *w = NULL;
433
434 r = unit_name_unescape(f, &w);
435 if (r < 0)
436 return r;
437
438 /* Don't accept trailing or leading slashes */
439 if (startswith(w, "/") || endswith(w, "/"))
440 return -EINVAL;
441
442 /* Prefix a slash again */
443 s = strjoin("/", w);
444 if (!s)
445 return -ENOMEM;
446
447 if (!path_is_normalized(s))
448 return -EINVAL;
449 }
450
451 if (ret)
452 *ret = TAKE_PTR(s);
453
454 return 0;
455 }
456
457 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
458 _cleanup_free_ char *s = NULL;
459 const char *p, *e;
460 size_t a, b;
461
462 assert(f);
463 assert(i);
464 assert(ret);
465
466 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
467 return -EINVAL;
468 if (!unit_instance_is_valid(i))
469 return -EINVAL;
470
471 assert_se(p = strchr(f, '@'));
472 assert_se(e = strrchr(f, '.'));
473
474 a = p - f;
475 b = strlen(i);
476
477 s = new(char, a + 1 + b + strlen(e) + 1);
478 if (!s)
479 return -ENOMEM;
480
481 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
482
483 /* Make sure the resulting name still is valid, i.e. didn't grow too large */
484 if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
485 return -EINVAL;
486
487 *ret = TAKE_PTR(s);
488 return 0;
489 }
490
491 int unit_name_template(const char *f, char **ret) {
492 const char *p, *e;
493 char *s;
494 size_t a;
495
496 assert(f);
497 assert(ret);
498
499 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
500 return -EINVAL;
501
502 assert_se(p = strchr(f, '@'));
503 assert_se(e = strrchr(f, '.'));
504
505 a = p - f;
506
507 s = new(char, a + 1 + strlen(e) + 1);
508 if (!s)
509 return -ENOMEM;
510
511 strcpy(mempcpy(s, f, a + 1), e);
512
513 *ret = s;
514 return 0;
515 }
516
517 bool unit_name_is_hashed(const char *name) {
518 char *s;
519
520 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
521 return false;
522
523 assert_se(s = strrchr(name, '.'));
524
525 if (s - name < UNIT_NAME_HASH_LENGTH_CHARS + 1)
526 return false;
527
528 s -= UNIT_NAME_HASH_LENGTH_CHARS;
529 if (s[-1] != '_')
530 return false;
531
532 for (size_t i = 0; i < UNIT_NAME_HASH_LENGTH_CHARS; i++)
533 if (!strchr(LOWERCASE_HEXDIGITS, s[i]))
534 return false;
535
536 return true;
537 }
538
539 int unit_name_hash_long(const char *name, char **ret) {
540 _cleanup_free_ char *n = NULL, *hash = NULL;
541 char *suffix;
542 le64_t h;
543 size_t len;
544
545 if (strlen(name) < UNIT_NAME_MAX)
546 return -EMSGSIZE;
547
548 suffix = strrchr(name, '.');
549 if (!suffix)
550 return -EINVAL;
551
552 if (unit_type_from_string(suffix+1) < 0)
553 return -EINVAL;
554
555 h = htole64(siphash24_string(name, LONG_UNIT_NAME_HASH_KEY.bytes));
556
557 hash = hexmem(&h, sizeof(h));
558 if (!hash)
559 return -ENOMEM;
560
561 assert_se(strlen(hash) == UNIT_NAME_HASH_LENGTH_CHARS);
562
563 len = UNIT_NAME_MAX - 1 - strlen(suffix+1) - UNIT_NAME_HASH_LENGTH_CHARS - 2;
564 assert(len > 0 && len < UNIT_NAME_MAX);
565
566 n = strndup(name, len);
567 if (!n)
568 return -ENOMEM;
569
570 if (!strextend(&n, "_", hash, suffix))
571 return -ENOMEM;
572 assert_se(unit_name_is_valid(n, UNIT_NAME_PLAIN));
573
574 *ret = TAKE_PTR(n);
575
576 return 0;
577 }
578
579 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
580 _cleanup_free_ char *p = NULL, *s = NULL;
581 int r;
582
583 assert(path);
584 assert(suffix);
585 assert(ret);
586
587 if (!unit_suffix_is_valid(suffix))
588 return -EINVAL;
589
590 r = unit_name_path_escape(path, &p);
591 if (r < 0)
592 return r;
593
594 s = strjoin(p, suffix);
595 if (!s)
596 return -ENOMEM;
597
598 if (strlen(s) >= UNIT_NAME_MAX) {
599 _cleanup_free_ char *n = NULL;
600
601 log_debug("Unit name \"%s\" too long, falling back to hashed unit name.", s);
602
603 r = unit_name_hash_long(s, &n);
604 if (r < 0)
605 return r;
606
607 free_and_replace(s, n);
608 }
609
610 /* Refuse if this for some other reason didn't result in a valid name */
611 if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
612 return -EINVAL;
613
614 *ret = TAKE_PTR(s);
615 return 0;
616 }
617
618 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
619 _cleanup_free_ char *p = NULL, *s = NULL;
620 int r;
621
622 assert(prefix);
623 assert(path);
624 assert(suffix);
625 assert(ret);
626
627 if (!unit_prefix_is_valid(prefix))
628 return -EINVAL;
629
630 if (!unit_suffix_is_valid(suffix))
631 return -EINVAL;
632
633 r = unit_name_path_escape(path, &p);
634 if (r < 0)
635 return r;
636
637 s = strjoin(prefix, "@", p, suffix);
638 if (!s)
639 return -ENOMEM;
640
641 if (strlen(s) >= UNIT_NAME_MAX) /* Return a slightly more descriptive error for this specific condition */
642 return -ENAMETOOLONG;
643
644 /* Refuse if this for some other reason didn't result in a valid name */
645 if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
646 return -EINVAL;
647
648 *ret = TAKE_PTR(s);
649 return 0;
650 }
651
652 int unit_name_to_path(const char *name, char **ret) {
653 _cleanup_free_ char *prefix = NULL;
654 int r;
655
656 assert(name);
657
658 r = unit_name_to_prefix(name, &prefix);
659 if (r < 0)
660 return r;
661
662 if (unit_name_is_hashed(name))
663 return -ENAMETOOLONG;
664
665 return unit_name_path_unescape(prefix, ret);
666 }
667
668 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
669 const char *valid_chars;
670 bool mangled = false;
671
672 assert(f);
673 assert(t);
674
675 /* We'll only escape the obvious characters here, to play safe.
676 *
677 * Returns true if any characters were mangled, false otherwise.
678 */
679
680 valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
681
682 for (; *f; f++)
683 if (*f == '/') {
684 *(t++) = '-';
685 mangled = true;
686 } else if (!strchr(valid_chars, *f)) {
687 t = do_escape_char(*f, t);
688 mangled = true;
689 } else
690 *(t++) = *f;
691 *t = 0;
692
693 return mangled;
694 }
695
696 /**
697 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
698 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
699 * except that @suffix is appended if a valid unit suffix is not present.
700 *
701 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
702 */
703 int unit_name_mangle_with_suffix(
704 const char *name,
705 const char *operation,
706 UnitNameMangle flags,
707 const char *suffix,
708 char **ret) {
709
710 _cleanup_free_ char *s = NULL;
711 bool mangled, suggest_escape = true, warn = flags & UNIT_NAME_MANGLE_WARN;
712 int r;
713
714 assert(name);
715 assert(suffix);
716 assert(ret);
717
718 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
719 return -EINVAL;
720
721 if (!unit_suffix_is_valid(suffix))
722 return -EINVAL;
723
724 /* Already a fully valid unit name? If so, no mangling is necessary... */
725 if (unit_name_is_valid(name, UNIT_NAME_ANY))
726 goto good;
727
728 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
729 if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
730 if (flags & UNIT_NAME_MANGLE_GLOB)
731 goto good;
732 log_full(warn ? LOG_NOTICE : LOG_DEBUG,
733 "Glob pattern passed%s%s, but globs are not supported for this.",
734 operation ? " " : "", strempty(operation));
735 suggest_escape = false;
736 }
737
738 if (path_is_absolute(name)) {
739 _cleanup_free_ char *n = NULL;
740
741 r = path_simplify_alloc(name, &n);
742 if (r < 0)
743 return r;
744
745 if (is_device_path(n)) {
746 r = unit_name_from_path(n, ".device", ret);
747 if (r >= 0)
748 return 1;
749 if (r != -EINVAL)
750 return r;
751 }
752
753 r = unit_name_from_path(n, ".mount", ret);
754 if (r >= 0)
755 return 1;
756 if (r != -EINVAL)
757 return r;
758 }
759
760 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
761 if (!s)
762 return -ENOMEM;
763
764 mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
765 if (mangled)
766 log_full(warn ? LOG_NOTICE : LOG_DEBUG,
767 "Invalid unit name \"%s\" escaped as \"%s\"%s.",
768 name, s,
769 suggest_escape ? " (maybe you should use systemd-escape?)" : "");
770
771 /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
772 * "foo.*" as a valid glob. */
773 if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
774 strcat(s, suffix);
775
776 /* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed,
777 * since globs generally do not qualify as valid unit names) */
778 if (!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) && !unit_name_is_valid(s, UNIT_NAME_ANY))
779 return -EINVAL;
780
781 *ret = TAKE_PTR(s);
782 return 1;
783
784 good:
785 s = strdup(name);
786 if (!s)
787 return -ENOMEM;
788
789 *ret = TAKE_PTR(s);
790 return 0;
791 }
792
793 int slice_build_parent_slice(const char *slice, char **ret) {
794 _cleanup_free_ char *s = NULL;
795 char *dash;
796 int r;
797
798 assert(slice);
799 assert(ret);
800
801 if (!slice_name_is_valid(slice))
802 return -EINVAL;
803
804 if (streq(slice, SPECIAL_ROOT_SLICE)) {
805 *ret = NULL;
806 return 0;
807 }
808
809 s = strdup(slice);
810 if (!s)
811 return -ENOMEM;
812
813 dash = strrchr(s, '-');
814 if (dash)
815 strcpy(dash, ".slice");
816 else {
817 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
818 if (r < 0)
819 return r;
820 }
821
822 *ret = TAKE_PTR(s);
823 return 1;
824 }
825
826 int slice_build_subslice(const char *slice, const char *name, char **ret) {
827 char *subslice;
828
829 assert(slice);
830 assert(name);
831 assert(ret);
832
833 if (!slice_name_is_valid(slice))
834 return -EINVAL;
835
836 if (!unit_prefix_is_valid(name))
837 return -EINVAL;
838
839 if (streq(slice, SPECIAL_ROOT_SLICE))
840 subslice = strjoin(name, ".slice");
841 else {
842 char *e;
843
844 assert_se(e = endswith(slice, ".slice"));
845
846 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
847 if (!subslice)
848 return -ENOMEM;
849
850 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
851 }
852
853 *ret = subslice;
854 return 0;
855 }
856
857 bool slice_name_is_valid(const char *name) {
858 const char *p, *e;
859 bool dash = false;
860
861 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
862 return false;
863
864 if (streq(name, SPECIAL_ROOT_SLICE))
865 return true;
866
867 e = endswith(name, ".slice");
868 if (!e)
869 return false;
870
871 for (p = name; p < e; p++) {
872
873 if (*p == '-') {
874
875 /* Don't allow initial dash */
876 if (p == name)
877 return false;
878
879 /* Don't allow multiple dashes */
880 if (dash)
881 return false;
882
883 dash = true;
884 } else
885 dash = false;
886 }
887
888 /* Don't allow trailing hash */
889 if (dash)
890 return false;
891
892 return true;
893 }
894
895 bool unit_name_prefix_equal(const char *a, const char *b) {
896 const char *p, *q;
897
898 assert(a);
899 assert(b);
900
901 if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
902 return false;
903
904 p = strchr(a, '@');
905 if (!p)
906 p = strrchr(a, '.');
907
908 q = strchr(b, '@');
909 if (!q)
910 q = strrchr(b, '.');
911
912 assert(p);
913 assert(q);
914
915 return memcmp_nn(a, p - a, b, q - b) == 0;
916 }