install: preset in the scope the caller asked for

unit_file_preset() and unit_file_preset_all() zero-initialized their
install contexts, so their scope was always RUNTIME_SCOPE_SYSTEM. In
the global scope, specifiers in [Install] were then resolved as if for
the system: %U became 0 and a unit got enabled into
target-0.target.wants/. Set the scope the caller asked for, so that
this fails with -EUNATCH instead.

Covered by a new test that presets a unit using %U in the global
scope, with both functions.

(cherry picked from commit 0a4491ef3fce30d31a35c95031c8026a0235e0a2)
This commit is contained in:
Daan De Meyer
2026-10-16 11:36:32 +01:00
committed by Luca Boccassi
parent d14b9ace0c
commit c74e3aa5b7
2 changed files with 45 additions and 2 deletions
+2 -2
View File
@@ -3688,7 +3688,7 @@ int unit_file_preset(
InstallChange **changes,
size_t *n_changes) {
_cleanup_(install_context_done) InstallContext plus = {}, minus = {};
_cleanup_(install_context_done) InstallContext plus = { .scope = scope }, minus = { .scope = scope };
_cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
const char *config_path;
@@ -3727,7 +3727,7 @@ int unit_file_preset_all(
InstallChange **changes,
size_t *n_changes) {
_cleanup_(install_context_done) InstallContext plus = {}, minus = {};
_cleanup_(install_context_done) InstallContext plus = { .scope = scope }, minus = { .scope = scope };
_cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
const char *config_path = NULL;
+43
View File
@@ -10,6 +10,7 @@
#include "path-util.h"
#include "rm-rf.h"
#include "special.h"
#include "stat-util.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
@@ -1131,6 +1132,48 @@ TEST(preset_multiple_instances) {
install_changes_free(changes, n_changes);
}
TEST(preset_scope) {
const char *unit, *preset, *link;
InstallChange *changes = NULL;
size_t n_changes = 0;
/* %U can't be resolved in the global scope, since there's no user to resolve it to. Make sure
* presetting in the global scope fails because of that, instead of resolving specifiers in the
* system scope (where %U is 0) and creating a target-0.target.wants/ symlink. Check both
* unit_file_preset() and unit_file_preset_all(), they set up their install contexts separately. */
unit = strjoina(root, "/usr/lib/systemd/user/scoped.service");
ASSERT_OK(mkdir_parents(unit, 0755));
ASSERT_OK(write_string_file(unit,
"[Install]\n"
"WantedBy=target-%U.target\n", WRITE_STRING_FILE_CREATE));
preset = strjoina(root, "/usr/lib/systemd/user-preset/50-scoped.preset");
ASSERT_OK(mkdir_parents(preset, 0755));
ASSERT_OK(write_string_file(preset, "enable scoped.service\n", WRITE_STRING_FILE_CREATE));
ASSERT_ERROR(unit_file_preset(RUNTIME_SCOPE_GLOBAL, 0, root, STRV_MAKE("scoped.service"),
UNIT_FILE_PRESET_FULL, &changes, &n_changes), EUNATCH);
ASSERT_EQ(n_changes, 1u);
ASSERT_EQ(changes[0].type, -EUNATCH);
ASSERT_STREQ(changes[0].path, "target-%U.target");
install_changes_free(changes, n_changes);
changes = NULL; n_changes = 0;
link = strjoina(root, USER_CONFIG_UNIT_DIR"/target-0.target.wants/scoped.service");
ASSERT_ERROR(is_symlink(link), ENOENT);
ASSERT_ERROR(unit_file_preset_all(RUNTIME_SCOPE_GLOBAL, 0, root, UNIT_FILE_PRESET_FULL,
&changes, &n_changes), EUNATCH);
ASSERT_EQ(n_changes, 1u);
ASSERT_EQ(changes[0].type, -EUNATCH);
ASSERT_STREQ(changes[0].path, "target-%U.target");
install_changes_free(changes, n_changes);
changes = NULL; n_changes = 0;
ASSERT_ERROR(is_symlink(link), ENOENT);
}
static void verify_one(
const InstallInfo *i,
const char *alias,