From 00c084fe0276b664bc28cdbf0bb11c8e1302c82d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 6 Nov 2025 14:54:47 +0000 Subject: [PATCH 1/2] core: prepare reload/reexec for varlink --- src/core/dbus-manager.c | 17 ++++------------- src/core/dbus.c | 10 +++++----- src/core/manager.c | 20 ++++++++++++++++++-- src/core/manager.h | 4 +++- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 2610442384..3f3d0f2d62 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1513,23 +1513,14 @@ static void log_caller(sd_bus_message *message, Manager *manager, const char *me assert(manager); assert(method); - if (sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_COMM, &creds) < 0) + if (sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_AUGMENT, &creds) < 0) return; /* We need at least the PID, otherwise there's nothing to log, the rest is optional. */ if (bus_creds_get_pidref(creds, &pidref) < 0) return; - const char *comm = NULL; - Unit *caller; - - (void) sd_bus_creds_get_comm(creds, &comm); - caller = manager_get_unit_by_pidref(manager, &pidref); - - log_notice("%s requested from client PID " PID_FMT "%s%s%s%s%s%s...", - method, pidref.pid, - comm ? " ('" : "", strempty(comm), comm ? "')" : "", - caller ? " (unit " : "", caller ? caller->id : "", caller ? ")" : ""); + manager_log_caller(manager, &pidref, method); } static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -1564,8 +1555,8 @@ static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error * * is finished. That way the caller knows when the reload * finished. */ - assert(!m->pending_reload_message); - r = sd_bus_message_new_method_return(message, &m->pending_reload_message); + assert(!m->pending_reload_message_dbus); + r = sd_bus_message_new_method_return(message, &m->pending_reload_message_dbus); if (r < 0) return r; diff --git a/src/core/dbus.c b/src/core/dbus.c index e29354fd74..4d6623502d 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -56,17 +56,17 @@ void bus_send_pending_reload_message(Manager *m) { assert(m); - if (!m->pending_reload_message) + if (!m->pending_reload_message_dbus) return; /* If we cannot get rid of this message we won't dispatch any D-Bus messages, so that we won't end up wanting * to queue another message. */ - r = sd_bus_message_send(m->pending_reload_message); + r = sd_bus_message_send(m->pending_reload_message_dbus); if (r < 0) log_warning_errno(r, "Failed to send queued reload message, ignoring: %m"); - m->pending_reload_message = sd_bus_message_unref(m->pending_reload_message); + m->pending_reload_message_dbus = sd_bus_message_unref(m->pending_reload_message_dbus); return; } @@ -1004,8 +1004,8 @@ static void destroy_bus(Manager *m, sd_bus **bus) { } /* Get rid of queued message on this bus */ - if (m->pending_reload_message && sd_bus_message_get_bus(m->pending_reload_message) == *bus) - m->pending_reload_message = sd_bus_message_unref(m->pending_reload_message); + if (m->pending_reload_message_dbus && sd_bus_message_get_bus(m->pending_reload_message_dbus) == *bus) + m->pending_reload_message_dbus = sd_bus_message_unref(m->pending_reload_message_dbus); /* Possibly flush unwritten data, but only if we are * unprivileged, since we don't want to sync here */ diff --git a/src/core/manager.c b/src/core/manager.c index b501d3f1c5..f8b748a4ac 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -2570,7 +2570,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { /* When we are reloading, let's not wait with generating signals, since we need to exit the manager as quickly * as we can. There's no point in throttling generation of signals in that case. */ - if (MANAGER_IS_RELOADING(m) || m->send_reloading_done || m->pending_reload_message) + if (MANAGER_IS_RELOADING(m) || m->send_reloading_done || m->pending_reload_message_dbus) budget = UINT_MAX; /* infinite budget in this case */ else { /* Anything to do at all? */ @@ -2623,7 +2623,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { n++; } - if (m->pending_reload_message) { + if (m->pending_reload_message_dbus) { bus_send_pending_reload_message(m); n++; } @@ -5197,6 +5197,22 @@ LogTarget manager_get_executor_log_target(Manager *m) { return log_get_target(); } +void manager_log_caller(Manager *manager, PidRef *caller, const char *method) { + _cleanup_free_ char *comm = NULL; + + assert(manager); + assert(pidref_is_set(caller)); + assert(method); + + (void) pidref_get_comm(caller, &comm); + Unit *caller_unit = manager_get_unit_by_pidref(manager, caller); + + log_notice("%s requested from client PID " PID_FMT "%s%s%s%s%s%s...", + method, caller->pid, + comm ? " ('" : "", strempty(comm), comm ? "')" : "", + caller_unit ? " (unit " : "", caller_unit ? caller_unit->id : "", caller_unit ? ")" : ""); +} + static const char* const manager_state_table[_MANAGER_STATE_MAX] = { [MANAGER_INITIALIZING] = "initializing", [MANAGER_STARTING] = "starting", diff --git a/src/core/manager.h b/src/core/manager.h index b7ee766b05..8c57c1d9a2 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -317,7 +317,7 @@ typedef struct Manager { /* This is used during reloading: before the reload we queue * the reply message here, and afterwards we send it */ - sd_bus_message *pending_reload_message; + sd_bus_message *pending_reload_message_dbus; Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ @@ -636,6 +636,8 @@ int manager_override_watchdog_pretimeout_governor(Manager *m, const char *govern LogTarget manager_get_executor_log_target(Manager *m); +void manager_log_caller(Manager *manager, PidRef *caller, const char *method); + int manager_allocate_idle_pipe(Manager *m); void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope); From 55a1b36e91944dd1bc7c0861b69cff20aff8554d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 3 Nov 2025 00:44:57 +0000 Subject: [PATCH 2/2] core: add varlink Reload/Reexecute methods Same as the D-Bus ones --- src/core/dbus-manager.c | 1 + src/core/manager.c | 7 +- src/core/manager.h | 4 +- src/core/varlink-manager.c | 100 +++++++++++++++++++++ src/core/varlink-manager.h | 4 + src/core/varlink.c | 17 ++++ src/core/varlink.h | 2 + src/shared/varlink-io.systemd.Manager.c | 13 +++ test/units/TEST-59-RELOADING-RESTART.sh | 4 + test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 3 + 10 files changed, 153 insertions(+), 2 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 3f3d0f2d62..ea4b1f3767 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1556,6 +1556,7 @@ static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error * * finished. */ assert(!m->pending_reload_message_dbus); + assert(!m->pending_reload_message_vl); r = sd_bus_message_new_method_return(message, &m->pending_reload_message_dbus); if (r < 0) return r; diff --git a/src/core/manager.c b/src/core/manager.c index f8b748a4ac..1dfacadc29 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -2570,7 +2570,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { /* When we are reloading, let's not wait with generating signals, since we need to exit the manager as quickly * as we can. There's no point in throttling generation of signals in that case. */ - if (MANAGER_IS_RELOADING(m) || m->send_reloading_done || m->pending_reload_message_dbus) + if (MANAGER_IS_RELOADING(m) || m->send_reloading_done || m->pending_reload_message_dbus || m->pending_reload_message_vl) budget = UINT_MAX; /* infinite budget in this case */ else { /* Anything to do at all? */ @@ -2628,6 +2628,11 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { n++; } + if (m->pending_reload_message_vl) { + manager_varlink_send_pending_reload_message(m); + n++; + } + return n; } diff --git a/src/core/manager.h b/src/core/manager.h index 8c57c1d9a2..2de287e1ca 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -316,8 +316,10 @@ typedef struct Manager { sd_id128_t bus_id, deserialized_bus_id; /* This is used during reloading: before the reload we queue - * the reply message here, and afterwards we send it */ + * the reply message here, and afterwards we send it. + * It can be either a D-Bus message or a Varlink message, but not both. */ sd_bus_message *pending_reload_message_dbus; + sd_varlink *pending_reload_message_vl; Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 3414ccb9ed..35c752f81e 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -7,9 +7,13 @@ #include "alloc-util.h" #include "architecture.h" #include "build.h" +#include "bus-polkit.h" #include "confidential-virt.h" #include "json-util.h" #include "manager.h" +#include "pidref.h" +#include "process-util.h" +#include "selinux-access.h" #include "set.h" #include "strv.h" #include "syslog-util.h" @@ -17,6 +21,7 @@ #include "version.h" #include "varlink-common.h" #include "varlink-manager.h" +#include "varlink-util.h" #include "virt.h" #include "watchdog.h" @@ -181,3 +186,98 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd return sd_varlink_reply(link, v); } + +int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, "reload"); + if (r < 0) + return r; + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.reload-daemon", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + /* We need at least the pidref, otherwise there's nothing to log about. */ + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + else + manager_log_caller(manager, &pidref, "Reload"); + + /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ + if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { + log_warning("Reloading request rejected due to rate limit."); + return sd_varlink_error(link, VARLINK_ERROR_MANAGER_RATE_LIMIT_REACHED, NULL); + } + + /* Instead of sending the reply back right away, we just remember that we need to and then send it + * after the reload is finished. That way the caller knows when the reload finished. */ + + assert(!manager->pending_reload_message_vl); + assert(!manager->pending_reload_message_dbus); + manager->pending_reload_message_vl = sd_varlink_ref(link); + + manager->objective = MANAGER_RELOAD; + + return 1; +} + +int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, "reload"); + if (r < 0) + return r; + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.reload-daemon", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + /* We need at least the pidref, otherwise there's nothing to log about. */ + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + else + manager_log_caller(manager, &pidref, "Reexecute"); + + /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ + if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { + log_warning("Reexecution request rejected due to rate limit."); + return sd_varlink_error(link, VARLINK_ERROR_MANAGER_RATE_LIMIT_REACHED, NULL); + } + + /* We don't send a reply back here, the client should just wait for us disconnecting. */ + + manager->objective = MANAGER_REEXECUTE; + + return 1; +} diff --git a/src/core/varlink-manager.h b/src/core/varlink-manager.h index fde4b61376..bff621d536 100644 --- a/src/core/varlink-manager.h +++ b/src/core/varlink-manager.h @@ -3,4 +3,8 @@ #include "core-forward.h" +#define VARLINK_ERROR_MANAGER_RATE_LIMIT_REACHED "io.systemd.Manager.RateLimitReached" + int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index 90e023cc16..99f12c59e5 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -384,6 +384,8 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_bind_method_many( s, "io.systemd.Manager.Describe", vl_method_describe_manager, + "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, + "io.systemd.Manager.Reload", vl_method_reload_manager, "io.systemd.Unit.List", vl_method_list_units, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.GetEnvironment", varlink_method_get_environment); @@ -495,3 +497,18 @@ void manager_varlink_done(Manager *m) { m->varlink_server = sd_varlink_server_unref(m->varlink_server); m->managed_oom_varlink = sd_varlink_close_unref(m->managed_oom_varlink); } + +void manager_varlink_send_pending_reload_message(Manager *m) { + int r; + + assert(m); + + if (!m->pending_reload_message_vl) + return; + + r = sd_varlink_reply(m->pending_reload_message_vl, /* parameters= */ NULL); + if (r < 0) + log_warning_errno(r, "Failed to send queued reload message, ignoring: %m"); + + m->pending_reload_message_vl = sd_varlink_unref(m->pending_reload_message_vl); +} diff --git a/src/core/varlink.h b/src/core/varlink.h index 72e6a2b130..f959cf8a67 100644 --- a/src/core/varlink.h +++ b/src/core/varlink.h @@ -12,3 +12,5 @@ void manager_varlink_done(Manager *m); * - The value of ManagedOOM*= properties change * - A unit with ManagedOOM*= properties changes unit active state */ int manager_varlink_send_managed_oom_update(Unit *u); + +void manager_varlink_send_pending_reload_message(Manager *m); diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index cdcaa53b71..dff8127bb5 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -173,10 +173,23 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("Runtime information of the manager"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, ManagerRuntime, 0)); +static SD_VARLINK_DEFINE_METHOD( + Reexecute); + +static SD_VARLINK_DEFINE_METHOD( + Reload); + +static SD_VARLINK_DEFINE_ERROR(RateLimitReached); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Manager, "io.systemd.Manager", &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("Reexecute the main manager process"), + &vl_method_Reexecute, + SD_VARLINK_SYMBOL_COMMENT("Reload the manager configuration"), + &vl_method_Reload, + &vl_error_RateLimitReached, &vl_type_ManagerContext, &vl_type_ManagerRuntime, &vl_type_Timestamp, diff --git a/test/units/TEST-59-RELOADING-RESTART.sh b/test/units/TEST-59-RELOADING-RESTART.sh index 6600bd054d..361d091a60 100755 --- a/test/units/TEST-59-RELOADING-RESTART.sh +++ b/test/units/TEST-59-RELOADING-RESTART.sh @@ -89,6 +89,9 @@ systemctl daemon-reload # The timeout will hit (and the test will fail) if the reloads are not rate-limited timeout 15 bash -c 'while systemctl daemon-reload --no-block; do true; done' +# Same for varlink, rate limiting is shared +timeout 15 bash -c 'while varlinkctl call --timeout=1 /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}'; do true; done' + # Rate limit should reset after 9s sleep 10 @@ -96,6 +99,7 @@ systemctl daemon-reload # Same test for reexec, but we wait here timeout 15 bash -c 'while systemctl daemon-reexec; do true; done' +timeout 15 bash -c 'while varlinkctl call --timeout=infinity /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}'; do true; done' # Rate limit should reset after 9s sleep 10 diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index a35097ffa7..6168fc65eb 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -184,6 +184,9 @@ rm /tmp/describe1.json /tmp/describe2.json varlinkctl info /run/systemd/io.systemd.Manager varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Manager varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Describe '{}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}' +# This will disconnect and fail, as the manager reexec and drops connections +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reexecute '{}' ||: # test io.systemd.Network varlinkctl info /run/systemd/netif/io.systemd.Network