1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-25 10:04:04 +03:00

shared/install: rework alias check and add test

This mostly reuses existing checkers used by pid1, so handling of aliases
should be consistent. Hopefully, with the test it'll be clearer what it
happening.

Support for .wants/.requires "aliases" is restored. Those are still used in the
wild quite a bit, so we need to support them.

See https://github.com/systemd/systemd/pull/13119 for a discussion of aliases
with an instance that point to a different template: this is allowed.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2019-12-19 16:53:12 +01:00
parent 9a4f9e69e1
commit 3f57bc2267
3 changed files with 203 additions and 38 deletions

View File

@ -1708,36 +1708,67 @@ static int install_info_discover_and_check(
return install_info_may_process(ret ? *ret : NULL, paths, changes, n_changes);
}
int unit_file_verify_alias(const UnitFileInstallInfo *i, const char *dst) {
int unit_file_verify_alias(const UnitFileInstallInfo *i, const char *dst, char **ret_dst) {
_cleanup_free_ char *dst_updated = NULL;
int r;
if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE) &&
!unit_name_is_valid(dst, UNIT_NAME_TEMPLATE) &&
!i->default_instance)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Aliases for templates without DefaultInstance must be templates.");
/* Verify that dst is a valid either a valid alias or a valid .wants/.requires symlink for the target
* unit *i. Return negative on error or if not compatible, zero on success.
*
* ret_dst is set in cases where "instance propagation" happens, i.e. when the instance part is
* inserted into dst. It is not normally set, even on success, so that the caller can easily
* distinguish the case where instance propagation occured.
*/
if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE | UNIT_NAME_TEMPLATE) &&
!unit_name_is_valid(dst, UNIT_NAME_TEMPLATE | UNIT_NAME_INSTANCE))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Aliases for template instances must be a templates or template instances containing the instance name.");
const char *path_alias = strrchr(dst, '/');
if (path_alias) {
/* This branch covers legacy Alias= function of creating .wants and .requires symlinks. */
_cleanup_free_ char *dir = NULL;
if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE | UNIT_NAME_TEMPLATE) &&
unit_name_is_valid(dst, UNIT_NAME_INSTANCE)) {
_cleanup_free_ char *src_inst = NULL, *dst_inst = NULL;
path_alias ++; /* skip over slash */
r = unit_name_to_instance(i->name, &src_inst);
dir = dirname_malloc(dst);
if (!dir)
return log_oom();
if (!endswith(dir, ".wants") && !endswith(dir, ".requires"))
return log_warning_errno(SYNTHETIC_ERRNO(EXDEV),
"Invalid path \"%s\" in alias.", dir);
/* That's the name we want to use for verification. */
r = unit_symlink_name_compatible(path_alias, i->name);
if (r < 0)
return log_error_errno(r, "Failed to verify alias validity: %m");
if (r == 0)
return log_warning_errno(SYNTHETIC_ERRNO(EXDEV),
"Invalid unit %s symlink %s.",
i->name, dst);
} else {
/* If the symlink target has an instance set and the symlink source doesn't, we "propagate
* the instance", i.e. instantiate the symlink source with the target instance. */
if (unit_name_is_valid(dst, UNIT_NAME_TEMPLATE)) {
_cleanup_free_ char *inst = NULL;
r = unit_name_to_instance(i->name, &inst);
if (r < 0)
return log_error_errno(r, "Failed to extract instance name from %s: %m", i->name);
if (r == UNIT_NAME_INSTANCE) {
r = unit_name_replace_instance(dst, inst, &dst_updated);
if (r < 0)
return log_error_errno(r, "Failed to build unit name from %s+%s: %m",
dst, inst);
}
}
r = unit_validate_alias_symlink_and_warn(dst_updated ?: dst, i->name);
if (r < 0)
return r;
r = unit_name_to_instance(dst, &dst_inst);
if (r < 0)
return r;
if (!strstr(dst_inst, src_inst?src_inst:i->default_instance))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Aliases for template instances must be a templates or template instances containing the instance name.");
}
*ret_dst = TAKE_PTR(dst_updated);
return 0;
}
@ -1757,31 +1788,17 @@ static int install_info_symlink_alias(
assert(config_path);
STRV_FOREACH(s, i->aliases) {
_cleanup_free_ char *alias_path = NULL, *dst = NULL;
_cleanup_free_ char *alias_path = NULL, *dst = NULL, *dst_updated = NULL;
q = install_full_printf(i, *s, &dst);
if (q < 0)
return q;
q = unit_file_verify_alias(i, dst);
q = unit_file_verify_alias(i, dst, &dst_updated);
if (q < 0)
continue;
if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) &&
unit_name_is_valid(dst, UNIT_NAME_TEMPLATE)) {
_cleanup_free_ char *s_copy = NULL, *instance = NULL;
q = unit_name_to_instance(i->name, &instance);
if (q < 0)
return q;
q = unit_name_replace_instance(dst, instance, &s_copy);
if (q < 0)
return q;
free_and_replace(dst, s_copy);
}
alias_path = path_make_absolute(dst, config_path);
alias_path = path_make_absolute(dst_updated ?: dst, config_path);
if (!alias_path)
return -ENOMEM;

