diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 9cabc3c4f8..2baf9d17e8 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -189,15 +189,24 @@ suffix is .requires/ in this case. Along with a unit file foo.service, a "drop-in" directory - foo.service.d/ may exist. All files with the suffix - .conf from this directory will be parsed after the file itself is - parsed. This is useful to alter or add configuration settings for a unit, without having to - modify unit files. Each drop-in file must have appropriate section headers. Note that for - instantiated units, this logic will first look for the instance .d/ - subdirectory and read its .conf files, followed by the template - .d/ subdirectory and the .conf files there. + foo.service.d/ may exist. All files with the suffix .conf from this + directory will be parsed after the unit file itself is parsed. This is useful to alter or add configuration + settings for a unit, without having to modify unit files. Drop-in files must contain appropriate section + headers. For instantiated units, this logic will first look for the instance .d/ subdirectory + (e.g. foo@bar.service.d/) and read its .conf files, followed by the template + .d/ subdirectory (e.g. foo@.service.d/) and the .conf + files there. Moreover for units names containing dashes (-), the set of directories generated by + truncating the unit name after all dashes is searched too. Specifically, for a unit name + foo-bar-baz.service not only the the regular drop-in directory + foo-bar-baz.service.d/ is searched but also both foo-bar-.service.d/ and + foo-.service.d/. This is useful for defining common drop-ins for a set of related units, whose + names begin with a common prefix. This scheme is particularly useful for mount, automount and slice units, whose + systematic naming structure is built around dashes as component separators. Note that equally named drop-in files + further down the prefix hierarchy override those further up, + i.e. foo-bar-.service.d/10-override.conf overrides + foo-.service.d/10-override.conf. - In addition to /etc/systemd/system, the drop-in .d + In addition to /etc/systemd/system, the drop-in .d/ directories for system services can be placed in /usr/lib/systemd/system or /run/systemd/system directories. Drop-in files in /etc take precedence over those in /run which in turn take precedence over those diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 07a80829a5..6fb9cbc361 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -241,25 +241,45 @@ int unit_name_change_suffix(const char *n, const char *suffix, char **ret) { } int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) { - char *s; + UnitType type; assert(prefix); assert(suffix); assert(ret); + if (isempty(suffix)) + return -EINVAL; + if (suffix[0] != '.') + return -EINVAL; + + type = unit_type_from_string(suffix + 1); + if (type < 0) + return -EINVAL; + + return unit_name_build_from_type(prefix, instance, type, ret); +} + +int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) { + const char *ut; + char *s; + + assert(prefix); + assert(type >= 0); + assert(type < _UNIT_TYPE_MAX); + assert(ret); + if (!unit_prefix_is_valid(prefix)) return -EINVAL; if (instance && !unit_instance_is_valid(instance)) return -EINVAL; - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; + ut = unit_type_to_string(type); if (!instance) - s = strappend(prefix, suffix); + s = strjoin(prefix, ".", ut); else - s = strjoin(prefix, "@", instance, suffix); + s = strjoin(prefix, "@", instance, ".", ut); if (!s) return -ENOMEM; diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h index aadaa85d73..d3e9d3e018 100644 --- a/src/basic/unit-name.h +++ b/src/basic/unit-name.h @@ -40,6 +40,7 @@ UnitType unit_name_to_type(const char *n) _pure_; int unit_name_change_suffix(const char *n, const char *suffix, char **ret); int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); +int unit_name_build_from_type(const char *prefix, const char *instance, UnitType, char **ret); char *unit_name_escape(const char *f); int unit_name_unescape(const char *f, char **ret); diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c index 9fde34893d..0a56f0df86 100644 --- a/src/core/load-dropin.c +++ b/src/core/load-dropin.c @@ -18,26 +18,21 @@ #include "unit.h" static int unit_name_compatible(const char *a, const char *b) { - _cleanup_free_ char *prefix = NULL; + _cleanup_free_ char *template = NULL; int r; - /* the straightforward case: the symlink name matches the target */ + /* The straightforward case: the symlink name matches the target */ if (streq(a, b)) return 1; - r = unit_name_template(a, &prefix); + r = unit_name_template(a, &template); if (r == -EINVAL) - /* not a template */ - return 0; + return 0; /* Not a template */ if (r < 0) - /* oom, or some other failure. Just skip the warning. */ - return r; + return r; /* OOM, or some other failure. Just skip the warning. */ - /* an instance name points to a target that is just the template name */ - if (streq(prefix, b)) - return 1; - - return 0; + /* An instance name points to a target that is just the template name */ + return streq(template, b); } static int process_deps(Unit *u, UnitDependency dependency, const char *dir_suffix) { @@ -56,8 +51,8 @@ static int process_deps(Unit *u, UnitDependency dependency, const char *dir_suff return r; STRV_FOREACH(p, paths) { - const char *entry; _cleanup_free_ char *target = NULL; + const char *entry; entry = basename(*p); diff --git a/src/core/load-dropin.h b/src/core/load-dropin.h index c97de616f1..583460e3dd 100644 --- a/src/core/load-dropin.h +++ b/src/core/load-dropin.h @@ -13,6 +13,8 @@ /* Read service data supplementary drop-in directories */ static inline int unit_find_dropin_paths(Unit *u, char ***paths) { + assert(u); + return unit_file_find_dropin_conf_paths(NULL, u->manager->lookup_paths.search_path, u->manager->unit_path_cache, diff --git a/src/shared/dropin.c b/src/shared/dropin.c index 9544cab0b0..d9362f6919 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -115,14 +115,16 @@ static int unit_file_find_dir( assert(path); r = chase_symlinks(path, original_root, 0, &chased); - /* Ignore -ENOENT, after all most units won't have a drop-in dir. - * Also ignore -ENAMETOOLONG, users are not even able to create - * the drop-in dir in such case. This mostly happens for device units with long /sys path. - * */ - if (IN_SET(r, -ENOENT, -ENAMETOOLONG)) + if (r == -ENOENT) /* Ignore -ENOENT, after all most units won't have a drop-in dir. */ return 0; + if (r == -ENAMETOOLONG) { + /* Also, ignore -ENAMETOOLONG but log about it. After all, users are not even able to create the + * drop-in dir in such case. This mostly happens for device units with an overly long /sys path. */ + log_debug_errno(r, "Path '%s' too long, couldn't canonicalize, ignoring.", path); + return 0; + } if (r < 0) - return log_full_errno(LOG_WARNING, r, "Failed to canonicalize path %s: %m", path); + return log_warning_errno(r, "Failed to canonicalize path '%s': %m", path); r = strv_push(dirs, chased); if (r < 0) @@ -140,7 +142,12 @@ static int unit_file_find_dirs( const char *suffix, char ***dirs) { + _cleanup_free_ char *prefix = NULL, *instance = NULL, *built = NULL; + bool is_instance, chopped; + const char *dash; + UnitType type; char *path; + size_t n; int r; assert(unit_path); @@ -148,26 +155,76 @@ static int unit_file_find_dirs( assert(suffix); path = strjoina(unit_path, "/", name, suffix); - if (!unit_path_cache || set_get(unit_path_cache, path)) { r = unit_file_find_dir(original_root, path, dirs); if (r < 0) return r; } - if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) { - /* Also try the template dir */ - + is_instance = unit_name_is_valid(name, UNIT_NAME_INSTANCE); + if (is_instance) { /* Also try the template dir */ _cleanup_free_ char *template = NULL; r = unit_name_template(name, &template); if (r < 0) return log_error_errno(r, "Failed to generate template from unit name: %m"); - return unit_file_find_dirs(original_root, unit_path_cache, unit_path, template, suffix, dirs); + r = unit_file_find_dirs(original_root, unit_path_cache, unit_path, template, suffix, dirs); + if (r < 0) + return r; } - return 0; + /* Let's see if there's a "-" prefix for this unit name. If so, let's invoke ourselves for it. This will then + * recursively do the same for all our prefixes. i.e. this means given "foo-bar-waldo.service" we'll also + * search "foo-bar-.service" and "foo-.service". + * + * Note the order in which we do it: we traverse up adding drop-ins on each step. This means the more specific + * drop-ins may override the more generic drop-ins, which is the intended behaviour. */ + + r = unit_name_to_prefix(name, &prefix); + if (r < 0) + return log_error_errno(r, "Failed to derive unit name prefix from unit name: %m"); + + chopped = false; + for (;;) { + dash = strrchr(prefix, '-'); + if (!dash) /* No dash? if so we are done */ + return 0; + + n = (size_t) (dash - prefix); + if (n == 0) /* Leading dash? If so, we are done */ + return 0; + + if (prefix[n+1] != 0 || chopped) { + prefix[n+1] = 0; + break; + } + + /* Trailing dash? If so, chop it off and try again, but not more than once. */ + prefix[n] = 0; + chopped = true; + } + + if (!unit_prefix_is_valid(prefix)) + return 0; + + type = unit_name_to_type(name); + if (type < 0) { + log_error("Failed to to derive unit type from unit name: %m"); + return -EINVAL; + } + + if (is_instance) { + r = unit_name_to_instance(name, &instance); + if (r < 0) + return log_error_errno(r, "Failed to derive unit name instance from unit name: %m"); + } + + r = unit_name_build_from_type(prefix, instance, type, &built); + if (r < 0) + return log_error_errno(r, "Failed to build prefix unit name: %m"); + + return unit_file_find_dirs(original_root, unit_path_cache, unit_path, built, suffix, dirs); } int unit_file_find_dropin_paths( @@ -180,15 +237,15 @@ int unit_file_find_dropin_paths( char ***ret) { _cleanup_strv_free_ char **dirs = NULL; - Iterator i; char *t, **p; + Iterator i; int r; assert(ret); SET_FOREACH(t, names, i) STRV_FOREACH(p, lookup_path) - unit_file_find_dirs(original_root, unit_path_cache, *p, t, dir_suffix, &dirs); + (void) unit_file_find_dirs(original_root, unit_path_cache, *p, t, dir_suffix, &dirs); if (strv_isempty(dirs)) { *ret = NULL; diff --git a/src/shared/dropin.h b/src/shared/dropin.h index a14fcc023f..fa6f5402f9 100644 --- a/src/shared/dropin.h +++ b/src/shared/dropin.h @@ -36,6 +36,7 @@ static inline int unit_file_find_dropin_conf_paths( Set *unit_path_cache, Set *names, char ***paths) { + return unit_file_find_dropin_paths(original_root, lookup_path, unit_path_cache, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 079d571857..388eee47a1 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4021,8 +4021,9 @@ static void print_status_info( char ** dropin; STRV_FOREACH(dropin, i->dropin_paths) { - if (! dir || last) { - printf(dir ? " " : " Drop-In: "); + if (!dir || last) { + printf(dir ? " " : + " Drop-In: "); dir = mfree(dir); @@ -4032,7 +4033,8 @@ static void print_status_info( return; } - printf("%s\n %s", dir, + printf("%s\n" + " %s", dir, special_glyph(TREE_RIGHT)); } diff --git a/src/test/test-engine.c b/src/test/test-engine.c index 566874a299..1c12a8644d 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -19,12 +19,16 @@ int main(int argc, char *argv[]) { _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; _cleanup_(manager_freep) Manager *m = NULL; - Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, *h = NULL; + Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, *h = NULL, *unit_with_multiple_dashes = NULL; FILE *serial = NULL; FDSet *fdset = NULL; Job *j; int r; + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + r = enter_cgroup_subroot(); if (r == -ENOMEDIUM) { log_notice_errno(r, "Skipping test: cgroupfs not available"); @@ -130,5 +134,10 @@ int main(int argc, char *argv[]) { assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], c)); assert_se(!hashmap_get(c->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); + assert_se(manager_load_unit(m, "unit-with-multiple-dashes.service", NULL, NULL, &unit_with_multiple_dashes) >= 0); + + assert_se(strv_equal(unit_with_multiple_dashes->documentation, STRV_MAKE("man:test", "man:override2", "man:override3"))); + assert_se(streq_ptr(unit_with_multiple_dashes->description, "override4")); + return 0; } diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index d71221228e..061adc70d8 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -449,6 +449,26 @@ static void test_unit_name_path_unescape(void) { test_unit_name_path_unescape_one("", NULL, -EINVAL); } +static void test_unit_name_to_prefix_one(const char *input, int ret, const char *output) { + _cleanup_free_ char *k = NULL; + + assert_se(unit_name_to_prefix(input, &k) == ret); + assert_se(streq_ptr(k, output)); +} + +static void test_unit_name_to_prefix(void) { + test_unit_name_to_prefix_one("foobar.service", 0, "foobar"); + test_unit_name_to_prefix_one("", -EINVAL, NULL); + test_unit_name_to_prefix_one("foobar", -EINVAL, NULL); + test_unit_name_to_prefix_one(".service", -EINVAL, NULL); + test_unit_name_to_prefix_one("quux.quux", -EINVAL, NULL); + test_unit_name_to_prefix_one("quux.mount", 0, "quux"); + test_unit_name_to_prefix_one("quux-quux.mount", 0, "quux-quux"); + test_unit_name_to_prefix_one("quux@bar.mount", 0, "quux"); + test_unit_name_to_prefix_one("quux-@.mount", 0, "quux-"); + test_unit_name_to_prefix_one("@.mount", -EINVAL, NULL); +} + int main(int argc, char* argv[]) { _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; int r, rc = 0; @@ -482,6 +502,7 @@ int main(int argc, char* argv[]) { test_unit_name_escape(); test_unit_name_template(); test_unit_name_path_unescape(); + test_unit_name_to_prefix(); return rc; } diff --git a/test/meson.build b/test/meson.build index 0bd45cd891..c112e09577 100644 --- a/test/meson.build +++ b/test/meson.build @@ -158,13 +158,18 @@ test_data_files = ''' test-path/path-unit.path test-path/paths.target test-path/sysinit.target - testsuite.target - timers.target - unstoppable.service test-umount/empty.mountinfo + test-umount/example.swaps test-umount/garbled.mountinfo test-umount/rhbug-1554943.mountinfo - test-umount/example.swaps + testsuite.target + timers.target + unit-with-.service.d/20-override.conf + unit-with-multiple-.service.d/20-override.conf + unit-with-multiple-.service.d/30-override.conf + unit-with-multiple-dashes.service + unit-with-multiple-dashes.service.d/10-override.conf + unstoppable.service '''.split() if conf.get('ENABLE_RESOLVE') == 1 diff --git a/test/unit-.service.d/10-override.conf b/test/unit-.service.d/10-override.conf new file mode 100644 index 0000000000..916737d415 --- /dev/null +++ b/test/unit-.service.d/10-override.conf @@ -0,0 +1,2 @@ +[Unit] +Description=override0 diff --git a/test/unit-with-.service.d/20-override.conf b/test/unit-with-.service.d/20-override.conf new file mode 100644 index 0000000000..c6c2438f73 --- /dev/null +++ b/test/unit-with-.service.d/20-override.conf @@ -0,0 +1,2 @@ +[Unit] +Documentation=man:override1 diff --git a/test/unit-with-multiple-.service.d/20-override.conf b/test/unit-with-multiple-.service.d/20-override.conf new file mode 100644 index 0000000000..62fafd2e3b --- /dev/null +++ b/test/unit-with-multiple-.service.d/20-override.conf @@ -0,0 +1,2 @@ +[Unit] +Documentation=man:override2 diff --git a/test/unit-with-multiple-.service.d/30-override.conf b/test/unit-with-multiple-.service.d/30-override.conf new file mode 100644 index 0000000000..b9616da8a8 --- /dev/null +++ b/test/unit-with-multiple-.service.d/30-override.conf @@ -0,0 +1,2 @@ +[Unit] +Documentation=man:override3 diff --git a/test/unit-with-multiple-dashes.service b/test/unit-with-multiple-dashes.service new file mode 100644 index 0000000000..b38b3604b8 --- /dev/null +++ b/test/unit-with-multiple-dashes.service @@ -0,0 +1,6 @@ +[Unit] +Description=A unit with multiple dashes +Documentation=man:test + +[Service] +ExecStart=/bin/true diff --git a/test/unit-with-multiple-dashes.service.d/10-override.conf b/test/unit-with-multiple-dashes.service.d/10-override.conf new file mode 100644 index 0000000000..982c3621a6 --- /dev/null +++ b/test/unit-with-multiple-dashes.service.d/10-override.conf @@ -0,0 +1,2 @@ +[Unit] +Description=override4