]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: preserve shutdown timestamps via LUO
authorLuca Boccassi <luca.boccassi@gmail.com>
Fri, 19 Jun 2026 22:49:44 +0000 (23:49 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 30 Jun 2026 09:21:18 +0000 (10:21 +0100)
src/core/luo.c
src/shared/luo-util.c
src/shared/luo-util.h
src/shutdown/shutdown.c
test/units/TEST-91-LIVEUPDATE.sh

index ffb208f3aa89566684fc8c3de9eda45f536e93f3..6633c86994beabf34d78c384b4c8cb4819a9383f 100644 (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");
index c1ce23f5b9039f71819a7a3728c667dd231b54b2..bba25b29ada2e4d532ef6d0966d960947423b35b 100644 (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;
index 96500e24fc4215208b7c6c13c4078a33e0e92565..8f4ca5ab5427d60491f2ac84263a11b5ea75c285 100644 (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);
index bc1f41d990418bc574cbe82613961cdafde89cfd..1eec7726ef8a663b4329d87532de152041253e1b 100644 (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) {
index 9fba388cfa53bd93ae0fa0d7133b4aa7c61c711b..0fafb84fa5dcae8ee5ae1abaabd224387e74c542 100755 (executable)
@@ -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