diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index f95b5259ff..9b1b1f7721 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -30,6 +30,7 @@ #include "signal-util.h" #include "stat-util.h" #include "string-util.h" +#include "strv.h" #include "unit.h" static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); @@ -100,6 +101,29 @@ static int property_get_extra_file_descriptors( return sd_bus_message_close_container(reply); } +static int property_get_refresh_on_reload( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *reterr_error) { + + Service *s = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(bus); + assert(reply); + + r = service_refresh_on_reload_to_strv(s->refresh_on_reload_flags, &l); + if (r < 0) + return r; + + return sd_bus_message_append_strv(reply, l); +} + static int property_get_exit_status_set( sd_bus *bus, const char *path, @@ -373,6 +397,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ExtraFileDescriptorNames", "as", property_get_extra_file_descriptors, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RefreshOnReload", "as", property_get_refresh_on_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), @@ -840,6 +865,47 @@ static int bus_service_set_transient_property( return 1; } + if (streq(name, "RefreshOnReload")) { + const char *t; + int invert; + + r = sd_bus_message_enter_container(message, 'a', "(bs)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(bs)", &invert, &t)) > 0) { + ServiceRefreshOnReload f; + + f = service_refresh_on_reload_flag_from_string(t); + if (f < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RefreshOnReload= value: %s", t); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (!s->refresh_on_reload_set) + s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f; + else + SET_FLAG(s->refresh_on_reload_flags, f, !invert); + + s->refresh_on_reload_set = true; + unit_write_settingf(u, flags, name, "%s=%s%s", name, invert ? "~" : "", t); + } + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags) && !s->refresh_on_reload_set) { /* empty array? */ + s->refresh_on_reload_flags = 0; + s->refresh_on_reload_set = true; + unit_write_settingf(u, flags, name, "%s=no", name); + } + + return 1; + } + return 0; } diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 7f8198ff48..5605445c34 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -479,6 +479,7 @@ Service.USBFunctionStrings, config_parse_unit_path_printf, Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy) Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files) Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal) +Service.RefreshOnReload, config_parse_service_refresh_on_reload, 0, 0 {{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }} {{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }} {{ KILL_CONTEXT_CONFIG_ITEMS('Service') }} diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index ce90b08e56..d2bfd20fd4 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -4816,6 +4816,55 @@ int config_parse_import_credential( return 0; } +int config_parse_service_refresh_on_reload( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Service *s = ASSERT_PTR(userdata); + int r; + + if (isempty(rvalue)) { + s->refresh_on_reload_set = false; + return 0; + } + + r = parse_boolean(rvalue); + if (r >= 0) { + s->refresh_on_reload_flags = r > 0 ? _SERVICE_REFRESH_ON_RELOAD_ALL : 0; + s->refresh_on_reload_set = true; + return 0; + } + + ServiceRefreshOnReload f; + bool invert = false; + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + r = service_refresh_on_reload_from_string_many(rvalue, &f); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + /* If the first entry is negated, mask off from default; otherwise assign "positive" values directly */ + if (!s->refresh_on_reload_set) + s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f; + else + SET_FLAG(s->refresh_on_reload_flags, f, !invert); + + s->refresh_on_reload_set = true; + return 0; +} + int config_parse_set_status( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 3b0ee5fb30..4677564904 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -105,6 +105,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_quota); CONFIG_PARSER_PROTOTYPE(config_parse_set_credential); CONFIG_PARSER_PROTOTYPE(config_parse_load_credential); CONFIG_PARSER_PROTOTYPE(config_parse_import_credential); +CONFIG_PARSER_PROTOTYPE(config_parse_service_refresh_on_reload); CONFIG_PARSER_PROTOTYPE(config_parse_set_status); CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv); CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems); diff --git a/src/core/service.c b/src/core/service.c index ec3fd2ed8c..77cc254f59 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -134,6 +134,8 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f static void service_reload_finish(Service *s, ServiceResult f); static void service_enter_reload_by_notify(Service *s); +static bool service_can_reload_extensions(Service *s, bool warn); + static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) { return IN_SET(state, SERVICE_START, SERVICE_START_POST, @@ -754,6 +756,11 @@ static int service_verify(Service *s) { s->restart_usec = s->restart_max_delay_usec; } + if (s->refresh_on_reload_set && s->refresh_on_reload_flags != _SERVICE_REFRESH_ON_RELOAD_ALL) { + if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS)) + service_can_reload_extensions(s, /* warn = */ true); + } + return 0; } @@ -891,6 +898,11 @@ static int service_add_extras(Service *s) { (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) s->notify_access = NOTIFY_MAIN; + if (!s->refresh_on_reload_set) { + assert_cc(SERVICE_REFRESH_ON_RELOAD_DEFAULT == SERVICE_RELOAD_EXTENSIONS); + s->refresh_on_reload_flags = service_can_reload_extensions(s, /* warn = */ false) ? SERVICE_RELOAD_EXTENSIONS : 0; + } + /* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when * delegation is on, in that case it we assume the payload knows better what to do and can process * things in a more focused way. */ @@ -2823,28 +2835,53 @@ static void service_enter_reload(Service *s) { service_enter_reload_signal(s); } -static bool service_should_reload_extensions(Service *s) { - int r; - +static bool service_can_reload_extensions(Service *s, bool warn) { assert(s); - if (!pidref_is_set(&s->main_pid)) { - log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID."); - return false; - } - - r = exec_context_has_vpicked_extensions(&s->exec_context); - if (r < 0) - log_unit_warning_errno(UNIT(s), r, "Failed to determine if service should reload extensions, assuming false: %m"); - if (r == 0) - log_unit_debug(UNIT(s), "Service has no extensions to reload."); - if (r <= 0) - return false; - // TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload. // For now, skip for user services. + + if (exec_context_has_vpicked_extensions(&s->exec_context) <= 0) { + if (warn) + log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no extensions using vpick. Ignoring."); + return false; + } + + if (!s->exec_command[SERVICE_EXEC_START]) { + if (warn) + log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no main process (ExecStart=). Ignoring."); + return false; + } + if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) { - log_once(LOG_WARNING, "Not reloading extensions for user services."); + if (warn) + log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, which is not supported in user mode. Ignoring."); + return false; + } + + return true; +} + +static bool service_get_effective_reload_extensions(Service *s) { + assert(s); + + if (!FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS)) + return false; + + if (!service_can_reload_extensions(s, /* warn = */ false)) + return false; + + return true; +} + +static bool service_should_reload_extensions(Service *s) { + assert(s); + + if (!service_get_effective_reload_extensions(s)) + return false; + + if (!pidref_is_set(&s->main_pid)) { + log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID."); return false; } @@ -5888,6 +5925,72 @@ static const char* const service_timeout_failure_mode_table[_SERVICE_TIMEOUT_FAI DEFINE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode); +static const struct { + ServiceRefreshOnReload flag; + const char *name; +} service_refresh_on_reload_table[] = { + { SERVICE_RELOAD_EXTENSIONS, "extensions" }, +}; + +ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) { + assert(s); + + FOREACH_ELEMENT(i, service_refresh_on_reload_table) + if (streq(s, i->name)) + return i->flag; + + return _SERVICE_REFRESH_ON_RELOAD_INVALID; +} + +int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret) { + ServiceRefreshOnReload flags = 0; + int r; + + assert(s); + assert(ret); + + for (;;) { + _cleanup_free_ char *v = NULL; + ServiceRefreshOnReload f; + + r = extract_first_word(&s, &v, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + f = service_refresh_on_reload_flag_from_string(v); + if (f < 0) + return f; + assert(f > 0); + + flags |= f; + } + + *ret = flags; + return 0; +} + +int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(flags >= 0); + assert(ret); + + FOREACH_ELEMENT(i, service_refresh_on_reload_table) { + if (!FLAGS_SET(flags, i->flag)) + continue; + + r = strv_extend(&l, i->name); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(l); + return 0; +} + const UnitVTable service_vtable = { .object_size = sizeof(Service), .exec_context_offset = offsetof(Service, exec_context), diff --git a/src/core/service.h b/src/core/service.h index 167b7891e0..9aeba9916a 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -96,6 +96,14 @@ typedef enum ServiceRestartMode { _SERVICE_RESTART_MODE_INVALID = -EINVAL, } ServiceRestartMode; +typedef enum ServiceRefreshOnReload { + SERVICE_RELOAD_EXTENSIONS = 1 << 0, + _SERVICE_REFRESH_ON_RELOAD_ALL = (1 << 1) - 1, + _SERVICE_REFRESH_ON_RELOAD_INVALID = -EINVAL, +} ServiceRefreshOnReload; + +#define SERVICE_REFRESH_ON_RELOAD_DEFAULT SERVICE_RELOAD_EXTENSIONS + typedef struct ServiceFDStore { Service *service; @@ -237,6 +245,9 @@ typedef struct Service { int reload_signal; usec_t reload_begin_usec; + bool refresh_on_reload_set; + ServiceRefreshOnReload refresh_on_reload_flags; + OOMPolicy oom_policy; char *usb_function_descriptors; @@ -288,6 +299,10 @@ DECLARE_STRING_TABLE_LOOKUP(service_result, ServiceResult); DECLARE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode); +ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) _pure_; +int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret); +int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret); + DEFINE_CAST(SERVICE, Service); /* Only exported for unit tests */ diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 9a8e31474a..057a89b8ab 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1172,6 +1172,56 @@ static int bus_append_import_credential(sd_bus_message *m, const char *field, co return 1; } +static int bus_append_refresh_on_reload(sd_bus_message *m, const char *field, const char *eq) { + int r; + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(m, 's', field); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(bs)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(bs)"); + if (r < 0) + return bus_log_create_error(r); + + bool invert = *eq == '~'; + + for (const char *p = eq + invert;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + return parse_log_error(r, field, eq); + if (r == 0) + break; + + r = sd_bus_message_append(m, "(bs)", invert, word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 1; +} + static int bus_append_log_extra_fields(sd_bus_message *m, const char *field, const char *eq) { int r; @@ -2665,6 +2715,7 @@ static const BusProperty service_properties[] = { { "SuccessExitStatus", bus_append_exit_status }, { "OpenFile", bus_append_open_file }, { "ReloadSignal", bus_append_signal_from_string }, + { "RefreshOnReload", bus_append_refresh_on_reload }, {} };