From: Simon Lucido Date: Mon, 20 Apr 2026 15:05:27 +0000 (+0200) Subject: core: add ReloadCount to Manager and bump on successful reload X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d667b6b97ff45e0739d165563b896f7d80417b99;p=thirdparty%2Fsystemd.git core: add ReloadCount to Manager and bump on successful reload Introduce a counter that tracks how many configuration reloads have been successfully completed by the manager. The increment lives in manager_reload() right after the "point of no return", so failed reload attempts that bail out earlier (e.g. during serialization) do not bump the counter. It is accessible as a new ReloadCount property to org.freedesktop.systemd1.Manager (D-Bus) and ReloadCount to io.systemd.Manager.Describe (Varlink). Also add an integration test for ReloadCount that verifies that the new ReloadCount property increments by one per daemon-reload, accumulates correctly across multiple reloads, and that D-Bus and Varlink return identical values. Also tests that the counter reset after a reexec. Co-developed-by: Claude Opus 4.7 Signed-off-by: Simon Lucido --- diff --git a/NEWS b/NEWS index 451e3f1b796..49061c5e11a 100644 --- a/NEWS +++ b/NEWS @@ -63,6 +63,12 @@ CHANGES WITH 261 in spe: require direct IMDS access. The new meson option "-Dimds-network=" can be used to change the default mode to "locked" at build-time. + * The manager exposes a new ReloadCount property on its D-Bus and + Varlink interfaces (org.freedesktop.systemd1.Manager and + io.systemd.Manager respectively). The counter increments after + each successfully completed daemon-reload. It is not preserved + across daemon-reexec. + Changes in systemd-sysext/systemd-confext: * New initrd services systemd-sysext-sysroot.service and diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 76a8dd045f6..847e76f95c7 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -572,6 +572,8 @@ node /org/freedesktop/systemd1 { readonly s CtrlAltDelBurstAction = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u SoftRebootsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t ReloadCount = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultMemoryZSwapWriteback = ...; }; @@ -1279,6 +1281,8 @@ node /org/freedesktop/systemd1 { + + @@ -1866,6 +1870,10 @@ node /org/freedesktop/systemd1 { SoftRebootsCount encodes how many soft-reboots were successfully completed since the last full boot. Starts at 0. + ReloadCount encodes the number of successfully completed configuration + reloads of the manager. The counter is reset to 0 on + daemon-reexec and on the initial boot. + Virtualization contains a short ID string describing the virtualization technology the system runs in. On bare-metal hardware this is the empty string. Otherwise, it contains an identifier such as kvm, vmware and so on. For a full list of @@ -12646,8 +12654,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ DefaultMemoryZSwapWriteback, DefaultCPUPressureThresholdUSec, DefaultCPUPressureWatch, - DefaultIOPressureThresholdUSec, and - DefaultIOPressureWatch were added in version 261. + DefaultIOPressureThresholdUSec, + DefaultIOPressureWatch, and + ReloadCount were added in version 261. Unit Objects diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 076a26c6fd1..88de34c4ea4 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2978,6 +2978,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultRestrictSUIDSGID", "b", bus_property_get_bool, offsetof(Manager, defaults.restrict_suid_sgid), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CtrlAltDelBurstAction", "s", bus_property_get_emergency_action, offsetof(Manager, cad_burst_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SoftRebootsCount", "u", bus_property_get_unsigned, offsetof(Manager, soft_reboots_count), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReloadCount", "t", NULL, offsetof(Manager, reload_count), 0), SD_BUS_PROPERTY("DefaultMemoryZSwapWriteback", "b", bus_property_get_bool, offsetof(Manager, defaults.memory_zswap_writeback), SD_BUS_VTABLE_PROPERTY_CONST), /* deprecated cgroup v1 property */ diff --git a/src/core/manager.c b/src/core/manager.c index 17908d4db86..da4e9ca4081 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -3650,6 +3650,10 @@ int manager_reload(Manager *m) { /* 💀 This is the point of no return, from here on there is no way back. 💀 */ reloading = NULL; + /* Bump before sending the Reloading signal, so any client that reads + * ReloadCount in response to that signal observes the new value. */ + m->reload_count = saturate_add(m->reload_count, 1, UINT64_MAX); + bus_manager_send_reloading(m, true); /* Start by flushing out all jobs and units, all generated units, all runtime environments, all dynamic users diff --git a/src/core/manager.h b/src/core/manager.h index 7d58c330a1b..abf1764d785 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -492,6 +492,9 @@ typedef struct Manager { unsigned soft_reboots_count; + /* The number of successfully completed configuration reloads. */ + uint64_t reload_count; + /* Original ambient capabilities when we were initialized */ uint64_t saved_ambient_set; } Manager; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 0bef5cbe984..384d4709c97 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -193,7 +193,8 @@ static int manager_runtime_build_json(sd_json_variant **ret, const char *name, v JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("WatchdogLastPingTimestamp", watchdog_get_last_ping_as_dual_timestamp(&watchdog_last_ping)), SD_JSON_BUILD_PAIR_STRING("SystemState", manager_state_to_string(manager_state(m))), SD_JSON_BUILD_PAIR_UNSIGNED("ExitCode", m->return_value), - SD_JSON_BUILD_PAIR_UNSIGNED("SoftRebootsCount", m->soft_reboots_count)); + SD_JSON_BUILD_PAIR_UNSIGNED("SoftRebootsCount", m->soft_reboots_count), + SD_JSON_BUILD_PAIR_UNSIGNED("ReloadCount", m->reload_count)); } int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index 0c5ab53702b..81b3e894a34 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -194,7 +194,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Exit code of the manager"), SD_VARLINK_DEFINE_FIELD(ExitCode, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Encodes how many soft-reboots were successfully completed"), - SD_VARLINK_DEFINE_FIELD(SoftRebootsCount, SD_VARLINK_INT, 0)); + SD_VARLINK_DEFINE_FIELD(SoftRebootsCount, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Number of successfully completed configuration reloads"), + SD_VARLINK_DEFINE_FIELD(ReloadCount, SD_VARLINK_INT, 0)); static SD_VARLINK_DEFINE_METHOD( Describe, diff --git a/test/units/TEST-07-PID1.reload-count.sh b/test/units/TEST-07-PID1.reload-count.sh new file mode 100755 index 00000000000..7c31b65c5fc --- /dev/null +++ b/test/units/TEST-07-PID1.reload-count.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Verify that the manager exposes a ReloadCount property that increments on +# every daemon-reload, resets to zero across daemon-reexec (since the count +# is not serialized), and is reachable over both D-Bus and Varlink. + +read_count_dbus() { + busctl -j get-property org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + ReloadCount | jq -r '.data' +} + +read_count_varlink() { + varlinkctl call /run/systemd/io.systemd.Manager \ + io.systemd.Manager.Describe '{}' | jq -r '.runtime.ReloadCount' +} + +# Sanity: both transports must agree. +dbus_count=$(read_count_dbus) +varlink_count=$(read_count_varlink) +(( dbus_count == varlink_count )) + +# A single reload bumps the counter by one. +before=$(read_count_dbus) +systemctl daemon-reload +(( $(read_count_dbus) == before + 1 )) + +# Multiple reloads accumulate. +systemctl daemon-reload +systemctl daemon-reload +(( $(read_count_dbus) == before + 3 )) + +# And both transports still agree after the reload. +dbus_count=$(read_count_dbus) +varlink_count=$(read_count_varlink) +(( dbus_count == varlink_count )) + +# A daemon-reexec resets the counter back to zero on both transports, since +# reload_count lives only in memory and is not carried across the reexec. +# `systemctl daemon-reexec` returns as soon as the old PID 1 closes its bus +# connection, which is before the new PID 1 has rebound /run/systemd/private. +# Use --watch-bind=yes to block on inotify until the new socket is live. +systemctl daemon-reexec +busctl --watch-bind=yes call org.freedesktop.systemd1 /org/freedesktop/systemd1 \ + org.freedesktop.DBus.Peer Ping >/dev/null +(( $(read_count_dbus) == 0 )) +(( $(read_count_varlink) == 0 ))