install: when a template unit is instantiated via a /usr symlink, consider it enabled

If a unit foobar@.service stored below /usr is instantiated via a
symlink foobar@quux.service also below /usr, then we should consider the
instance statically enabled, while the template itself should continue
to be considered enabled/disabled/static depending on its [Install]
section.

In order to implement this we'll now look for enablement symlinks in all
unit search paths, not just in the config and runtime dirs.

Fixes: #5136
This commit is contained in:
Lennart Poettering 2017-02-07 20:16:12 +01:00
parent 9f6cbcf53c
commit dfead90d93
2 changed files with 107 additions and 35 deletions

View file

@ -208,7 +208,7 @@ static int path_is_control(const LookupPaths *p, const char *path) {
path_equal_ptr(parent, p->runtime_control);
}
static int path_is_config(const LookupPaths *p, const char *path) {
static int path_is_config(const LookupPaths *p, const char *path, bool check_parent) {
_cleanup_free_ char *parent = NULL;
assert(p);
@ -217,15 +217,19 @@ static int path_is_config(const LookupPaths *p, const char *path) {
/* Note that we do *not* have generic checks for /etc or /run in place, since with
* them we couldn't discern configuration from transient or generated units */
parent = dirname_malloc(path);
if (!parent)
return -ENOMEM;
if (check_parent) {
parent = dirname_malloc(path);
if (!parent)
return -ENOMEM;
return path_equal_ptr(parent, p->persistent_config) ||
path_equal_ptr(parent, p->runtime_config);
path = parent;
}
return path_equal_ptr(path, p->persistent_config) ||
path_equal_ptr(path, p->runtime_config);
}
static int path_is_runtime(const LookupPaths *p, const char *path) {
static int path_is_runtime(const LookupPaths *p, const char *path, bool check_parent) {
_cleanup_free_ char *parent = NULL;
const char *rpath;
@ -239,16 +243,20 @@ static int path_is_runtime(const LookupPaths *p, const char *path) {
if (rpath && path_startswith(rpath, "/run"))
return true;
parent = dirname_malloc(path);
if (!parent)
return -ENOMEM;
if (check_parent) {
parent = dirname_malloc(path);
if (!parent)
return -ENOMEM;
return path_equal_ptr(parent, p->runtime_config) ||
path_equal_ptr(parent, p->generator) ||
path_equal_ptr(parent, p->generator_early) ||
path_equal_ptr(parent, p->generator_late) ||
path_equal_ptr(parent, p->transient) ||
path_equal_ptr(parent, p->runtime_control);
path = parent;
}
return path_equal_ptr(path, p->runtime_config) ||
path_equal_ptr(path, p->generator) ||
path_equal_ptr(path, p->generator_early) ||
path_equal_ptr(path, p->generator_late) ||
path_equal_ptr(path, p->transient) ||
path_equal_ptr(path, p->runtime_control);
}
static int path_is_vendor(const LookupPaths *p, const char *path) {
@ -826,7 +834,9 @@ static int find_symlinks_in_scope(
const char *name,
UnitFileState *state) {
bool same_name_link_runtime = false, same_name_link = false;
bool same_name_link_runtime = false, same_name_link_config = false;
bool enabled_in_runtime = false, enabled_at_all = false;
char **p;
int r;
assert(scope >= 0);
@ -834,27 +844,66 @@ static int find_symlinks_in_scope(
assert(paths);
assert(name);
/* First look in the persistent config path */
r = find_symlinks(paths->root_dir, name, paths->persistent_config, paths, &same_name_link);
if (r < 0)
return r;
if (r > 0) {
*state = UNIT_FILE_ENABLED;
return r;
STRV_FOREACH(p, paths->search_path) {
bool same_name_link = false;
r = find_symlinks(paths->root_dir, name, *p, paths, &same_name_link);
if (r < 0)
return r;
if (r > 0) {
/* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */
r = path_is_config(paths, *p, false);
if (r < 0)
return r;
if (r > 0) {
/* This is the best outcome, let's return it immediately. */
*state = UNIT_FILE_ENABLED;
return 1;
}
r = path_is_runtime(paths, *p, false);
if (r < 0)
return r;
if (r > 0)
enabled_in_runtime = true;
else
enabled_at_all = true;
} else if (same_name_link) {
r = path_is_config(paths, *p, false);
if (r < 0)
return r;
if (r > 0)
same_name_link_config = true;
else {
r = path_is_runtime(paths, *p, false);
if (r < 0)
return r;
if (r > 0)
same_name_link_runtime = true;
}
}
}
/* Then look in runtime config path */
r = find_symlinks(paths->root_dir, name, paths->runtime_config, paths, &same_name_link_runtime);
if (r < 0)
return r;
if (r > 0) {
if (enabled_in_runtime) {
*state = UNIT_FILE_ENABLED_RUNTIME;
return r;
return 1;
}
/* Here's a special rule: if the unit we are looking for is an instance, and it symlinked in the search path
* outside of runtime and configuration directory, then we consider it statically enabled. Note we do that only
* for instance, not for regular names, as those are merely aliases, while instances explicitly instantiate
* something, and hence are a much stronger concept. */
if (enabled_at_all && unit_name_is_valid(name, UNIT_NAME_INSTANCE)) {
*state = UNIT_FILE_STATIC;
return 1;
}
/* Hmm, we didn't find it, but maybe we found the same name
* link? */
if (same_name_link) {
if (same_name_link_config) {
*state = UNIT_FILE_LINKED;
return 1;
}
@ -1409,7 +1458,7 @@ static int install_info_traverse(
return -ELOOP;
if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS)) {
r = path_is_config(paths, i->path);
r = path_is_config(paths, i->path, true);
if (r < 0)
return r;
if (r > 0)
@ -2040,7 +2089,7 @@ static int path_shall_revert(const LookupPaths *paths, const char *path) {
/* Checks whether the path is one where the drop-in directories shall be removed. */
r = path_is_config(paths, path);
r = path_is_config(paths, path, true);
if (r != 0)
return r;
@ -2148,7 +2197,7 @@ int unit_file_revert(
if (errno != ENOENT)
return -errno;
} else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
r = path_is_config(&paths, path);
r = path_is_config(&paths, path, true);
if (r < 0)
return r;
if (r > 0) {
@ -2494,7 +2543,7 @@ static int unit_file_lookup_state(
switch (i->type) {
case UNIT_FILE_TYPE_MASKED:
r = path_is_runtime(paths, i->path);
r = path_is_runtime(paths, i->path, true);
if (r < 0)
return r;

View file

@ -736,6 +736,28 @@ static void test_preset_order(const char *root) {
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "prefix-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
}
static void test_static_instance(const char *root) {
UnitFileState state;
const char *p;
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "static-instance@.service", &state) == -ENOENT);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "static-instance@foo.service", &state) == -ENOENT);
p = strjoina(root, "/usr/lib/systemd/system/static-instance@.service");
assert_se(write_string_file(p,
"[Install]\n"
"WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "static-instance@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "static-instance@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
p = strjoina(root, "/usr/lib/systemd/system/static-instance@foo.service");
assert_se(symlink("static-instance@.service", p) >= 0);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "static-instance@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "static-instance@foo.service", &state) >= 0 && state == UNIT_FILE_STATIC);
}
int main(int argc, char *argv[]) {
char root[] = "/tmp/rootXXXXXX";
const char *p;
@ -766,6 +788,7 @@ int main(int argc, char *argv[]) {
test_preset_and_list(root);
test_preset_order(root);
test_revert(root);
test_static_instance(root);
assert_se(rm_rf(root, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);