core: preserve shutdown timestamps via LUO

This commit is contained in:
Luca Boccassi
2026-06-19 23:49:44 +01:00
parent fa5f6a654f
commit ec919e7ec1
5 changed files with 72 additions and 3 deletions

View File

@@ -107,16 +107,27 @@ int manager_luo_restore_fd_stores(Manager *m) {
struct {
unsigned kexecs_count;
dual_timestamp previous_shutdown_start, previous_shutdown_finish,
previous_shutdown_late_start, previous_shutdown_late_finish;
} 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 },
{ "kexecsCount", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, voffsetof(state_data, kexecs_count), 0 },
{ "ShutdownStartTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_start), 0 },
{ "ShutdownFinishTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_finish), 0 },
{ "ShutdownLateStartTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_late_start), 0 },
{ "ShutdownLateFinishTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_late_finish), 0 },
{}
};
r = sd_json_dispatch(q.state, state_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &state_data);
if (r >= 0)
if (r >= 0) {
m->kexecs_count = state_data.kexecs_count;
m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_START] = state_data.previous_shutdown_start;
m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_FINISH] = state_data.previous_shutdown_finish;
m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_LATE_START] = state_data.previous_shutdown_late_start;
m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_LATE_FINISH] = state_data.previous_shutdown_late_finish;
}
/* If we found a LUO session then by definition we have just successfully kexec rebooted */
(void) INC_SAFE(&m->kexecs_count, 1);
@@ -307,7 +318,9 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) {
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_UNSIGNED("kexecsCount", m->kexecs_count),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ShutdownStartTimestamp", &m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_START]),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ShutdownFinishTimestamp", &m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_FINISH]))),
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");

View File

@@ -19,6 +19,7 @@
#include "parse-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "time-util.h"
/* Kernel API defined at https://docs.kernel.org/userspace-api/liveupdate.html The /dev/liveupdate is a
* single-owner singleton, only a single process at any given time can open it. Callers can create named
@@ -210,6 +211,42 @@ int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_
return 0;
}
int luo_serialization_add_shutdown_timestamps(
sd_json_variant **serialization,
const dual_timestamp *shutdown_late_start,
const dual_timestamp *shutdown_late_finish) {
int r;
assert(serialization);
/* sd-shutdown calls this to add its timestamps to the preserved JSON payload, so that the next
* boot can expose them as the previous boot's shutdown timestamps. */
if (!*serialization)
return 0; /* No LUO serialization, nothing to augment */
if ((!shutdown_late_start || !dual_timestamp_is_set(shutdown_late_start)) &&
(!shutdown_late_finish || !dual_timestamp_is_set(shutdown_late_finish)))
return 0; /* Nothing to add */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *new_state =
sd_json_variant_ref(sd_json_variant_by_key(*serialization, "state"));
r = sd_json_variant_merge_objectbo(
&new_state,
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ShutdownLateStartTimestamp", (dual_timestamp*) shutdown_late_start),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ShutdownLateFinishTimestamp", (dual_timestamp*) shutdown_late_finish));
if (r < 0)
return log_error_errno(r, "Failed to merge LUO shutdown timestamps into state: %m");
r = sd_json_variant_set_field(serialization, "state", new_state);
if (r < 0)
return log_error_errno(r, "Failed to update LUO 'state' object: %m");
return 1;
}
int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) {
_cleanup_close_ int device_fd = -EBADF, session_fd = -EBADF;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL, *units = NULL;

View File

@@ -40,6 +40,7 @@ int luo_session_finish(int session_fd);
bool luo_session_name_is_valid(const char *name);
int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds);
int luo_serialization_add_shutdown_timestamps(sd_json_variant **serialization, const dual_timestamp *shutdown_late_start, const dual_timestamp *shutdown_late_finish);
int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd);
int fd_is_luo_session(int fd);

View File

@@ -650,6 +650,7 @@ int main(int argc, char *argv[]) {
* down is left, so record the late shutdown timestamp here. */
dual_timestamp shutdown_late_finish;
dual_timestamp_now(&shutdown_late_finish);
(void) luo_serialization_add_shutdown_timestamps(&luo_serialization, &shutdown_late_start, &shutdown_late_finish);
if (streq(arg_verb, "exit")) {
if (in_container) {

View File

@@ -80,6 +80,23 @@ if grep -qw luo_nboot=1 /proc/cmdline; then
assert_eq "$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager KExecsCount | jq -r '.data')" "1"
# The previous boot's shutdown timestamps were preserved across the kexec via LUO and are now exposed
# as the PreviousShutdown* properties (the live Shutdown* ones describe this boot, which is not shutting
# down, so they are unset here).
ts_shutdown_start=$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager PreviousShutdownStartTimestampMonotonic | jq -r '.data')
ts_shutdown_finish=$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager PreviousShutdownFinishTimestampMonotonic | jq -r '.data')
ts_shutdown_late_start=$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager PreviousShutdownLateStartTimestampMonotonic | jq -r '.data')
ts_shutdown_late_finish=$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager PreviousShutdownLateFinishTimestampMonotonic | jq -r '.data')
assert_ge "${ts_shutdown_start}" 1
assert_ge "${ts_shutdown_finish}" 1
assert_ge "${ts_shutdown_late_start}" 1
assert_ge "${ts_shutdown_late_finish}" 1
assert_le "${ts_shutdown_start}" "${ts_shutdown_finish}"
assert_le "${ts_shutdown_finish}" "${ts_shutdown_late_start}"
assert_le "${ts_shutdown_late_start}" "${ts_shutdown_late_finish}"
# 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