LUO: only deserialize on boot, add version and structure to JSON payload (#42607)

This commit is contained in:
Luca Boccassi
2026-06-16 17:55:56 +01:00
committed by GitHub
4 changed files with 116 additions and 38 deletions
+47 -8
View 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");
+4 -2
View File
@@ -2194,8 +2194,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, Hashmap *named_
m->soft_reboots_count++;
/* If a LUO (Live Update Orchestrator) session from a previous kexec is available, restore
* preserved file descriptors into the appropriate service fd stores now, before coldplug. */
(void) manager_luo_restore_fd_stores(m);
* preserved file descriptors into the appropriate service fd stores now, before coldplug.
* Only do this when booting up (i.e., skip on reexec/soft reboot/etc.) */
if (m->previous_objective < 0)
(void) manager_luo_restore_fd_stores(m);
/* Pick up fds passed via the LISTEN_FDS=/LISTEN_FDNAMES= protocol that are tagged with a
* unit id ("unit-id|fdname"), and route them into the matching unit's fd store. Untagged
+52 -18
View 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_entries, root) {
sd_json_variant *entry;
JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, units) {
sd_json_variant *entry, *fdstore;
JSON_VARIANT_ARRAY_FOREACH(entry, unit_entries) {
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, 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) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *fd_list = NULL;
sd_json_variant *entry;
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_ARRAY_FOREACH(entry, entries) {
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, *fds_json;
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;
+13 -10
View 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);