From f9e6419f3426ce1b9d281e833f924e91b84b1127 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 15 Jun 2026 21:35:47 +0100 Subject: [PATCH] core: add KExecsCount property Use LUO to count kexec reboots, and expose through a new property Fixes https://github.com/systemd/systemd/issues/42581 --- man/org.freedesktop.systemd1.xml | 10 +++++++++ src/core/dbus-manager.c | 1 + src/core/luo.c | 28 ++++++++++++++++++------- src/core/manager-serialize.c | 8 +++++++ src/core/manager.h | 3 +++ src/core/varlink-manager.c | 1 + src/shared/luo-util.c | 3 +++ src/shared/luo-util.h | 1 + src/shared/varlink-io.systemd.Manager.c | 2 ++ test/units/TEST-91-LIVEUPDATE.sh | 2 ++ 10 files changed, 52 insertions(+), 7 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 42455ef6c7..de4a6e229f 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -576,6 +576,8 @@ node /org/freedesktop/systemd1 { readonly s CtrlAltDelBurstAction = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u SoftRebootsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u KExecsCount = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t ReloadCount = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -1293,6 +1295,8 @@ node /org/freedesktop/systemd1 { + + @@ -1882,6 +1886,11 @@ node /org/freedesktop/systemd1 { SoftRebootsCount encodes how many soft-reboots were successfully completed since the last full boot. Starts at 0. + KExecsCount encodes how many kexec-based live updates were successfully + completed since the last full boot. Starts at 0. The counter is preserved across the + kexec through the Live Update Orchestrator (LUO), and is hence 0 when the kernel does + not provide LUO support. + 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. @@ -12749,6 +12758,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ReloadCount, EventLoopRateLimitIntervalUSec, and EventLoopRateLimitBurst were added in version 261. + KExecsCount was added in version 262. Unit Objects diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 595136f22c..69921ad626 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -3010,6 +3010,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("KExecsCount", "u", bus_property_get_unsigned, offsetof(Manager, kexecs_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), diff --git a/src/core/luo.c b/src/core/luo.c index 396135f4cf..ffb208f3aa 100644 --- a/src/core/luo.c +++ b/src/core/luo.c @@ -85,11 +85,13 @@ int manager_luo_restore_fd_stores(Manager *m) { struct { uint64_t version; + sd_json_variant *state; sd_json_variant *units; } q = {}; static const sd_json_dispatch_field mapping_dispatch_table[] = { { "version", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(q, version), SD_JSON_MANDATORY }, + { "state", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(q, state), 0 }, { "units", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(q, units), 0 }, {} }; @@ -103,6 +105,22 @@ int manager_luo_restore_fd_stores(Manager *m) { return 0; } + struct { + unsigned kexecs_count; + } state_data = {}; + + static const sd_json_dispatch_field state_dispatch_table[] = { + { "kexecsCount", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, voffsetof(state_data, kexecs_count), 0 }, + {} + }; + + r = sd_json_dispatch(q.state, state_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &state_data); + if (r >= 0) + m->kexecs_count = state_data.kexecs_count; + + /* If we found a LUO session then by definition we have just successfully kexec rebooted */ + (void) INC_SAFE(&m->kexecs_count, 1); + /* Retrieve all fds from the session and dispatch each to the named unit, eagerly loading the * unit if necessary. */ JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, q.units) { @@ -284,16 +302,12 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { return log_error_errno(r, "Failed to add unit to LUO serialization JSON: %m"); } - if (n_serialized == 0) { - log_debug("No fd store entries to serialize for LUO."); - *ret_f = NULL; - *ret_fds = NULL; - return 0; - } - r = sd_json_buildo( &root, SD_JSON_BUILD_PAIR_UNSIGNED("version", LUO_PROTOCOL_VERSION), + SD_JSON_BUILD_PAIR("state", + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("kexecsCount", m->kexecs_count))), SD_JSON_BUILD_PAIR_CONDITION(!!units, "units", SD_JSON_BUILD_VARIANT(units))); if (r < 0) return log_error_errno(r, "Failed to build LUO serialization JSON: %m"); diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index bef4021771..72c6aa19a3 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -124,6 +124,7 @@ int manager_serialize( (void) serialize_item(f, "previous-objective", manager_objective_to_string(m->objective)); (void) serialize_item_format(f, "soft-reboots-count", "%u", m->soft_reboots_count); + (void) serialize_item_format(f, "kexecs-count", "%u", m->kexecs_count); (void) serialize_item_format(f, "fd-store-upstream-next-index", "%" PRIu64, m->fd_store_upstream_next_index); for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) { @@ -758,6 +759,13 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { log_notice("Failed to parse soft reboots counter '%s', ignoring.", val); else m->soft_reboots_count = n; + } else if ((val = startswith(l, "kexecs-count="))) { + unsigned n; + + if (safe_atou(val, &n) < 0) + log_notice("Failed to parse kexecs counter '%s', ignoring.", val); + else + m->kexecs_count = n; } else if ((val = startswith(l, "fd-store-upstream-next-index="))) { if (safe_atou64(val, &m->fd_store_upstream_next_index) < 0) log_notice("Failed to parse fd-store-upstream-next-index '%s', ignoring.", val); diff --git a/src/core/manager.h b/src/core/manager.h index 4695112c04..e655e168c6 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -510,6 +510,9 @@ typedef struct Manager { unsigned soft_reboots_count; + /* When LUO is enabled we can count consecutive kexec reboots. */ + unsigned kexecs_count; + /* The number of successfully completed configuration reloads. */ uint64_t reload_count; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 384d4709c9..f63d2a2e8f 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -194,6 +194,7 @@ static int manager_runtime_build_json(sd_json_variant **ret, const char *name, v 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("KExecsCount", m->kexecs_count), SD_JSON_BUILD_PAIR_UNSIGNED("ReloadCount", m->reload_count)); } diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c index 172735daf4..5de805e1f6 100644 --- a/src/shared/luo-util.c +++ b/src/shared/luo-util.c @@ -342,9 +342,12 @@ int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) if (!sd_json_variant_is_unsigned(version)) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "LUO serialization 'version' field is missing or not an unsigned integer"); + sd_json_variant *state = sd_json_variant_by_key(serialization, "state"); + r = sd_json_buildo( &mapping, SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_VARIANT(version)), + SD_JSON_BUILD_PAIR_CONDITION(!!state, "state", SD_JSON_BUILD_VARIANT(state)), SD_JSON_BUILD_PAIR_CONDITION(!!units, "units", SD_JSON_BUILD_VARIANT(units))); if (r < 0) return log_error_errno(r, "Failed to build LUO mapping: %m"); diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h index a7f28bb273..96500e24fc 100644 --- a/src/shared/luo-util.h +++ b/src/shared/luo-util.h @@ -12,6 +12,7 @@ * * { * "version": 1, + * "state": { }, * "units": { * "unit-name.service": { * "fdstore": [ diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index d4702b249d..9331a2b1ae 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -189,6 +189,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( 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_FIELD_COMMENT("Encodes how many consecutive kexec reboots were successfully completed since the last full boot. Zero if the Live Update Orchestrator is not available."), + SD_VARLINK_DEFINE_FIELD(KExecsCount, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Number of successfully completed configuration reloads"), SD_VARLINK_DEFINE_FIELD(ReloadCount, SD_VARLINK_INT, 0)); diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh index 27f734748a..bea029afa2 100755 --- a/test/units/TEST-91-LIVEUPDATE.sh +++ b/test/units/TEST-91-LIVEUPDATE.sh @@ -78,6 +78,8 @@ if grep -qw luo_nboot=1 /proc/cmdline; then # Verify that the fd store of the main test service survived the kexec. /usr/lib/systemd/tests/unit-tests/manual/test-luo check + assert_eq "$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager KExecsCount | jq -r '.data')" "1" + # Negative path: a unit stored a child LUO session named like PID 1's own # ("systemd") on the first boot. PID 1's serialize step must have refused to # serialize that fd store entry (anti-hijack guard in