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"
31 #include "string-util.h"
33 #include "unit-name.h"
35 /* Characters valid in a unit name. */
41 /* The same, but also permits the single @ character that may appear */
42 #define VALID_CHARS_WITH_AT \
46 /* All chars valid in a unit name glob */
47 #define VALID_CHARS_GLOB \
51 bool unit_name_is_valid(const char *n
, UnitNameFlags flags
) {
52 const char *e
, *i
, *at
;
54 assert((flags
& ~(UNIT_NAME_PLAIN
|UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
)) == 0);
56 if (_unlikely_(flags
== 0))
62 if (strlen(n
) >= UNIT_NAME_MAX
)
69 if (unit_type_from_string(e
+ 1) < 0)
72 for (i
= n
, at
= NULL
; i
< e
; i
++) {
77 if (!strchr("@" VALID_CHARS
, *i
))
84 if (flags
& UNIT_NAME_PLAIN
)
88 if (flags
& UNIT_NAME_INSTANCE
)
92 if (flags
& UNIT_NAME_TEMPLATE
)
93 if (at
&& e
== at
+ 1)
99 bool unit_prefix_is_valid(const char *p
) {
101 /* We don't allow additional @ in the prefix string */
106 return in_charset(p
, VALID_CHARS
);
109 bool unit_instance_is_valid(const char *i
) {
111 /* The max length depends on the length of the string, so we
112 * don't really check this here. */
117 /* We allow additional @ in the instance string, we do not
118 * allow them in the prefix! */
120 return in_charset(i
, "@" VALID_CHARS
);
123 bool unit_suffix_is_valid(const char *s
) {
130 if (unit_type_from_string(s
+ 1) < 0)
136 int unit_name_to_prefix(const char *n
, char **ret
) {
143 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
152 s
= strndup(n
, p
- n
);
160 int unit_name_to_instance(const char *n
, char **instance
) {
167 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
170 /* Everything past the first @ and before the last . is the instance */
191 int unit_name_to_prefix_and_instance(const char *n
, char **ret
) {
198 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
205 s
= strndup(n
, d
- n
);
213 UnitType
unit_name_to_type(const char *n
) {
218 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
219 return _UNIT_TYPE_INVALID
;
221 assert_se(e
= strrchr(n
, '.'));
223 return unit_type_from_string(e
+ 1);
226 int unit_name_change_suffix(const char *n
, const char *suffix
, char **ret
) {
234 if (!unit_name_is_valid(n
, UNIT_NAME_ANY
))
237 if (!unit_suffix_is_valid(suffix
))
240 assert_se(e
= strrchr(n
, '.'));
245 s
= new(char, a
+ b
+ 1);
249 strcpy(mempcpy(s
, n
, a
), suffix
);
255 int unit_name_build(const char *prefix
, const char *instance
, const char *suffix
, char **ret
) {
262 if (!unit_prefix_is_valid(prefix
))
265 if (instance
&& !unit_instance_is_valid(instance
))
268 if (!unit_suffix_is_valid(suffix
))
272 s
= strappend(prefix
, suffix
);
274 s
= strjoin(prefix
, "@", instance
, suffix
);
282 static char *do_escape_char(char c
, char *t
) {
287 *(t
++) = hexchar(c
>> 4);
293 static char *do_escape(const char *f
, char *t
) {
297 /* do not create units with a leading '.', like for "/.dotdir" mount points */
299 t
= do_escape_char(*f
, t
);
306 else if (IN_SET(*f
, '-', '\\') || !strchr(VALID_CHARS
, *f
))
307 t
= do_escape_char(*f
, t
);
315 char *unit_name_escape(const char *f
) {
320 r
= new(char, strlen(f
)*4+1);
330 int unit_name_unescape(const char *f
, char **ret
) {
331 _cleanup_free_
char *r
= NULL
;
340 for (t
= r
; *f
; f
++) {
343 else if (*f
== '\\') {
357 *(t
++) = (char) (((uint8_t) a
<< 4U) | (uint8_t) b
);
371 int unit_name_path_escape(const char *f
, char **ret
) {
381 path_kill_slashes(p
);
383 if (STR_IN_SET(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
) {
420 r
= unit_name_unescape(f
, &w
);
424 /* Don't accept trailing or leading slashes */
425 if (startswith(w
, "/") || endswith(w
, "/")) {
430 /* Prefix a slash again */
431 s
= strappend("/", w
);
436 if (!path_is_normalized(s
)) {
450 int unit_name_replace_instance(const char *f
, const char *i
, char **ret
) {
459 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
461 if (!unit_instance_is_valid(i
))
464 assert_se(p
= strchr(f
, '@'));
465 assert_se(e
= strrchr(f
, '.'));
470 s
= new(char, a
+ 1 + b
+ strlen(e
) + 1);
474 strcpy(mempcpy(mempcpy(s
, f
, a
+ 1), i
, b
), e
);
480 int unit_name_template(const char *f
, char **ret
) {
488 if (!unit_name_is_valid(f
, UNIT_NAME_INSTANCE
|UNIT_NAME_TEMPLATE
))
491 assert_se(p
= strchr(f
, '@'));
492 assert_se(e
= strrchr(f
, '.'));
496 s
= new(char, a
+ 1 + strlen(e
) + 1);
500 strcpy(mempcpy(s
, f
, a
+ 1), e
);
506 int unit_name_from_path(const char *path
, const char *suffix
, char **ret
) {
507 _cleanup_free_
char *p
= NULL
;
515 if (!unit_suffix_is_valid(suffix
))
518 r
= unit_name_path_escape(path
, &p
);
522 s
= strappend(p
, suffix
);
530 int unit_name_from_path_instance(const char *prefix
, const char *path
, const char *suffix
, char **ret
) {
531 _cleanup_free_
char *p
= NULL
;
540 if (!unit_prefix_is_valid(prefix
))
543 if (!unit_suffix_is_valid(suffix
))
546 r
= unit_name_path_escape(path
, &p
);
550 s
= strjoin(prefix
, "@", p
, suffix
);
558 int unit_name_to_path(const char *name
, char **ret
) {
559 _cleanup_free_
char *prefix
= NULL
;
564 r
= unit_name_to_prefix(name
, &prefix
);
568 return unit_name_path_unescape(prefix
, ret
);
571 static char *do_escape_mangle(const char *f
, UnitNameMangle allow_globs
, char *t
) {
572 const char *valid_chars
;
575 assert(IN_SET(allow_globs
, UNIT_NAME_GLOB
, UNIT_NAME_NOGLOB
));
578 /* We'll only escape the obvious characters here, to play
581 valid_chars
= allow_globs
== UNIT_NAME_GLOB
? VALID_CHARS_GLOB
: VALID_CHARS_WITH_AT
;
586 else if (!strchr(valid_chars
, *f
))
587 t
= do_escape_char(*f
, t
);
596 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
597 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
598 * except that @suffix is appended if a valid unit suffix is not present.
600 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
602 int unit_name_mangle_with_suffix(const char *name
, UnitNameMangle allow_globs
, const char *suffix
, char **ret
) {
610 if (isempty(name
)) /* We cannot mangle empty unit names to become valid, sorry. */
613 if (!unit_suffix_is_valid(suffix
))
616 /* Already a fully valid unit name? If so, no mangling is necessary... */
617 if (unit_name_is_valid(name
, UNIT_NAME_ANY
))
620 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
621 if (allow_globs
== UNIT_NAME_GLOB
&&
622 string_is_glob(name
) &&
623 in_charset(name
, VALID_CHARS_GLOB
))
626 if (is_device_path(name
)) {
627 r
= unit_name_from_path(name
, ".device", ret
);
634 if (path_is_absolute(name
)) {
635 r
= unit_name_from_path(name
, ".mount", ret
);
642 s
= new(char, strlen(name
) * 4 + strlen(suffix
) + 1);
646 t
= do_escape_mangle(name
, allow_globs
, s
);
649 /* 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
651 if ((allow_globs
!= UNIT_NAME_GLOB
|| !string_is_glob(s
)) && unit_name_to_type(s
) < 0)
666 int slice_build_parent_slice(const char *slice
, char **ret
) {
673 if (!slice_name_is_valid(slice
))
676 if (streq(slice
, "-.slice")) {
685 dash
= strrchr(s
, '-');
687 strcpy(dash
, ".slice");
689 r
= free_and_strdup(&s
, "-.slice");
700 int slice_build_subslice(const char *slice
, const char*name
, char **ret
) {
707 if (!slice_name_is_valid(slice
))
710 if (!unit_prefix_is_valid(name
))
713 if (streq(slice
, "-.slice"))
714 subslice
= strappend(name
, ".slice");
718 assert_se(e
= endswith(slice
, ".slice"));
720 subslice
= new(char, (e
- slice
) + 1 + strlen(name
) + 6 + 1);
724 stpcpy(stpcpy(stpcpy(mempcpy(subslice
, slice
, e
- slice
), "-"), name
), ".slice");
731 bool slice_name_is_valid(const char *name
) {
735 if (!unit_name_is_valid(name
, UNIT_NAME_PLAIN
))
738 if (streq(name
, "-.slice"))
741 e
= endswith(name
, ".slice");
745 for (p
= name
; p
< e
; p
++) {
749 /* Don't allow initial dash */
753 /* Don't allow multiple dashes */
762 /* Don't allow trailing hash */