]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add version and structure to LUO json payload 42607/head
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 15 Jun 2026 20:33:08 +0000 (21:33 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 16 Jun 2026 12:51:59 +0000 (13:51 +0100)
We might want to add more state to the LUO session json payload,
so add a version (to allow clean compat breaks if needed) and nest
the current fdstore contents under a 'units' object, so that more
top-level data can be added in the future without breaking
backward compatibility.

Follow-up for 257c35c1a3936f53b80f16397a6909f4cd81124d

src/core/luo.c
src/shared/luo-util.c
src/shared/luo-util.h

index 91c3a01dad83287399306356d362b731165d23da..396135f4cff320b9e9d78052eb02ab1357b13191 100644 (file)
@@ -53,7 +53,7 @@ int manager_luo_restore_fd_stores(Manager *m) {
         _cleanup_close_ int device_fd = -EBADF;
         _cleanup_(luo_session_finishp) int session_fd = -EBADF;
         const char *unit_id;
-        sd_json_variant *fds_json;
+        sd_json_variant *unit_json;
         int r, n_total = 0;
 
         assert(m);
@@ -83,18 +83,39 @@ int manager_luo_restore_fd_stores(Manager *m) {
         if (r < 0)
                 return r;
 
+        struct {
+                uint64_t version;
+                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 },
+                { "units",   SD_JSON_VARIANT_OBJECT,        sd_json_dispatch_variant_noref, voffsetof(q, units),   0                 },
+                {}
+        };
+
+        r = sd_json_dispatch(mapping, mapping_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &q);
+        if (r < 0)
+                return r;
+
+        if (q.version > LUO_PROTOCOL_VERSION) {
+                log_warning("LUO mapping has unsupported version %" PRIu64 ", skipping state restoration.", q.version);
+                return 0;
+        }
+
         /* 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, fds_json, mapping) {
-                sd_json_variant *entry;
+        JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, q.units) {
+                sd_json_variant *entry, *fds_json;
 
                 if (!unit_name_is_valid(unit_id, UNIT_NAME_ANY)) {
                         log_warning("Invalid unit name '%s' in LUO mapping, skipping.", unit_id);
                         continue;
                 }
 
+                fds_json = sd_json_variant_by_key(unit_json, "fdstore");
                 if (!sd_json_variant_is_array(fds_json)) {
-                        log_warning("LUO mapping for unit '%s' is not a JSON array, skipping.", unit_id);
+                        log_warning("LUO mapping fdstore for unit '%s' is not a JSON array, skipping.", unit_id);
                         continue;
                 }
 
@@ -172,7 +193,7 @@ int manager_luo_restore_fd_stores(Manager *m) {
 }
 
 int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL, *units = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_fdset_free_ FDSet *fds = NULL;
         Unit *u;
@@ -192,8 +213,9 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) {
         if (!fds)
                 return log_oom();
 
-        /* Build a JSON object: { "unit_id": [ { "type": "fd", "name": "...", "fd": N },
-         *                                     { "type": "luo_session", "name": "...", "fd": N, "sessionName": "..." } ], ... }
+        /* Build a JSON object: { "version": 1,
+         *                        "units": { "unit_id": { "fdstore": [ { "type": "fd", "name": "...", "fd": N },
+         *                                                             { "type": "luo_session", "name": "...", "fd": N, "sessionName": "..." } ] }, ... } }
          * This is passed to systemd-shutdown which will create a LUO session and preserve the fds. */
         HASHMAP_FOREACH(u, m->units) {
                 _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL;
@@ -247,7 +269,17 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) {
                         n_serialized++;
                 }
 
-                r = sd_json_variant_set_field(&root, u->id, entries);
+                if (!entries)
+                        continue;
+
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *unit_json = NULL;
+                r = sd_json_buildo(
+                                &unit_json,
+                                SD_JSON_BUILD_PAIR_VARIANT("fdstore", entries));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to build LUO unit object: %m");
+
+                r = sd_json_variant_set_field(&units, u->id, unit_json);
                 if (r < 0)
                         return log_error_errno(r, "Failed to add unit to LUO serialization JSON: %m");
         }
@@ -259,6 +291,13 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) {
                 return 0;
         }
 
+        r = sd_json_buildo(
+                        &root,
+                        SD_JSON_BUILD_PAIR_UNSIGNED("version", LUO_PROTOCOL_VERSION),
+                        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");
+
         r = open_serialization_file("luo-fd-store", &f);
         if (r < 0)
                 return log_error_errno(r, "Failed to create LUO serialization file: %m");
index ab4a4a3235e607a111c4db1140f963531cfb3f4e..e6de4c25c2d76eb29cae562126dbec462b28af8c 100644 (file)
@@ -5,6 +5,7 @@
 #include <linux/magic.h>
 #include <sys/ioctl.h>
 #include <sys/vfs.h>
+#include <unistd.h>
 
 #include "sd-json.h"
 
@@ -149,13 +150,23 @@ int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_
 
         /* Collect all fd numbers referenced in the JSON (plus the serialization fd itself)
          * so the caller can protect them from close_all_fds(). */
+        sd_json_variant *units = sd_json_variant_by_key(root, "units");
+        if (units && !sd_json_variant_is_object(units))
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "LUO serialization 'units' field is not an object, ignoring.");
+
         const char *unit_id _unused_;
