core: add varlink Reload/Reexecute methods (#39561)

Same as the D-Bus ones
This commit is contained in:
Yu Watanabe
2025-11-15 04:17:01 +09:00
committed by GitHub
11 changed files with 182 additions and 22 deletions

View File

@@ -1542,23 +1542,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) {
@@ -1593,8 +1584,9 @@ 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);
assert(!m->pending_reload_message_vl);
r = sd_bus_message_new_method_return(message, &m->pending_reload_message_dbus);
if (r < 0)
return r;

View File

@@ -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 */

View File

@@ -2577,7 +2577,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 || m->pending_reload_message_vl)
budget = UINT_MAX; /* infinite budget in this case */
else {
/* Anything to do at all? */
@@ -2630,11 +2630,16 @@ 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++;
}
if (m->pending_reload_message_vl) {
manager_varlink_send_pending_reload_message(m);
n++;
}
return n;
}
@@ -5206,6 +5211,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",

View File

@@ -321,8 +321,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 */
sd_bus_message *pending_reload_message;
* 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 */
@@ -641,6 +643,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);

View File

@@ -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"
@@ -200,3 +205,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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -175,10 +175,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,

View File

@@ -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

View File

@@ -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