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