]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add KExecsCount property
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 15 Jun 2026 20:35:47 +0000 (21:35 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 19 Jun 2026 18:36:01 +0000 (19:36 +0100)
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
src/core/dbus-manager.c
src/core/luo.c
src/core/manager-serialize.c
src/core/manager.h
src/core/varlink-manager.c
src/shared/luo-util.c
src/shared/luo-util.h
src/shared/varlink-io.systemd.Manager.c
test/units/TEST-91-LIVEUPDATE.sh

index 42455ef6c7a635b4fa750a82014927880da7a5c7..de4a6e229fab20c22d4f62888b659b1778925636 100644 (file)
@@ -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 {
 
     <variablelist class="dbus-property" generated="True" extra-ref="SoftRebootsCount"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="KExecsCount"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ReloadCount"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="DefaultMemoryZSwapWriteback"/>
@@ -1882,6 +1886,11 @@ node /org/freedesktop/systemd1 {
       <para><varname>SoftRebootsCount</varname> encodes how many soft-reboots were successfully completed
       since the last full boot. Starts at <literal>0</literal>.</para>
 
+      <para><varname>KExecsCount</varname> encodes how many kexec-based live updates were successfully
+      completed since the last full boot. Starts at <literal>0</literal>. The counter is preserved across the
+      kexec through the Live Update Orchestrator (LUO), and is hence <literal>0</literal> when the kernel does
+      not provide LUO support.</para>
+
       <para><varname>ReloadCount</varname> encodes the number of successfully completed configuration
       reloads of the manager. The counter is reset to <literal>0</literal> on
       <command>daemon-reexec</command> and on the initial boot.</para>
@@ -12749,6 +12758,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>ReloadCount</varname>,
       <varname>EventLoopRateLimitIntervalUSec</varname>, and
       <varname>EventLoopRateLimitBurst</varname> were added in version 261.</para>
+      <para><varname>KExecsCount</varname> was added in version 262.</para>
     </refsect2>
     <refsect2>
       <title>Unit Objects</title>
index 595136f22c9944137441a248b594b2a1bec676a8..69921ad6263d8a0937b330bc4d67d9a3b1f810e3 100644 (file)
@@ -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),
 
index 396135f4cff320b9e9d78052eb02ab1357b13191..ffb208f3aa89566684fc8c3de9eda45f536e93f3 100644 (file)
@@ -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");
index bef4021771e3feac8c7bdf03d3097c4a05a1be01..72c6aa19a3bdcc58b699c5f2ef45e5722bf3ae5b 100644 (file)
@@ -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);
index 4695112c041ffe8b0864a29f39f4207c1a4431e1..e655e168c60b4d90d75d94b95212998f8cf41b63 100644 (file)
@@ -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;
 
index 384d4709c97867b946c53a1206c6710a47bb6276..f63d2a2e8f3db99edba75557f3cd09e648188ef5 100644 (file)
@@ -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));
 }
 
index 172735daf4047c9d687158d8a4bffd630f7700fc..5de805e1f640e82c4c8473bf3d3cd7a5e08ef98d 100644 (file)
@@ -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");
index a7f28bb27317457b50c97bbe49e89acb907b7a8a..96500e24fc4215208b7c6c13c4078a33e0e92565 100644 (file)
@@ -12,6 +12,7 @@
  *
  *   {
  *     "version": 1,
+ *     "state": { },
  *     "units": {
  *       "unit-name.service": {
  *         "fdstore": [
index d4702b249d2517016970f2fb1bc39b9b1ff51dad..9331a2b1aee62c598f3d1737c467ded6d8e70558 100644 (file)
@@ -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));
 
index 27f734748ac938ec30c1caf373e8f29b7605ea9c..bea029afa28d52e9429cef9546428a83fa4f6db5 100755 (executable)
@@ -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