From: Luca Boccassi Date: Fri, 19 Jun 2026 22:49:44 +0000 (+0100) Subject: core: preserve shutdown timestamps via LUO X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ec919e7ec1b62cc1c8a1dc9bffce203df72a556f;p=thirdparty%2Fsystemd.git core: preserve shutdown timestamps via LUO --- diff --git a/src/core/luo.c b/src/core/luo.c index ffb208f3aa8..6633c86994b 100644 --- a/src/core/luo.c +++ b/src/core/luo.c @@ -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"); diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c index c1ce23f5b90..bba25b29ada 100644 --- a/src/shared/luo-util.c +++ b/src/shared/luo-util.c @@ -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; diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h index 96500e24fc4..8f4ca5ab542 100644 --- a/src/shared/luo-util.h +++ b/src/shared/luo-util.h @@ -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); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index bc1f41d9904..1eec7726ef8 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -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) { diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh index 9fba388cfa5..0fafb84fa5d 100755 --- a/test/units/TEST-91-LIVEUPDATE.sh +++ b/test/units/TEST-91-LIVEUPDATE.sh @@ -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