From: Mike Yuan Date: Sun, 14 Dec 2025 17:20:36 +0000 (+0100) Subject: core/service: introduce RefreshOnReload= setting X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a43bc1aed81211af566aa0eb338dbecca2d99824;p=thirdparty%2Fsystemd.git core/service: introduce RefreshOnReload= setting This allows controlling resources to be refreshed before performing reload, with one extra benefit that in the future we can permit "seemless reload"s, i.e. no active signaling is done to the main process after refreshing get updated. This could come in handy for programs that loads stuff on demand or watches changes via inotify. --- diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index f95b5259ff9..9b1b1f77218 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 7f8198ff487..5605445c34e 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 ce90b08e562..d2bfd20fd43 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 3b0ee5fb30c..4677564904c 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 ec3fd2ed8c3..77cc254f597 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."); + // 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; } - 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) + 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; + } - // TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload. - // For now, skip for user services. 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 167b7891e08..9aeba9916af 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 9a8e31474a3..057a89b8abe 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 }, {} };