]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
8 #include "alloc-util.h"
10 #include "hexdecoct.h"
11 #include "path-util.h"
13 #include "string-util.h"
15 #include "unit-name.h"
17 /* Characters valid in a unit name. */
23 /* The same, but also permits the single @ character that may appear */
24 #define VALID_CHARS_WITH_AT \
28 /* All chars valid in a unit name glob */
29 #define VALID_CHARS_GLOB \
33 bool unit_name_is_valid(const char *n
, UnitNameFlags flags
) {
34 const char *e
, *i
, *at
;
36 assert((flags
& ~(UNIT_NAME_PLAIN
|UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
)) == 0);
38 if (_unlikely_(flags
== 0))
44 if (strlen(n
) >= UNIT_NAME_MAX
)
51 if (unit_type_from_string(e
+ 1) < 0)
54 for (i
= n
, at
= NULL
; i
< e
; i
++) {
59 if (!strchr("@" VALID_CHARS
, *i
))
66 if (flags
& UNIT_NAME_PLAIN
)
70 if (flags
& UNIT_NAME_INSTANCE
)
74 if (flags
& UNIT_NAME_TEMPLATE
)
75 if (at
&& e
== at
+ 1)
81 bool unit_prefix_is_valid(const char *p
) {
83 /* We don't allow additional @ in the prefix string */
88 return in_charset(p
, VALID_CHARS
);
91 bool unit_instance_is_valid(const char *i
) {
93 /* The max length depends on the length of the string, so we
94 * don't really check this here. */
99 /* We allow additional @ in the instance string, we do not
100 * allow them in the prefix! */
102 return in_charset(i
, "@" VALID_CHARS
);
105 bool unit_suffix_is_valid(const char *s
) {
112 if (unit_type_from_string(s
+ 1) < 0)
118 int unit_name_to_prefix(const char *n
, char **ret
) {
125 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
134 s
= strndup(n
, p
- n
);
142 int unit_name_to_instance(const char *n
, char **ret
) {
147 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
150 /* Everything past the first @ and before the last . is the instance */
155 return UNIT_NAME_PLAIN
;
165 char *i
= strndup(p
, d
-p
);
171 return d
> p
? UNIT_NAME_INSTANCE
: UNIT_NAME_TEMPLATE
;
174 int unit_name_to_prefix_and_instance(const char *n
, char **ret
) {
181 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
188 s
= strndup(n
, d
- n
);
196 UnitType
unit_name_to_type(const char *n
) {
201 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
202 return _UNIT_TYPE_INVALID
;
204 assert_se(e
= strrchr(n
, '.'));
206 return unit_type_from_string(e
+ 1);
209 int unit_name_change_suffix(const char *n
, const char *suffix
, char **ret
) {
217 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
220 if (!unit_suffix_is_valid(suffix
))
223 assert_se(e
= strrchr(n
, '.'));
228 s
= new(char, a
+ b
+ 1);
232 strcpy(mempcpy(s
, n
, a
), suffix
);
238 int unit_name_build(const char *prefix
, const char *instance
, const char *suffix
, char **ret
) {
245 if (suffix
[0] != '.')
248 type
= unit_type_from_string(suffix
+ 1);
252 return unit_name_build_from_type(prefix
, instance
, type
, ret
);
255 int unit_name_build_from_type(const char *prefix
, const char *instance
, UnitType type
, char **ret
) {
261 assert(type
< _UNIT_TYPE_MAX
);
264 if (!unit_prefix_is_valid(prefix
))
267 if (instance
&& !unit_instance_is_valid(instance
))
270 ut
= unit_type_to_string(type
);
273 s
= strjoin(prefix
, ".", ut
);
275 s
= strjoin(prefix
, "@", instance
, ".", ut
);
283 static char *do_escape_char(char c
, char *t
) {
288 *(t
++) = hexchar(c
>> 4);
294 static char *do_escape(const char *f
, char *t
) {
298 /* do not create units with a leading '.', like for "/.dotdir" mount points */
300 t
= do_escape_char(*f
, t
);
307 else if (IN_SET(*f
, '-', '\\') || !strchr(VALID_CHARS
, *f
))
308 t
= do_escape_char(*f
, t
);
316 char *unit_name_escape(const char *f
) {
321 r
= new(char, strlen(f
)*4+1);
331 int unit_name_unescape(const char *f
, char **ret
) {
332 _cleanup_free_
char *r
= NULL
;
341 for (t
= r
; *f
; f
++) {
344 else if (*f
== '\\') {
358 *(t
++) = (char) (((uint8_t) a
<< 4U) | (uint8_t) b
);
371 int unit_name_path_escape(const char *f
, char **ret
) {
381 path_simplify(p
, false);
383 if (empty_or_root(p
))
386 if (!path_is_normalized(p
))
389 /* Truncate trailing slashes */
390 delete_trailing_chars(p
, "/");
392 /* Truncate leading slashes */
393 p
= skip_leading_chars(p
, "/");
395 s
= unit_name_escape(p
);
404 int unit_name_path_unescape(const char *f
, char **ret
) {
405 _cleanup_free_
char *s
= NULL
;
418 _cleanup_free_
char *w
= NULL
;
420 r
= unit_name_unescape(f
, &w
);
424 /* Don't accept trailing or leading slashes */
425 if (startswith(w
, "/") || endswith(w
, "/"))
428 /* Prefix a slash again */
433 if (!path_is_normalized(s
))
443 int unit_name_replace_instance(const char *f
, const char *i
, char **ret
) {
452 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
454 if (!unit_instance_is_valid(i
))
457 assert_se(p
= strchr(f
, '@'));
458 assert_se(e
= strrchr(f
, '.'));
463 s
= new(char, a
+ 1 + b
+ strlen(e
) + 1);
467 strcpy(mempcpy(mempcpy(s
, f
, a
+ 1), i
, b
), e
);
473 int unit_name_template(const char *f
, char **ret
) {
481 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
484 assert_se(p
= strchr(f
, '@'));
485 assert_se(e
= strrchr(f
, '.'));
489 s
= new(char, a
+ 1 + strlen(e
) + 1);
493 strcpy(mempcpy(s
, f
, a
+ 1), e
);
499 int unit_name_from_path(const char *path
, const char *suffix
, char **ret
) {
500 _cleanup_free_
char *p
= NULL
;
508 if (!unit_suffix_is_valid(suffix
))
511 r
= unit_name_path_escape(path
, &p
);
515 s
= strjoin(p
, suffix
);
523 int unit_name_from_path_instance(const char *prefix
, const char *path
, const char *suffix
, char **ret
) {
524 _cleanup_free_
char *p
= NULL
;
533 if (!unit_prefix_is_valid(prefix
))
536 if (!unit_suffix_is_valid(suffix
))
539 r
= unit_name_path_escape(path
, &p
);
543 s
= strjoin(prefix
, "@", p
, suffix
);
551 int unit_name_to_path(const char *name
, char **ret
) {
552 _cleanup_free_
char *prefix
= NULL
;
557 r
= unit_name_to_prefix(name
, &prefix
);
561 return unit_name_path_unescape(prefix
, ret
);
564 static bool do_escape_mangle(const char *f
, bool allow_globs
, char *t
) {
565 const char *valid_chars
;
566 bool mangled
= false;
571 /* We'll only escape the obvious characters here, to play safe.
573 * Returns true if any characters were mangled, false otherwise.
576 valid_chars
= allow_globs
? VALID_CHARS_GLOB
: VALID_CHARS_WITH_AT
;
582 } else if (!strchr(valid_chars
, *f
)) {
583 t
= do_escape_char(*f
, t
);
593 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
594 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
595 * except that @suffix is appended if a valid unit suffix is not present.
597 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
599 int unit_name_mangle_with_suffix(const char *name
, const char *operation
, UnitNameMangle flags
, const char *suffix
, char **ret
) {
602 bool mangled
, suggest_escape
= true;
608 if (isempty(name
)) /* We cannot mangle empty unit names to become valid, sorry. */
611 if (!unit_suffix_is_valid(suffix
))
614 /* Already a fully valid unit name? If so, no mangling is necessary... */
615 if (unit_name_is_valid(name
, UNIT_NAME_ANY
))
618 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
619 if (string_is_glob(name
) && in_charset(name
, VALID_CHARS_GLOB
)) {
620 if (flags
& UNIT_NAME_MANGLE_GLOB
)
622 log_full(flags
& UNIT_NAME_MANGLE_WARN
? LOG_NOTICE
: LOG_DEBUG
,
623 "Glob pattern passed%s%s, but globs are not supported for this.",
624 operation
? " " : "", operation
?: "");
625 suggest_escape
= false;
628 if (is_device_path(name
)) {
629 r
= unit_name_from_path(name
, ".device", ret
);
636 if (path_is_absolute(name
)) {
637 r
= unit_name_from_path(name
, ".mount", ret
);
644 s
= new(char, strlen(name
) * 4 + strlen(suffix
) + 1);
648 mangled
= do_escape_mangle(name
, flags
& UNIT_NAME_MANGLE_GLOB
, s
);
650 log_full(flags
& UNIT_NAME_MANGLE_WARN
? LOG_NOTICE
: LOG_DEBUG
,
651 "Invalid unit name \"%s\" escaped as \"%s\"%s.",
653 suggest_escape
? " (maybe you should use systemd-escape?)" : "");
655 /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
656 * "foo.*" as a valid glob. */
657 if ((!(flags
& UNIT_NAME_MANGLE_GLOB
) || !string_is_glob(s
)) && unit_name_to_type(s
) < 0)
672 bool service_unit_name_is_valid(const char *name
) {
673 _cleanup_free_
char *prefix
= NULL
, *s
= NULL
;
674 const char *e
, *service_name
= name
;
676 if (!unit_name_is_valid(name
, UNIT_NAME_ANY
))
679 e
= endswith(name
, ".service");
683 /* If it's a template or instance, get the prefix as a service name. */
684 if (unit_name_is_valid(name
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
)) {
685 if (unit_name_to_prefix(name
, &prefix
) < 0)
688 s
= strjoin(prefix
, ".service");
695 /* Reject reserved service name(s). */
696 if (streq(service_name
, SPECIAL_ROOT_SERVICE
))
702 int slice_build_parent_slice(const char *slice
, char **ret
) {
709 if (!slice_name_is_valid(slice
))
712 if (streq(slice
, SPECIAL_ROOT_SLICE
)) {
721 dash
= strrchr(s
, '-');
723 strcpy(dash
, ".slice");
725 r
= free_and_strdup(&s
, SPECIAL_ROOT_SLICE
);
736 int slice_build_subslice(const char *slice
, const char *name
, char **ret
) {
743 if (!slice_name_is_valid(slice
))
746 if (!unit_prefix_is_valid(name
))
749 if (streq(slice
, SPECIAL_ROOT_SLICE
))
750 subslice
= strjoin(name
, ".slice");
754 assert_se(e
= endswith(slice
, ".slice"));
756 subslice
= new(char, (e
- slice
) + 1 + strlen(name
) + 6 + 1);
760 stpcpy(stpcpy(stpcpy(mempcpy(subslice
, slice
, e
- slice
), "-"), name
), ".slice");
767 bool slice_name_is_valid(const char *name
) {
771 if (!unit_name_is_valid(name
, UNIT_NAME_PLAIN
))
774 if (streq(name
, SPECIAL_ROOT_SLICE
))
777 e
= endswith(name
, ".slice");
781 for (p
= name
; p
< e
; p
++) {
785 /* Don't allow initial dash */
789 /* Don't allow multiple dashes */
798 /* Don't allow trailing hash */