unit-name: introduce "strict" mode for unit name mangling (#42638)

unit_name_mangle_with_suffix() is quite benevolent by default and allows
the unit to "transition" into a different unit type than what's
requested via its suffix argument. For example, calling
unit_name_mangle_with_suffix() with "/foo/bar" as a unit name and
".service" as a suffix would give you "foo-bar.mount", without any
warning or error.

This could then lead to a quite confusing errors in certain situations:
```
~# systemd-run --remain-after-exit --unit /foo/bar true
Failed to start transient service unit: Cannot set property RemainAfterExit, or unknown property.
```
Given we can't change the default behaviour of
unit_name_mangle_with_suffix() as some parts of systemd already depend
on its "benevolence" (like systemctl), let's introduce a new flag -
UNIT_NAME_MANGLE_STRICT - that checks if the mangled/resolved unit
name's suffix matches the requested one and errors out if not.

With the flag used throughout systemd-run's code, the error in the above
case is now a bit more clear:
```
~# build/systemd-run --remain-after-exit --unit /foo/bar true
Path "/foo/bar" resolves to unit type "mount", but "service" is expected as unit.
Failed to mangle unit name: Invalid argument
```
Resolves: #39996
This commit is contained in:
Lennart Poettering
2026-06-25 17:18:55 +02:00
committed by GitHub
4 changed files with 80 additions and 16 deletions
+39 -10
View File
@@ -716,7 +716,7 @@ int unit_name_mangle_with_suffix(
char **ret) {
_cleanup_free_ char *s = NULL;
bool mangled, suggest_escape = true, warn = flags & UNIT_NAME_MANGLE_WARN;
bool mangled, suggest_escape = true, warn = FLAGS_SET(flags, UNIT_NAME_MANGLE_WARN);
int r;
assert(name);
@@ -730,12 +730,23 @@ int unit_name_mangle_with_suffix(
return -EINVAL;
/* Already a fully valid unit name? If so, no mangling is necessary... */
if (unit_name_is_valid(name, UNIT_NAME_ANY))
if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !endswith(name, suffix)) {
const char *e = ASSERT_PTR(strrchr(name, '.'));
return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
SYNTHETIC_ERRNO(EINVAL),
"Unit name \"%s\" has unit type \"%s\", but \"%s\" is expected%s%s.",
name, e + 1, suffix + 1,
operation ? " " : "", strempty(operation));
}
goto good;
}
/* Already a fully valid globbing expression? If so, no mangling is necessary either... */
if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
if (flags & UNIT_NAME_MANGLE_GLOB)
if (FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB))
goto good;
log_full(warn ? LOG_NOTICE : LOG_DEBUG,
"Glob pattern passed%s%s, but globs are not supported for this.",
@@ -744,23 +755,41 @@ int unit_name_mangle_with_suffix(
}
if (path_is_absolute(name)) {
_cleanup_free_ char *n = NULL;
_cleanup_free_ char *n = NULL, *u = NULL;
r = path_simplify_alloc(name, &n);
if (r < 0)
return r;
if (is_device_path(n)) {
r = unit_name_from_path(n, ".device", ret);
if (r >= 0)
r = unit_name_from_path(n, ".device", &u);
if (r >= 0) {
if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".device"))
return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
SYNTHETIC_ERRNO(EINVAL),
"Path \"%s\" resolves to unit type \"device\", but \"%s\" is expected%s%s.",
name, suffix + 1,
operation ? " " : "", strempty(operation));
*ret = TAKE_PTR(u);
return 1;
}
if (r != -EINVAL)
return r;
}
r = unit_name_from_path(n, ".mount", ret);
if (r >= 0)
r = unit_name_from_path(n, ".mount", &u);
if (r >= 0) {
if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".mount"))
return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
SYNTHETIC_ERRNO(EINVAL),
"Path \"%s\" resolves to unit type \"mount\", but \"%s\" is expected%s%s.",
name, suffix + 1,
operation ? " " : "", strempty(operation));
*ret = TAKE_PTR(u);
return 1;
}
if (r != -EINVAL)
return r;
}
@@ -769,7 +798,7 @@ int unit_name_mangle_with_suffix(
if (!s)
return -ENOMEM;
mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
mangled = do_escape_mangle(name, FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB), s);
if (mangled)
log_full(warn ? LOG_NOTICE : LOG_DEBUG,
"Invalid unit name \"%s\" escaped as \"%s\"%s.",
@@ -778,7 +807,7 @@ int unit_name_mangle_with_suffix(
/* 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 valid glob. */
if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
if ((!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
strcat(s, suffix);
/* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed,
+3 -2
View File
@@ -56,8 +56,9 @@ int unit_name_from_path_instance(const char *prefix, const char *path, const cha
int unit_name_to_path(const char *name, char **ret);
typedef enum UnitNameMangle {
UNIT_NAME_MANGLE_GLOB = 1 << 0,
UNIT_NAME_MANGLE_WARN = 1 << 1,
UNIT_NAME_MANGLE_GLOB = 1 << 0,
UNIT_NAME_MANGLE_WARN = 1 << 1,
UNIT_NAME_MANGLE_STRICT = 1 << 2, /* Refuse if the resolved unit type doesn't match the requested suffix */
} UnitNameMangle;
int unit_name_mangle_with_suffix(const char *name, const char *operation, UnitNameMangle flags, const char *suffix, char **ret);