]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/service: introduce RefreshOnReload= setting
authorMike Yuan <me@yhndnzj.com>
Sun, 14 Dec 2025 17:20:36 +0000 (18:20 +0100)
committerMike Yuan <me@yhndnzj.com>
Tue, 10 Feb 2026 20:54:12 +0000 (21:54 +0100)
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.

src/core/dbus-service.c
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/service.c
src/core/service.h
src/shared/bus-unit-util.c

index f95b5259ff906e5b489f5a87ac8700b90c5bb732..9b1b1f772183361da5ac2cea54ef6b00c22ef2a2 100644 (file)
@@ -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;
 }
 
index 7f8198ff487c2b4a33f1cad4743c2a9962029ef4..5605445c34ead041aea1b47213e54c659d1af5b2 100644 (file)
@@ -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') }}
index ce90b08e562a382aa8f53ab8956625faa6f7495d..d2bfd20fd43af518eef4b878c882eab37692dee0 100644 (file)
@@ -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,
index 3b0ee5fb30cd70db6c5e14281552ee9e5c0aa4a1..4677564904c52fc5082be6cf83d2359c405e9def 100644 (file)
@@ -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);
index ec3fd2ed8c36bd09ac9bbbc7a9cf33371456234d..77cc254f597b58af35c99583ed5d26af8a3f662b 100644 (file)
@@ -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),
index 167b7891e08f2b138b80e56dbb3595efbbb5c696..9aeba9916afe428fd86c28a7da41b8f25ba1927f 100644 (file)
@@ -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 */
index 9a8e31474a38cc680598afbd2ee374ab4eeba4ce..057a89b8abe50aa2d28bb30fc2674a5e38ffeb60 100644 (file)
@@ -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                  },
         {}
 };