1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "alloc-util.h"
10 //#include "glob-util.h"
11 #include "hexdecoct.h"
12 #include "path-util.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 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
35 const char *e, *i, *at;
37 assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
39 if (_unlikely_(flags == 0))
45 if (strlen(n) >= UNIT_NAME_MAX)
52 if (unit_type_from_string(e + 1) < 0)
55 for (i = n, at = NULL; i < e; i++) {
60 if (!strchr("@" VALID_CHARS, *i))
67 if (flags & UNIT_NAME_PLAIN)
71 if (flags & UNIT_NAME_INSTANCE)
75 if (flags & UNIT_NAME_TEMPLATE)
76 if (at && e == at + 1)
82 bool unit_prefix_is_valid(const char *p) {
84 /* We don't allow additional @ in the prefix string */
89 return in_charset(p, VALID_CHARS);
92 bool unit_instance_is_valid(const char *i) {
94 /* The max length depends on the length of the string, so we
95 * don't really check this here. */
100 /* We allow additional @ in the instance string, we do not
101 * allow them in the prefix! */
103 return in_charset(i, "@" VALID_CHARS);
106 bool unit_suffix_is_valid(const char *s) {
113 if (unit_type_from_string(s + 1) < 0)
119 #if 0 /// UNNEEDED by elogind
120 int unit_name_to_prefix(const char *n, char **ret) {
127 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
136 s = strndup(n, p - n);
144 int unit_name_to_instance(const char *n, char **instance) {
151 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
154 /* Everything past the first @ and before the last . is the instance */
175 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
182 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
189 s = strndup(n, d - n);
198 UnitType unit_name_to_type(const char *n) {
203 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
204 return _UNIT_TYPE_INVALID;
206 assert_se(e = strrchr(n, '.'));
208 return unit_type_from_string(e + 1);
211 #if 0 /// UNNEEDED by elogind
212 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
220 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
223 if (!unit_suffix_is_valid(suffix))
226 assert_se(e = strrchr(n, '.'));
231 s = new(char, a + b + 1);
235 strcpy(mempcpy(s, n, a), suffix);
242 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
249 if (suffix[0] != '.')
252 type = unit_type_from_string(suffix + 1);
256 return unit_name_build_from_type(prefix, instance, type, ret);
259 int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
265 assert(type < _UNIT_TYPE_MAX);
268 if (!unit_prefix_is_valid(prefix))
271 if (instance && !unit_instance_is_valid(instance))
274 ut = unit_type_to_string(type);
277 s = strjoin(prefix, ".", ut);
279 s = strjoin(prefix, "@", instance, ".", ut);
287 #if 0 /// UNNEEDED by elogind
288 static char *do_escape_char(char c, char *t) {
293 *(t++) = hexchar(c >> 4);
299 static char *do_escape(const char *f, char *t) {
303 /* do not create units with a leading '.', like for "/.dotdir" mount points */
305 t = do_escape_char(*f, t);
312 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
313 t = do_escape_char(*f, t);
321 char *unit_name_escape(const char *f) {
326 r = new(char, strlen(f)*4+1);
336 int unit_name_unescape(const char *f, char **ret) {
337 _cleanup_free_ char *r = NULL;
346 for (t = r; *f; f++) {
349 else if (*f == '\\') {
363 *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
376 int unit_name_path_escape(const char *f, char **ret) {
386 path_simplify(p, false);
388 if (empty_or_root(p))
391 if (!path_is_normalized(p))
394 /* Truncate trailing slashes */
395 delete_trailing_chars(p, "/");
397 /* Truncate leading slashes */
398 p = skip_leading_chars(p, "/");
400 s = unit_name_escape(p);
409 int unit_name_path_unescape(const char *f, char **ret) {
425 r = unit_name_unescape(f, &w);
429 /* Don't accept trailing or leading slashes */
430 if (startswith(w, "/") || endswith(w, "/")) {
435 /* Prefix a slash again */
436 s = strappend("/", w);
441 if (!path_is_normalized(s)) {
455 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
464 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
466 if (!unit_instance_is_valid(i))
469 assert_se(p = strchr(f, '@'));
470 assert_se(e = strrchr(f, '.'));
475 s = new(char, a + 1 + b + strlen(e) + 1);
479 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
485 int unit_name_template(const char *f, char **ret) {
493 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
496 assert_se(p = strchr(f, '@'));
497 assert_se(e = strrchr(f, '.'));
501 s = new(char, a + 1 + strlen(e) + 1);
505 strcpy(mempcpy(s, f, a + 1), e);
511 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
512 _cleanup_free_ char *p = NULL;
520 if (!unit_suffix_is_valid(suffix))
523 r = unit_name_path_escape(path, &p);
527 s = strappend(p, suffix);
535 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
536 _cleanup_free_ char *p = NULL;
545 if (!unit_prefix_is_valid(prefix))
548 if (!unit_suffix_is_valid(suffix))
551 r = unit_name_path_escape(path, &p);
555 s = strjoin(prefix, "@", p, suffix);
563 int unit_name_to_path(const char *name, char **ret) {
564 _cleanup_free_ char *prefix = NULL;
569 r = unit_name_to_prefix(name, &prefix);
573 return unit_name_path_unescape(prefix, ret);
576 static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
577 const char *valid_chars;
578 bool mangled = false;
583 /* We'll only escape the obvious characters here, to play safe.
585 * Returns true if any characters were mangled, false otherwise.
588 valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
594 } else if (!strchr(valid_chars, *f)) {
595 t = do_escape_char(*f, t);
605 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
606 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
607 * except that @suffix is appended if a valid unit suffix is not present.
609 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
611 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle flags, const char *suffix, char **ret) {
620 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
623 if (!unit_suffix_is_valid(suffix))
626 /* Already a fully valid unit name? If so, no mangling is necessary... */
627 if (unit_name_is_valid(name, UNIT_NAME_ANY))
630 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
631 if ((flags & UNIT_NAME_MANGLE_GLOB) &&
632 string_is_glob(name) &&
633 in_charset(name, VALID_CHARS_GLOB))
636 if (is_device_path(name)) {
637 r = unit_name_from_path(name, ".device", ret);
644 if (path_is_absolute(name)) {
645 r = unit_name_from_path(name, ".mount", ret);
652 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
656 mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
658 log_full(flags & UNIT_NAME_MANGLE_WARN ? LOG_NOTICE : LOG_DEBUG,
659 "Invalid unit name \"%s\" was escaped as \"%s\" (maybe you should use systemd-escape?)",
662 /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a
664 if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
679 int slice_build_parent_slice(const char *slice, char **ret) {
686 if (!slice_name_is_valid(slice))
689 if (streq(slice, SPECIAL_ROOT_SLICE)) {
698 dash = strrchr(s, '-');
700 strcpy(dash, ".slice");
702 r = free_and_strdup(&s, SPECIAL_ROOT_SLICE);
714 int slice_build_subslice(const char *slice, const char *name, char **ret) {
721 if (!slice_name_is_valid(slice))
724 if (!unit_prefix_is_valid(name))
727 if (streq(slice, SPECIAL_ROOT_SLICE))
728 subslice = strappend(name, ".slice");
732 assert_se(e = endswith(slice, ".slice"));
734 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
738 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
745 bool slice_name_is_valid(const char *name) {
749 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
752 if (streq(name, SPECIAL_ROOT_SLICE))
755 e = endswith(name, ".slice");
759 for (p = name; p < e; p++) {
763 /* Don't allow initial dash */
767 /* Don't allow multiple dashes */
776 /* Don't allow trailing hash */
782 #if 0 /// UNNEEDED by elogind