]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
Merge pull request #28301 from berrange/cvm-lockdown
[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
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
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
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
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
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
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
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
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
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
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 */
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
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
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
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
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 }