diff --git a/man/systemd.service.xml b/man/systemd.service.xml index f57e37ca5b..ce7b847420 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -63,6 +63,19 @@ The systemd-run1 command allows creating .service and .scope units dynamically and transiently from the command line. + + In addition to the various drop-in behaviors described in + systemd.unit5, + services also support a top-level drop-in with -.service.d/ that allows + altering or adding to the settings of all services on the system. + The formatting and precedence of applying drop-in configurations follow what is defined in + systemd.unit5. + However, configurations in -.service.d/ have the lowest precedence compared to settings + in the service specific override directories. For example, for foo-bar-baz.service, + drop-ins in foo-bar-baz.service.d/ override the ones in + foo-bar-.service.d/, which override the ones foo-.service.d/, + which override the ones in -.service.d/. + diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 248fb924db..afd14b977c 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -119,6 +119,15 @@ + + -.service + + This is a reserved unit name used to support top-level drop-ins for services. See + systemd.service5 + for details. + + + basic.target diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index d812108d3c..f6e9d2a17c 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -192,6 +192,10 @@ over unit files wherever located. Multiple drop-in files with different names are applied in lexicographic order, regardless of which of the directories they reside in. + Service units also support a top-level drop-in directory for modifying the settings of all service units. See + systemd.service5 + for details. + diff --git a/src/basic/set.h b/src/basic/set.h index 2bb26c68b8..5f1956177e 100644 --- a/src/basic/set.h +++ b/src/basic/set.h @@ -102,8 +102,8 @@ static inline void *set_steal_first(Set *s) { /* no set_steal_first_key */ /* no set_first_key */ -static inline void *set_first(Set *s) { - return internal_hashmap_first_key_and_value(HASHMAP_BASE(s), false, NULL); +static inline void *set_first(const Set *s) { + return internal_hashmap_first_key_and_value(HASHMAP_BASE((Set *) s), false, NULL); } /* no set_next */ diff --git a/src/basic/special.h b/src/basic/special.h index add1c1d507..6475501078 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -105,3 +105,7 @@ /* The root directory. */ #define SPECIAL_ROOT_MOUNT "-.mount" + +/* Used to apply settings to all services through drop-ins. + * Should not exist as an actual service. */ +#define SPECIAL_ROOT_SERVICE "-.service" diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 4226f3014d..bcd01f8515 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -665,6 +665,36 @@ good: return 0; } +bool service_unit_name_is_valid(const char *name) { + _cleanup_free_ char *prefix = NULL, *s = NULL; + const char *e, *service_name = name; + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return false; + + e = endswith(name, ".service"); + if (!e) + return false; + + /* If it's a template or instance, get the prefix as a service name. */ + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) { + if (unit_name_to_prefix(name, &prefix) < 0) + return false; + + s = strjoin(prefix, ".service"); + if (!s) + return false; + + service_name = s; + } + + /* Reject reserved service name(s). */ + if (streq(service_name, SPECIAL_ROOT_SERVICE)) + return false; + + return true; +} + int slice_build_parent_slice(const char *slice, char **ret) { char *s, *dash; int r; diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h index 2e060ff3e8..ddcfc1b349 100644 --- a/src/basic/unit-name.h +++ b/src/basic/unit-name.h @@ -58,6 +58,8 @@ static inline int unit_name_mangle(const char *name, UnitNameMangle flags, char return unit_name_mangle_with_suffix(name, flags, ".service", ret); } +bool service_unit_name_is_valid(const char *name); + int slice_build_parent_slice(const char *slice, char **ret); int slice_build_subslice(const char *slice, const char *name, char **subslice); bool slice_name_is_valid(const char *name); diff --git a/src/core/service.c b/src/core/service.c index 3713490c93..256ca46fd8 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -550,6 +550,11 @@ static int service_verify(Service *s) { assert(s); assert(UNIT(s)->load_state == UNIT_LOADED); + if (!service_unit_name_is_valid(UNIT(s)->id)) { + log_unit_error(UNIT(s), "Service name is invalid or reserved. Refusing."); + return -EINVAL; + } + if (!s->exec_command[SERVICE_EXEC_START] && !s->exec_command[SERVICE_EXEC_STOP] && UNIT(s)->success_action == EMERGENCY_ACTION_NONE) { /* FailureAction= only makes sense if one of the start or stop commands is specified. diff --git a/src/shared/dropin.c b/src/shared/dropin.c index 4a29bd09c5..2f67a44bf0 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -19,6 +19,7 @@ #include "mkdir.h" #include "path-util.h" #include "set.h" +#include "special.h" #include "string-util.h" #include "strv.h" #include "unit-name.h" @@ -226,12 +227,34 @@ int unit_file_find_dropin_paths( char ***ret) { _cleanup_strv_free_ char **dirs = NULL; + UnitType type = _UNIT_TYPE_INVALID; char *name, **p; Iterator i; int r; assert(ret); + /* All the names in the unit are of the same type so just grab one. */ + name = (char*) set_first(names); + if (name) { + type = unit_name_to_type(name); + if (type < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to to derive unit type from unit name: %s", + name); + } + + /* Special drop in for -.service. Add this first as it's the most generic + * and should be able to be overridden by more specific drop-ins. */ + if (type == UNIT_SERVICE) + STRV_FOREACH(p, lookup_path) + (void) unit_file_find_dirs(original_root, + unit_path_cache, + *p, + SPECIAL_ROOT_SERVICE, + dir_suffix, + &dirs); + SET_FOREACH(name, names, i) STRV_FOREACH(p, lookup_path) (void) unit_file_find_dirs(original_root, unit_path_cache, *p, name, dir_suffix, &dirs); diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index 25c649828e..aa072c4ca8 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -355,6 +355,24 @@ static void test_unit_name_build(void) { free(t); } +static void test_service_unit_name_is_valid(void) { + assert_se(service_unit_name_is_valid("foo.service")); + assert_se(service_unit_name_is_valid("foo@bar.service")); + assert_se(service_unit_name_is_valid("foo@bar@bar.service")); + assert_se(service_unit_name_is_valid("--.service")); + assert_se(service_unit_name_is_valid(".-.service")); + assert_se(service_unit_name_is_valid("-foo-bar.service")); + assert_se(service_unit_name_is_valid("-foo-bar-.service")); + assert_se(service_unit_name_is_valid("foo-bar-.service")); + + assert_se(!service_unit_name_is_valid("-.service")); + assert_se(!service_unit_name_is_valid("")); + assert_se(!service_unit_name_is_valid("foo.slice")); + assert_se(!service_unit_name_is_valid("@.service")); + assert_se(!service_unit_name_is_valid("@bar.service")); + assert_se(!service_unit_name_is_valid("-@.service")); +} + static void test_slice_name_is_valid(void) { assert_se( slice_name_is_valid(SPECIAL_ROOT_SLICE)); assert_se( slice_name_is_valid("foo.slice")); @@ -840,6 +858,7 @@ int main(int argc, char* argv[]) { test_unit_prefix_is_valid(); test_unit_name_change_suffix(); test_unit_name_build(); + test_service_unit_name_is_valid(); test_slice_name_is_valid(); test_build_subslice(); test_build_parent_slice(); diff --git a/test/TEST-15-DROPIN/test-dropin.sh b/test/TEST-15-DROPIN/test-dropin.sh index 2cef5a3c5b..75c36df2b8 100755 --- a/test/TEST-15-DROPIN/test-dropin.sh +++ b/test/TEST-15-DROPIN/test-dropin.sh @@ -101,6 +101,19 @@ test_basic_dropins () { check_ok b Wants c.service systemctl stop a c + echo "*** test -.service.d/ top level drop-in" + create_services a b + check_ko a ExecCondition "/bin/echo a" + check_ko b ExecCondition "/bin/echo b" + mkdir -p /usr/lib/systemd/system/-.service.d + cat >/usr/lib/systemd/system/-.service.d/override.conf <