From: Luca Boccassi Date: Mon, 15 Jun 2026 20:33:08 +0000 (+0100) Subject: core: add version and structure to LUO json payload X-Git-Tag: v261-rc4~5^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F42607%2Fhead;p=thirdparty%2Fsystemd.git core: add version and structure to LUO json payload 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 --- diff --git a/src/core/luo.c b/src/core/luo.c index 91c3a01dad8..396135f4cff 100644 --- a/src/core/luo.c +++ b/src/core/luo.c @@ -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"); diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c index ab4a4a3235e..e6de4c25c2d 100644 --- a/src/shared/luo-util.c +++ b/src/shared/luo-util.c @@ -5,6 +5,7 @@ #include #include #include +#include #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; diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h index 1ca7ca6d712..3e820190613 100644 --- a/src/shared/luo-util.h +++ b/src/shared/luo-util.h @@ -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);