1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "alloc-util.h"
28 #include "glob-util.h"
29 #include "hexdecoct.h"
30 #include "path-util.h"
32 #include "string-util.h"
34 #include "unit-name.h"
36 /* Characters valid in a unit name. */
42 /* The same, but also permits the single @ character that may appear */
43 #define VALID_CHARS_WITH_AT \
47 /* All chars valid in a unit name glob */
48 #define VALID_CHARS_GLOB \
52 bool unit_name_is_valid(const char *n
, UnitNameFlags flags
) {
53 const char *e
, *i
, *at
;
55 assert((flags
& ~(UNIT_NAME_PLAIN
|UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
)) == 0);
57 if (_unlikely_(flags
== 0))
63 if (strlen(n
) >= UNIT_NAME_MAX
)
70 if (unit_type_from_string(e
+ 1) < 0)
73 for (i
= n
, at
= NULL
; i
< e
; i
++) {
78 if (!strchr("@" VALID_CHARS
, *i
))
85 if (flags
& UNIT_NAME_PLAIN
)
89 if (flags
& UNIT_NAME_INSTANCE
)
93 if (flags
& UNIT_NAME_TEMPLATE
)
94 if (at
&& e
== at
+ 1)
100 bool unit_prefix_is_valid(const char *p
) {
102 /* We don't allow additional @ in the prefix string */
107 return in_charset(p
, VALID_CHARS
);
110 bool unit_instance_is_valid(const char *i
) {
112 /* The max length depends on the length of the string, so we
113 * don't really check this here. */
118 /* We allow additional @ in the instance string, we do not
119 * allow them in the prefix! */
121 return in_charset(i
, "@" VALID_CHARS
);
124 bool unit_suffix_is_valid(const char *s
) {
131 if (unit_type_from_string(s
+ 1) < 0)
137 int unit_name_to_prefix(const char *n
, char **ret
) {
144 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
153 s
= strndup(n
, p
- n
);
161 int unit_name_to_instance(const char *n
, char **instance
) {
168 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
171 /* Everything past the first @ and before the last . is the instance */
192 int unit_name_to_prefix_and_instance(const char *n
, char **ret
) {
199 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
206 s
= strndup(n
, d
- n
);
214 UnitType
unit_name_to_type(const char *n
) {
219 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
220 return _UNIT_TYPE_INVALID
;
222 assert_se(e
= strrchr(n
, '.'));
224 return unit_type_from_string(e
+ 1);
227 int unit_name_change_suffix(const char *n
, const char *suffix
, char **ret
) {
235 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
238 if (!unit_suffix_is_valid(suffix
))
241 assert_se(e
= strrchr(n
, '.'));
246 s
= new(char, a
+ b
+ 1);
250 strcpy(mempcpy(s
, n
, a
), suffix
);
256 int unit_name_build(const char *prefix
, const char *instance
, const char *suffix
, char **ret
) {
263 if (!unit_prefix_is_valid(prefix
))
266 if (instance
&& !unit_instance_is_valid(instance
))
269 if (!unit_suffix_is_valid(suffix
))
273 s
= strappend(prefix
, suffix
);
275 s
= strjoin(prefix
, "@", instance
, suffix
);
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
);
372 int unit_name_path_escape(const char *f
, char **ret
) {
382 path_kill_slashes(p
);
384 if (STR_IN_SET(p
, "/", ""))
387 if (!path_is_normalized(p
))
390 /* Truncate trailing slashes */
391 delete_trailing_chars(p
, "/");
393 /* Truncate leading slashes */
394 p
= skip_leading_chars(p
, "/");
396 s
= unit_name_escape(p
);
405 int unit_name_path_unescape(const char *f
, char **ret
) {
421 r
= unit_name_unescape(f
, &w
);
425 /* Don't accept trailing or leading slashes */
426 if (startswith(w
, "/") || endswith(w
, "/")) {
431 /* Prefix a slash again */
432 s
= strappend("/", w
);
437 if (!path_is_normalized(s
)) {
451 int unit_name_replace_instance(const char *f
, const char *i
, char **ret
) {
460 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
462 if (!unit_instance_is_valid(i
))
465 assert_se(p
= strchr(f
, '@'));
466 assert_se(e
= strrchr(f
, '.'));
471 s
= new(char, a
+ 1 + b
+ strlen(e
) + 1);
475 strcpy(mempcpy(mempcpy(s
, f
, a
+ 1), i
, b
), e
);
481 int unit_name_template(const char *f
, char **ret
) {
489 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
492 assert_se(p
= strchr(f
, '@'));
493 assert_se(e
= strrchr(f
, '.'));
497 s
= new(char, a
+ 1 + strlen(e
) + 1);
501 strcpy(mempcpy(s
, f
, a
+ 1), e
);
507 int unit_name_from_path(const char *path
, const char *suffix
, char **ret
) {
508 _cleanup_free_
char *p
= NULL
;
516 if (!unit_suffix_is_valid(suffix
))
519 r
= unit_name_path_escape(path
, &p
);
523 s
= strappend(p
, suffix
);
531 int unit_name_from_path_instance(const char *prefix
, const char *path
, const char *suffix
, char **ret
) {
532 _cleanup_free_
char *p
= NULL
;
541 if (!unit_prefix_is_valid(prefix
))
544 if (!unit_suffix_is_valid(suffix
))
547 r
= unit_name_path_escape(path
, &p
);
551 s
= strjoin(prefix
, "@", p
, suffix
);
559 int unit_name_to_path(const char *name
, char **ret
) {
560 _cleanup_free_
char *prefix
= NULL
;
565 r
= unit_name_to_prefix(name
, &prefix
);
569 return unit_name_path_unescape(prefix
, ret
);
572 static char *do_escape_mangle(const char *f
, UnitNameMangle allow_globs
, char *t
) {
573 const char *valid_chars
;
576 assert(IN_SET(allow_globs
, UNIT_NAME_GLOB
, UNIT_NAME_NOGLOB
));
579 /* We'll only escape the obvious characters here, to play
582 valid_chars
= allow_globs
== UNIT_NAME_GLOB
? VALID_CHARS_GLOB
: VALID_CHARS_WITH_AT
;
587 else if (!strchr(valid_chars
, *f
))
588 t
= do_escape_char(*f
, t
);
597 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
598 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
599 * except that @suffix is appended if a valid unit suffix is not present.
601 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
603 int unit_name_mangle_with_suffix(const char *name
, UnitNameMangle allow_globs
, const char *suffix
, char **ret
) {
611 if (isempty(name
)) /* We cannot mangle empty unit names to become valid, sorry. */
614 if (!unit_suffix_is_valid(suffix
))
617 /* Already a fully valid unit name? If so, no mangling is necessary... */
618 if (unit_name_is_valid(name
, UNIT_NAME_ANY
))
621 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
622 if (allow_globs
== UNIT_NAME_GLOB
&&
623 string_is_glob(name
) &&
624 in_charset(name
, VALID_CHARS_GLOB
))
627 if (is_device_path(name
)) {
628 r
= unit_name_from_path(name
, ".device", ret
);
635 if (path_is_absolute(name
)) {
636 r
= unit_name_from_path(name
, ".mount", ret
);
643 s
= new(char, strlen(name
) * 4 + strlen(suffix
) + 1);
647 t
= do_escape_mangle(name
, allow_globs
, s
);
650 /* 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
652 if ((allow_globs
!= UNIT_NAME_GLOB
|| !string_is_glob(s
)) && unit_name_to_type(s
) < 0)
667 int slice_build_parent_slice(const char *slice
, char **ret
) {
674 if (!slice_name_is_valid(slice
))
677 if (streq(slice
, SPECIAL_ROOT_SLICE
)) {
686 dash
= strrchr(s
, '-');
688 strcpy(dash
, ".slice");
690 r
= free_and_strdup(&s
, SPECIAL_ROOT_SLICE
);
701 int slice_build_subslice(const char *slice
, const char*name
, char **ret
) {
708 if (!slice_name_is_valid(slice
))
711 if (!unit_prefix_is_valid(name
))
714 if (streq(slice
, SPECIAL_ROOT_SLICE
))
715 subslice
= strappend(name
, ".slice");
719 assert_se(e
= endswith(slice
, ".slice"));
721 subslice
= new(char, (e
- slice
) + 1 + strlen(name
) + 6 + 1);
725 stpcpy(stpcpy(stpcpy(mempcpy(subslice
, slice
, e
- slice
), "-"), name
), ".slice");
732 bool slice_name_is_valid(const char *name
) {
736 if (!unit_name_is_valid(name
, UNIT_NAME_PLAIN
))
739 if (streq(name
, SPECIAL_ROOT_SLICE
))
742 e
= endswith(name
, ".slice");
746 for (p
= name
; p
< e
; p
++) {
750 /* Don't allow initial dash */
754 /* Don't allow multiple dashes */
763 /* Don't allow trailing hash */