View File

@ -187,7 +187,7 @@ int unit_file_changes_add(UnitFileChange **changes, size_t *n_changes, UnitFileC
void unit_file_changes_free(UnitFileChange *changes, size_t n_changes);
void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, size_t n_changes, bool quiet);
int unit_file_verify_alias(const UnitFileInstallInfo *i, const char *dst);
int unit_file_verify_alias(const UnitFileInstallInfo *i, const char *dst, char **ret_dst);
int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name);

View File

@ -1043,6 +1043,152 @@ static void test_preset_multiple_instances(const char *root) {
unit_file_changes_free(changes, n_changes);
}
static void verify_one(
const UnitFileInstallInfo *i,
const char *alias,
int expected,
const char *updated_name) {
int r;
static const UnitFileInstallInfo *last_info = NULL;
_cleanup_free_ char *alias2 = NULL;
if (i != last_info)
log_info("-- %s --", (last_info = i)->name);
r = unit_file_verify_alias(i, alias, &alias2);
log_info_errno(r, "alias %s ← %s: %d/%m (expected %d)%s%s%s",
i->name, alias, r, expected,
alias2 ? " [" : "", alias2 ?: "", alias2 ? "]" : "");
assert(r == expected);
/* This is is test for "instance propagation". This propagation matters mostly for WantedBy= and
* RequiredBy= settings, and less so for Alias=. The only case where it should happen is when we have
* an Alias=alias@.service an instantiated template template@instance. In that case the instance name
* should be propagated into the alias as alias@instance. */
assert(streq_ptr(alias2, updated_name));
}
static void test_verify_alias(void) {
const UnitFileInstallInfo
plain_service = { .name = (char*) "plain.service" },
bare_template = { .name = (char*) "template1@.service" },
di_template = { .name = (char*) "template2@.service",
.default_instance = (char*) "di" },
inst_template = { .name = (char*) "template3@inst.service" },
di_inst_template = { .name = (char*) "template4@inst.service",
.default_instance = (char*) "di" };
verify_one(&plain_service, "alias.service", 0, NULL);
verify_one(&plain_service, "alias.socket", -EXDEV, NULL);
verify_one(&plain_service, "alias@.service", -EXDEV, NULL);
verify_one(&plain_service, "alias@inst.service", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.wants/plain.service", 0, NULL);
verify_one(&plain_service, "foo.target.wants/plain.socket", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.wants/plain@.service", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.wants/service", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.requires/plain.service", 0, NULL);
verify_one(&plain_service, "foo.target.requires/plain.socket", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.requires/plain@.service", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.requires/service", -EXDEV, NULL);
verify_one(&plain_service, "foo.target.conf/plain.service", -EXDEV, NULL);
verify_one(&bare_template, "alias.service", -EXDEV, NULL);
verify_one(&bare_template, "alias.socket", -EXDEV, NULL);
verify_one(&bare_template, "alias@.socket", -EXDEV, NULL);
verify_one(&bare_template, "alias@inst.socket", -EXDEV, NULL);
/* A general alias alias@.service → template1@.service. */
verify_one(&bare_template, "alias@.service", 0, NULL);
/* Only a specific instance is aliased, see the discussion in https://github.com/systemd/systemd/pull/13119. */
verify_one(&bare_template, "alias@inst.service", 0, NULL);
verify_one(&bare_template, "foo.target.wants/plain.service", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.wants/plain.socket", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.wants/plain@.service", -EXDEV, NULL);
/* Name mistmatch: we cannot allow this, because plain@foo.service would be pulled in by foo.taget,
* but would not be resolvable on its own, since systemd doesn't know how to load the fragment. */
verify_one(&bare_template, "foo.target.wants/plain@foo.service", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.wants/template1@foo.service", 0, NULL);
verify_one(&bare_template, "foo.target.wants/service", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.requires/plain.service", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.requires/plain.socket", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.requires/plain@.service", -EXDEV, NULL); /* instance missing */
verify_one(&bare_template, "foo.target.requires/template1@inst.service", 0, NULL);
verify_one(&bare_template, "foo.target.requires/service", -EXDEV, NULL);
verify_one(&bare_template, "foo.target.conf/plain.service", -EXDEV, NULL);
verify_one(&di_template, "alias.service", -EXDEV, NULL);
verify_one(&di_template, "alias.socket", -EXDEV, NULL);
verify_one(&di_template, "alias@.socket", -EXDEV, NULL);
verify_one(&di_template, "alias@inst.socket", -EXDEV, NULL);
verify_one(&di_template, "alias@inst.service", 0, NULL);
verify_one(&di_template, "alias@.service", 0, NULL);
verify_one(&di_template, "alias@di.service", 0, NULL);
verify_one(&di_template, "foo.target.wants/plain.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.wants/plain.socket", -EXDEV, NULL);
verify_one(&di_template, "foo.target.wants/plain@.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.wants/plain@di.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.wants/template2@di.service", 0, NULL);
verify_one(&di_template, "foo.target.wants/service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.requires/plain.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.requires/plain.socket", -EXDEV, NULL);
verify_one(&di_template, "foo.target.requires/plain@.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.requires/plain@di.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.requires/plain@foo.service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.requires/template2@di.service", 0, NULL);
verify_one(&di_template, "foo.target.requires/service", -EXDEV, NULL);
verify_one(&di_template, "foo.target.conf/plain.service", -EXDEV, NULL);
verify_one(&inst_template, "alias.service", -EXDEV, NULL);
verify_one(&inst_template, "alias.socket", -EXDEV, NULL);
verify_one(&inst_template, "alias@.socket", -EXDEV, NULL);
verify_one(&inst_template, "alias@inst.socket", -EXDEV, NULL);
verify_one(&inst_template, "alias@inst.service", 0, NULL);
verify_one(&inst_template, "alias@.service", 0, "alias@inst.service");
verify_one(&inst_template, "alias@di.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/plain.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/plain.socket", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/plain@.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/plain@di.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/plain@inst.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/template3@foo.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.wants/template3@inst.service", 0, NULL);
verify_one(&inst_template, "bar.target.wants/service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/plain.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/plain.socket", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/plain@.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/plain@di.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/plain@inst.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/template3@foo.service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.requires/template3@inst.service", 0, NULL);
verify_one(&inst_template, "bar.target.requires/service", -EXDEV, NULL);
verify_one(&inst_template, "bar.target.conf/plain.service", -EXDEV, NULL);
/* explicit alias overrides DefaultInstance */
verify_one(&di_inst_template, "alias.service", -EXDEV, NULL);
verify_one(&di_inst_template, "alias.socket", -EXDEV, NULL);
verify_one(&di_inst_template, "alias@.socket", -EXDEV, NULL);
verify_one(&di_inst_template, "alias@inst.socket", -EXDEV, NULL);
verify_one(&di_inst_template, "alias@inst.service", 0, NULL);
verify_one(&di_inst_template, "alias@.service", 0, "alias@inst.service");
verify_one(&di_inst_template, "alias@di.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/plain.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/plain.socket", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/plain@.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/plain@di.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/template4@foo.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/template4@inst.service", 0, NULL);
verify_one(&di_inst_template, "goo.target.wants/template4@di.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.wants/service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/plain.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/plain.socket", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/plain@.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/plain@di.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/plain@inst.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/template4@foo.service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.requires/template4@inst.service", 0, NULL);
verify_one(&di_inst_template, "goo.target.requires/service", -EXDEV, NULL);
verify_one(&di_inst_template, "goo.target.conf/plain.service", -EXDEV, NULL);
}
int main(int argc, char *argv[]) {
char root[] = "/tmp/rootXXXXXX";
const char *p;
@ -1080,5 +1226,7 @@ int main(int argc, char *argv[]) {
assert_se(rm_rf(root, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
test_verify_alias();
return 0;
}