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