-        sd_json_variant *unit_entries;
+        sd_json_variant *unit_json;
+
+        JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, units) {
+                sd_json_variant *entry, *fdstore;
 
-        JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_entries, root) {
-                sd_json_variant *entry;
+                fdstore = sd_json_variant_by_key(unit_json, "fdstore");
+                if (fdstore && !sd_json_variant_is_array(fdstore)) {
+                        log_warning("LUO serialization 'fdstore' field is not an array, skipping.");
+                        continue;
+                }
 
-                JSON_VARIANT_ARRAY_FOREACH(entry, unit_entries) {
+                JSON_VARIANT_ARRAY_FOREACH(entry, fdstore) {
                         struct {
                                 int fd;
                         } p = {
@@ -196,9 +207,9 @@ int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_
 
 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;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL, *units = NULL;
         const char *unit_id;
-        sd_json_variant *entries;
+        sd_json_variant *unit_json, *unit_object;
         uint64_t token = LUO_MAPPING_INDEX + 1;
         int r;
 
@@ -222,17 +233,27 @@ int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd)
                 return log_error_errno(session_fd, "Failed to create LUO session '%s': %m", LUO_SESSION_NAME);
 
         /* Build the mapping JSON for the new kernel's PID 1 and preserve each fd.
-         * JSON format:   { "unit_id": [ {"type": "fd", "name": "...", "token": N},
-         *                               {"type": "luo_session", "name": "...", "sessionName": "..."} ], ... }
+         * JSON format:   { "unit_id": { "fdstore": [ {"type": "fd", "name": "...", "token": N},
+         *                                            {"type": "luo_session", "name": "...", "sessionName": "..."} ] }, ... }
          *
          * For regular fds: type=fd, preserved in the systemd session with the given LUO token.
          * For LUO session fds: type=luo_session, the session survives kexec independently, as it cannot be
          * nested. */
-        JSON_VARIANT_OBJECT_FOREACH(unit_id, entries, serialization) {
+        unit_object = sd_json_variant_by_key(serialization, "units");
+        if (unit_object && !sd_json_variant_is_object(unit_object))
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "LUO serialization 'units' field is not an object");
+
+        JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, unit_object) {
                 _cleanup_(sd_json_variant_unrefp) sd_json_variant *fd_list = NULL;
-                sd_json_variant *entry;
+                sd_json_variant *entry, *fds_json;
 
-                JSON_VARIANT_ARRAY_FOREACH(entry, entries) {
+                fds_json = sd_json_variant_by_key(unit_json, "fdstore");
+                if (!sd_json_variant_is_array(fds_json)) {
+                        log_warning("LUO mapping fdstore for unit '%s' is not a JSON array, skipping.", unit_id);
+                        continue;
+                }
+
+                JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) {
                         struct {
                                 const char *type;
                                 const char *name;
@@ -298,17 +319,30 @@ int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd)
                 }
 
                 if (fd_list) {
-                        r = sd_json_variant_set_field(&mapping, unit_id, fd_list);
+                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *unit_entry = NULL;
+
+                        r = sd_json_buildo(
+                                        &unit_entry,
+                                        SD_JSON_BUILD_PAIR_VARIANT("fdstore", fd_list));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to build LUO unit object: %m");
+
+                        r = sd_json_variant_set_field(&units, unit_id, unit_entry);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to add unit to LUO mapping: %m");
                 }
         }
 
-        if (!mapping) {
-                log_debug("No fds were preserved in LUO session.");
-                *ret_session_fd = -EBADF;
-                return 0;
-        }
+        sd_json_variant *version = sd_json_variant_by_key(serialization, "version");
+        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");
+
+        r = sd_json_buildo(
+                        &mapping,
+                        SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_VARIANT(version)),
+                        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");
 
         /* Store the mapping as a memfd at LUO token 0 */
         _cleanup_free_ char *mapping_text = NULL;
index 1ca7ca6d71235f7f1df7252bd81d767700b81999..3e820190613e623d7e2ea2da806051c0b1c99b4f 100644 (file)
@@ -6,18 +6,20 @@
 
 #define LUO_SESSION_NAME "systemd"
 
-/* Index (token) 0 in the LUO session is always the mapping memfd, which contains a JSON document mapping
- * unit ids to arrays of fd store entries:
+/* Index (token) 0 in the LUO session is always the mapping memfd, which contains a versioned JSON document
+ * with manager-level state and a "units" object mapping unit ids to per-unit objects with an "fdstore" array
+ * of fd store entries:
  *
  *   {
- *     "unit-name.service": [
- *       { "type": "fd",          "name": "fdname1", "token": 1 },
- *       { "type": "fd",          "name": "fdname2", "token": 2 },
- *       { "type": "luo_session", "name": "fdname3", "sessionName": "unit.service/myapp" }
- *     ],
- *     "other-unit.service": [
- *       { "type": "fd",          "name": "stored", "token": 3 }
- *     ]
+ *     "version": 1,
+ *     "units": {
+ *       "unit-name.service": {
+ *         "fdstore": [
+ *           { "type": "fd",          "name": "fdname1", "token": 1 },
+ *           { "type": "luo_session", "name": "fdname3", "sessionName": "unit.service/myapp" }
+ *         ]
+ *       }
+ *     }
  *   }
  *
  * type=fd:          the fd was preserved in the "systemd" LUO session with the given token.
@@ -25,6 +27,7 @@
  *                   retrieved by session_name on the next boot.
  */
 #define LUO_MAPPING_INDEX UINT64_C(0)
+#define LUO_PROTOCOL_VERSION UINT64_C(1)
 
 int luo_open_device(void);
 int luo_create_session(int device_fd, const char *name);