1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
9 #include "memory-util.h"
10 #include "path-util.h"
11 #include "sparse-endian.h"
13 #include "siphash24.h"
14 #include "string-util.h"
16 #include "unit-name.h"
18 /* Characters valid in a unit name. */
24 /* The same, but also permits the single @ character that may appear */
25 #define VALID_CHARS_WITH_AT \
29 /* All chars valid in a unit name glob */
30 #define VALID_CHARS_GLOB \
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
37 bool unit_name_is_valid(const char *n
, UnitNameFlags flags
) {
38 const char *e
, *i
, *at
;
40 assert((flags
& ~(UNIT_NAME_PLAIN
|UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
)) == 0);
42 if (_unlikely_(flags
== 0))
48 if (strlen(n
) >= UNIT_NAME_MAX
)
55 if (unit_type_from_string(e
+ 1) < 0)
58 for (i
= n
, at
= NULL
; i
< e
; i
++) {
63 if (!strchr(VALID_CHARS_WITH_AT
, *i
))
70 if (flags
& UNIT_NAME_PLAIN
)
74 if (flags
& UNIT_NAME_INSTANCE
)
78 if (flags
& UNIT_NAME_TEMPLATE
)
79 if (at
&& e
== at
+ 1)
85 bool unit_prefix_is_valid(const char *p
) {
87 /* We don't allow additional @ in the prefix string */
92 return in_charset(p
, VALID_CHARS
);
95 bool unit_instance_is_valid(const char *i
) {
97 /* The max length depends on the length of the string, so we
98 * don't really check this here. */
103 /* We allow additional @ in the instance string, we do not
104 * allow them in the prefix! */
106 return in_charset(i
, "@" VALID_CHARS
);
109 bool unit_suffix_is_valid(const char *s
) {
116 if (unit_type_from_string(s
+ 1) < 0)
122 int unit_name_to_prefix(const char *n
, char **ret
) {
129 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
138 s
= strndup(n
, p
- n
);
146 UnitNameFlags
unit_name_to_instance(const char *n
, char **ret
) {
151 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
154 /* Everything past the first @ and before the last . is the instance */
159 return UNIT_NAME_PLAIN
;
169 char *i
= strndup(p
, d
-p
);
175 return d
> p
? UNIT_NAME_INSTANCE
: UNIT_NAME_TEMPLATE
;
178 int unit_name_to_prefix_and_instance(const char *n
, char **ret
) {
185 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
192 s
= strndup(n
, d
- n
);
200 UnitType
unit_name_to_type(const char *n
) {
205 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
206 return _UNIT_TYPE_INVALID
;
208 assert_se(e
= strrchr(n
, '.'));
210 return unit_type_from_string(e
+ 1);
213 int unit_name_change_suffix(const char *n
, const char *suffix
, char **ret
) {
214 _cleanup_free_
char *s
= NULL
;
222 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
225 if (!unit_suffix_is_valid(suffix
))
228 assert_se(e
= strrchr(n
, '.'));
233 s
= new(char, a
+ b
+ 1);
237 strcpy(mempcpy(s
, n
, a
), suffix
);
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
))
247 int unit_name_build(const char *prefix
, const char *instance
, const char *suffix
, char **ret
) {
254 if (suffix
[0] != '.')
257 type
= unit_type_from_string(suffix
+ 1);
261 return unit_name_build_from_type(prefix
, instance
, type
, ret
);
264 int unit_name_build_from_type(const char *prefix
, const char *instance
, UnitType type
, char **ret
) {
265 _cleanup_free_
char *s
= NULL
;
270 assert(type
< _UNIT_TYPE_MAX
);
273 if (!unit_prefix_is_valid(prefix
))
276 ut
= unit_type_to_string(type
);
279 if (!unit_instance_is_valid(instance
))
282 s
= strjoin(prefix
, "@", instance
, ".", ut
);
284 s
= strjoin(prefix
, ".", ut
);
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
))
296 static char *do_escape_char(char c
, char *t
) {
301 *(t
++) = hexchar(c
>> 4);
307 static char *do_escape(const char *f
, char *t
) {
311 /* do not create units with a leading '.', like for "/.dotdir" mount points */
313 t
= do_escape_char(*f
, t
);
320 else if (IN_SET(*f
, '-', '\\') || !strchr(VALID_CHARS
, *f
))
321 t
= do_escape_char(*f
, t
);
329 char* unit_name_escape(const char *f
) {
334 r
= new(char, strlen(f
)*4+1);
344 int unit_name_unescape(const char *f
, char **ret
) {
345 _cleanup_free_
char *r
= NULL
;
354 for (t
= r
; *f
; f
++) {
357 else if (*f
== '\\') {
371 *(t
++) = (char) (((uint8_t) a
<< 4U) | (uint8_t) b
);
384 int unit_name_path_escape(const char *f
, char **ret
) {
385 _cleanup_free_
char *p
= NULL
;
392 r
= path_simplify_alloc(f
, &p
);
396 if (empty_or_root(p
))
399 if (!path_is_normalized(p
))
402 /* Truncate trailing slashes and skip leading slashes */
403 delete_trailing_chars(p
, "/");
404 s
= unit_name_escape(skip_leading_chars(p
, "/"));
413 int unit_name_path_unescape(const char *f
, char **ret
) {
414 _cleanup_free_
char *s
= NULL
;
427 _cleanup_free_
char *w
= NULL
;
429 r
= unit_name_unescape(f
, &w
);
433 /* Don't accept trailing or leading slashes */
434 if (startswith(w
, "/") || endswith(w
, "/"))
437 /* Prefix a slash again */
442 if (!path_is_normalized(s
))
452 int unit_name_replace_instance_full(
453 const char *original
,
454 const char *instance
,
458 _cleanup_free_
char *s
= NULL
;
459 const char *prefix
, *suffix
;
466 if (!unit_name_is_valid(original
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
468 if (!unit_instance_is_valid(instance
) && !(accept_glob
&& in_charset(instance
, VALID_CHARS_GLOB
)))
471 prefix
= ASSERT_PTR(strchr(original
, '@'));
472 suffix
= ASSERT_PTR(strrchr(original
, '.'));
473 assert(prefix
< suffix
);
475 pl
= prefix
- original
+ 1; /* include '@' */
477 s
= new(char, pl
+ strlen(instance
) + strlen(suffix
) + 1);
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);
486 strcpy(stpcpy(stpncpy(s
, original
, pl
), instance
), suffix
);
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
))
497 int unit_name_template(const char *f
, char **ret
) {
505 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
508 assert_se(p
= strchr(f
, '@'));
509 assert_se(e
= strrchr(f
, '.'));
513 s
= new(char, a
+ 1 + strlen(e
) + 1);
517 strcpy(mempcpy(s
, f
, a
+ 1), e
);
523 bool unit_name_is_hashed(const char *name
) {
526 if (!unit_name_is_valid(name
, UNIT_NAME_PLAIN
))
529 assert_se(s
= strrchr(name
, '.'));
531 if (s
- name
< UNIT_NAME_HASH_LENGTH_CHARS
+ 1)
534 s
-= UNIT_NAME_HASH_LENGTH_CHARS
;
538 for (size_t i
= 0; i
< UNIT_NAME_HASH_LENGTH_CHARS
; i
++)
539 if (!strchr(LOWERCASE_HEXDIGITS
, s
[i
]))
545 int unit_name_hash_long(const char *name
, char **ret
) {
546 _cleanup_free_
char *n
= NULL
, *hash
= NULL
;
551 if (strlen(name
) < UNIT_NAME_MAX
)
554 suffix
= strrchr(name
, '.');
558 if (unit_type_from_string(suffix
+1) < 0)
561 h
= htole64(siphash24_string(name
, LONG_UNIT_NAME_HASH_KEY
.bytes
));
563 hash
= hexmem(&h
, sizeof(h
));
567 assert_se(strlen(hash
) == UNIT_NAME_HASH_LENGTH_CHARS
);
569 len
= UNIT_NAME_MAX
- 1 - strlen(suffix
+1) - UNIT_NAME_HASH_LENGTH_CHARS
- 2;
570 assert(len
> 0 && len
< UNIT_NAME_MAX
);
572 n
= strndup(name
, len
);
576 if (!strextend(&n
, "_", hash
, suffix
))
578 assert_se(unit_name_is_valid(n
, UNIT_NAME_PLAIN
));
585 int unit_name_from_path(const char *path
, const char *suffix
, char **ret
) {
586 _cleanup_free_
char *p
= NULL
, *s
= NULL
;
593 if (!unit_suffix_is_valid(suffix
))
596 r
= unit_name_path_escape(path
, &p
);
600 s
= strjoin(p
, suffix
);
604 if (strlen(s
) >= UNIT_NAME_MAX
) {
605 _cleanup_free_
char *n
= NULL
;
607 log_debug("Unit name \"%s\" too long, falling back to hashed unit name.", s
);
609 r
= unit_name_hash_long(s
, &n
);
613 free_and_replace(s
, n
);
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
))
624 int unit_name_from_path_instance(const char *prefix
, const char *path
, const char *suffix
, char **ret
) {
625 _cleanup_free_
char *p
= NULL
, *s
= NULL
;
633 if (!unit_prefix_is_valid(prefix
))
636 if (!unit_suffix_is_valid(suffix
))
639 r
= unit_name_path_escape(path
, &p
);
643 s
= strjoin(prefix
, "@", p
, suffix
);
647 if (strlen(s
) >= UNIT_NAME_MAX
) /* Return a slightly more descriptive error for this specific condition */
648 return -ENAMETOOLONG
;
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
))
658 int unit_name_to_path(const char *name
, char **ret
) {
659 _cleanup_free_
char *prefix
= NULL
;
664 r
= unit_name_to_prefix(name
, &prefix
);
668 if (unit_name_is_hashed(name
))
669 return -ENAMETOOLONG
;
671 return unit_name_path_unescape(prefix
, ret
);
674 static bool do_escape_mangle(const char *f
, bool allow_globs
, char *t
) {
675 const char *valid_chars
;
676 bool mangled
= false;
681 /* We'll only escape the obvious characters here, to play safe.
683 * Returns true if any characters were mangled, false otherwise.
686 valid_chars
= allow_globs
? VALID_CHARS_GLOB
: VALID_CHARS_WITH_AT
;
692 } else if (!strchr(valid_chars
, *f
)) {
693 t
= do_escape_char(*f
, t
);
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.
707 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
709 int unit_name_mangle_with_suffix(
711 const char *operation
,
712 UnitNameMangle flags
,
716 _cleanup_free_
char *s
= NULL
;
717 bool mangled
, suggest_escape
= true, warn
= flags
& UNIT_NAME_MANGLE_WARN
;
724 if (isempty(name
)) /* We cannot mangle empty unit names to become valid, sorry. */
727 if (!unit_suffix_is_valid(suffix
))
730 /* Already a fully valid unit name? If so, no mangling is necessary... */
731 if (unit_name_is_valid(name
, UNIT_NAME_ANY
))
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
)
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;
744 if (path_is_absolute(name
)) {
745 _cleanup_free_
char *n
= NULL
;
747 r
= path_simplify_alloc(name
, &n
);
751 if (is_device_path(n
)) {
752 r
= unit_name_from_path(n
, ".device", ret
);
759 r
= unit_name_from_path(n
, ".mount", ret
);
766 s
= new(char, strlen(name
) * 4 + strlen(suffix
) + 1);
770 mangled
= do_escape_mangle(name
, flags
& UNIT_NAME_MANGLE_GLOB
, s
);
772 log_full(warn
? LOG_NOTICE
: LOG_DEBUG
,
773 "Invalid unit name \"%s\" escaped as \"%s\"%s.",
775 suggest_escape
? " (maybe you should use systemd-escape?)" : "");
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)
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
))
791 return strdup_to(ret
, name
);
794 int slice_build_parent_slice(const char *slice
, char **ret
) {
798 if (!slice_name_is_valid(slice
))
801 if (streq(slice
, SPECIAL_ROOT_SLICE
)) {
806 _cleanup_free_
char *s
= strdup(slice
);
810 char *dash
= strrchr(s
, '-');
812 return strdup_to_full(ret
, SPECIAL_ROOT_SLICE
);
814 /* We know that s ended with .slice before truncation, so we have enough space. */
815 strcpy(dash
, ".slice");
821 int slice_build_subslice(const char *slice
, const char *name
, char **ret
) {
828 if (!slice_name_is_valid(slice
))
831 if (!unit_prefix_is_valid(name
))
834 if (streq(slice
, SPECIAL_ROOT_SLICE
))
835 subslice
= strjoin(name
, ".slice");
839 assert_se(e
= endswith(slice
, ".slice"));
841 subslice
= new(char, (e
- slice
) + 1 + strlen(name
) + 6 + 1);
845 stpcpy(stpcpy(stpcpy(mempcpy(subslice
, slice
, e
- slice
), "-"), name
), ".slice");
852 bool slice_name_is_valid(const char *name
) {
856 if (!unit_name_is_valid(name
, UNIT_NAME_PLAIN
))
859 if (streq(name
, SPECIAL_ROOT_SLICE
))
862 e
= endswith(name
, ".slice");
866 for (p
= name
; p
< e
; p
++) {
870 /* Don't allow initial dash */
874 /* Don't allow multiple dashes */
883 /* Don't allow trailing hash */
890 bool unit_name_prefix_equal(const char *a
, const char *b
) {
896 if (!unit_name_is_valid(a
, UNIT_NAME_ANY
) || !unit_name_is_valid(b
, UNIT_NAME_ANY
))
910 return memcmp_nn(a
, p
- a
, b
, q
- b
) == 0;