mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-19 22:55:43 -04:00
Currently, the number of LUO sessions is limited by a fixed number of pre-allocated pages for serialization (16 pages, allowing for ~819 sessions). This limitation is problematic if LUO is used to support things such as systemd file descriptor store, and would be used not just as VM memory but to save other states on the machine. Remove this limit by transitioning to a linked-block approach for session metadata serialization. Instead of a single contiguous block, session metadata is now stored in a chain of 16-page blocks. Each block starts with a header containing the physical address of the next block and the number of session entries in the current block. Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org> Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> Link: https://patch.msgid.link/20260603154402.468928-10-pasha.tatashin@soleen.com Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
683 lines
19 KiB
C
683 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
* Copyright (c) 2025, Google LLC.
|
|
* Pasha Tatashin <pasha.tatashin@soleen.com>
|
|
*/
|
|
|
|
/**
|
|
* DOC: LUO Sessions
|
|
*
|
|
* LUO Sessions provide the core mechanism for grouping and managing `struct
|
|
* file *` instances that need to be preserved across a kexec-based live
|
|
* update. Each session acts as a named container for a set of file objects,
|
|
* allowing a userspace agent to manage the lifecycle of resources critical to a
|
|
* workload.
|
|
*
|
|
* Core Concepts:
|
|
*
|
|
* - Named Containers: Sessions are identified by a unique, user-provided name,
|
|
* which is used for both creation in the current kernel and retrieval in the
|
|
* next kernel.
|
|
*
|
|
* - Userspace Interface: Session management is driven from userspace via
|
|
* ioctls on /dev/liveupdate.
|
|
*
|
|
* - Serialization: Session metadata is preserved using the KHO framework. When
|
|
* a live update is triggered via kexec, session metadata is serialized into
|
|
* a chain of linked-blocks and placed in a preserved memory region. The
|
|
* physical address of the first block header is stored in the centralized
|
|
* `struct luo_ser` structure.
|
|
*
|
|
* Session Lifecycle:
|
|
*
|
|
* 1. Creation: A userspace agent calls `luo_session_create()` to create a
|
|
* new, empty session and receives a file descriptor for it.
|
|
*
|
|
* 2. Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is
|
|
* made, `luo_session_serialize()` is called. It iterates through all
|
|
* active sessions and writes their metadata into a memory area preserved
|
|
* by KHO.
|
|
*
|
|
* 3. Deserialization (in new kernel): After kexec, `luo_session_deserialize()`
|
|
* runs, reading the serialized data and creating a list of `struct
|
|
* luo_session` objects representing the preserved sessions.
|
|
*
|
|
* 4. Retrieval: A userspace agent in the new kernel can then call
|
|
* `luo_session_retrieve()` with a session name to get a new file
|
|
* descriptor and access the preserved state.
|
|
*
|
|
* Locking:
|
|
*
|
|
* The LUO session subsystem uses a three-tier locking hierarchy to ensure thread
|
|
* safety and prevent deadlocks during concurrent session mutations and kexec
|
|
* serialization:
|
|
*
|
|
* 1. `luo_session_serialize_rwsem` (global rwsem):
|
|
* Protects session mutations (creation, retrieval, release, and ioctls)
|
|
* against the serialization process during reboot.
|
|
*
|
|
* - Readers: Taken by any path modifying or accessing session state (e.g.,
|
|
* `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`,
|
|
* and `luo_session_ioctl()`).
|
|
* - Writer: Taken by the serialization process (`luo_session_serialize()`)
|
|
* during reboot. On success, the write lock is held indefinitely to freeze
|
|
* the subsystem. On failure, it is released to allow recovery.
|
|
*
|
|
* 2. `luo_session_header->rwsem` (per-list rwsem):
|
|
* Synchronizes list-level operations for the incoming and outgoing session headers.
|
|
*
|
|
* - Writer: Taken during list mutation operations (inserting or removing a
|
|
* session from the list).
|
|
* - Reader: Taken when traversing the list (e.g., retrieving a session by name).
|
|
*
|
|
* 3. `luo_session->mutex` (per-session mutex):
|
|
* Protects the internal state and file sets of an individual session. It is
|
|
* acquired during per-session operations such as preserving, retrieving,
|
|
* or freezing files.
|
|
*
|
|
* Lock Hierarchy:
|
|
* `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex`
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/anon_inodes.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kexec_handover.h>
|
|
#include <linux/kho_block.h>
|
|
#include <linux/kho/abi/luo.h>
|
|
#include <linux/list.h>
|
|
#include <linux/liveupdate.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/slab.h>
|
|
#include <uapi/linux/liveupdate.h>
|
|
#include "luo_internal.h"
|
|
|
|
static DECLARE_RWSEM(luo_session_serialize_rwsem);
|
|
/**
|
|
* struct luo_session_header - Header struct for managing LUO sessions.
|
|
* @count: The number of sessions currently tracked in the @list.
|
|
* @list: The head of the linked list of `struct luo_session` instances.
|
|
* @rwsem: A read-write semaphore providing synchronized access to the
|
|
* session list and other fields in this structure.
|
|
* @block_set: The set of serialization blocks.
|
|
* @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
|
|
* @active: Set to true when first initialized. If previous kernel did not
|
|
* send session data, active stays false for incoming.
|
|
*/
|
|
struct luo_session_header {
|
|
long count;
|
|
struct list_head list;
|
|
struct rw_semaphore rwsem;
|
|
struct kho_block_set block_set;
|
|
u64 *sessions_pa;
|
|
bool active;
|
|
};
|
|
|
|
/**
|
|
* struct luo_session_global - Global container for managing LUO sessions.
|
|
* @incoming: The sessions passed from the previous kernel.
|
|
* @outgoing: The sessions that are going to be passed to the next kernel.
|
|
*/
|
|
struct luo_session_global {
|
|
struct luo_session_header incoming;
|
|
struct luo_session_header outgoing;
|
|
};
|
|
|
|
static struct luo_session_global luo_session_global = {
|
|
.incoming = {
|
|
.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
|
|
.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
|
|
.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
|
|
sizeof(struct luo_session_ser)),
|
|
},
|
|
.outgoing = {
|
|
.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
|
|
.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
|
|
.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
|
|
sizeof(struct luo_session_ser)),
|
|
},
|
|
};
|
|
|
|
static struct luo_session *luo_session_alloc(const char *name)
|
|
{
|
|
struct luo_session *session = kzalloc_obj(*session);
|
|
|
|
if (!session)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
strscpy(session->name, name, sizeof(session->name));
|
|
INIT_LIST_HEAD(&session->file_set.files_list);
|
|
luo_file_set_init(&session->file_set);
|
|
INIT_LIST_HEAD(&session->list);
|
|
mutex_init(&session->mutex);
|
|
|
|
return session;
|
|
}
|
|
|
|
static void luo_session_free(struct luo_session *session)
|
|
{
|
|
luo_file_set_destroy(&session->file_set);
|
|
mutex_destroy(&session->mutex);
|
|
kfree(session);
|
|
}
|
|
|
|
static int luo_session_insert(struct luo_session_header *sh,
|
|
struct luo_session *session)
|
|
{
|
|
struct luo_session *it;
|
|
int err;
|
|
|
|
guard(rwsem_write)(&sh->rwsem);
|
|
|
|
/*
|
|
* For outgoing we should make sure there is room in serialization array
|
|
* for new session.
|
|
*/
|
|
if (sh == &luo_session_global.outgoing) {
|
|
err = kho_block_set_grow(&sh->block_set, sh->count + 1);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* For small number of sessions this loop won't hurt performance
|
|
* but if we ever start using a lot of sessions, this might
|
|
* become a bottle neck during deserialization time, as it would
|
|
* cause O(n*n) complexity.
|
|
*/
|
|
list_for_each_entry(it, &sh->list, list) {
|
|
if (!strncmp(it->name, session->name, sizeof(it->name)))
|
|
return -EEXIST;
|
|
}
|
|
list_add_tail(&session->list, &sh->list);
|
|
sh->count++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void luo_session_remove(struct luo_session_header *sh,
|
|
struct luo_session *session)
|
|
{
|
|
guard(rwsem_write)(&sh->rwsem);
|
|
list_del(&session->list);
|
|
sh->count--;
|
|
if (sh == &luo_session_global.outgoing)
|
|
kho_block_set_shrink(&sh->block_set, sh->count);
|
|
}
|
|
|
|
static int luo_session_finish_one(struct luo_session *session)
|
|
{
|
|
guard(mutex)(&session->mutex);
|
|
return luo_file_finish(&session->file_set);
|
|
}
|
|
|
|
static void luo_session_unfreeze_one(struct luo_session *session,
|
|
struct luo_session_ser *ser)
|
|
{
|
|
guard(mutex)(&session->mutex);
|
|
luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
|
|
}
|
|
|
|
static int luo_session_freeze_one(struct luo_session *session,
|
|
struct luo_session_ser *ser)
|
|
{
|
|
guard(mutex)(&session->mutex);
|
|
return luo_file_freeze(&session->file_set, &ser->file_set_ser);
|
|
}
|
|
|
|
static int luo_session_release(struct inode *inodep, struct file *filep)
|
|
{
|
|
struct luo_session *session = filep->private_data;
|
|
struct luo_session_header *sh;
|
|
|
|
guard(rwsem_read)(&luo_session_serialize_rwsem);
|
|
/* If retrieved is set, it means this session is from incoming list */
|
|
if (session->retrieved) {
|
|
int err = luo_session_finish_one(session);
|
|
|
|
if (err) {
|
|
pr_warn("Unable to finish session [%s] on release\n",
|
|
session->name);
|
|
return err;
|
|
}
|
|
sh = &luo_session_global.incoming;
|
|
} else {
|
|
scoped_guard(mutex, &session->mutex)
|
|
luo_file_unpreserve_files(&session->file_set);
|
|
sh = &luo_session_global.outgoing;
|
|
}
|
|
|
|
luo_session_remove(sh, session);
|
|
luo_session_free(session);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int luo_session_preserve_fd(struct luo_session *session,
|
|
struct luo_ucmd *ucmd)
|
|
{
|
|
struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
|
|
int err;
|
|
|
|
guard(mutex)(&session->mutex);
|
|
err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
|
|
if (err)
|
|
return err;
|
|
|
|
err = luo_ucmd_respond(ucmd, sizeof(*argp));
|
|
if (err)
|
|
pr_warn("The file was successfully preserved, but response to user failed\n");
|
|
|
|
return err;
|
|
}
|
|
|
|
static int luo_session_retrieve_fd(struct luo_session *session,
|
|
struct luo_ucmd *ucmd)
|
|
{
|
|
struct liveupdate_session_retrieve_fd *argp = ucmd->cmd;
|
|
struct file *file;
|
|
int err;
|
|
|
|
argp->fd = get_unused_fd_flags(O_CLOEXEC);
|
|
if (argp->fd < 0)
|
|
return argp->fd;
|
|
|
|
mutex_lock(&session->mutex);
|
|
err = luo_retrieve_file(&session->file_set, argp->token, &file);
|
|
mutex_unlock(&session->mutex);
|
|
if (err < 0)
|
|
goto err_put_fd;
|
|
|
|
err = luo_ucmd_respond(ucmd, sizeof(*argp));
|
|
if (err)
|
|
goto err_put_file;
|
|
|
|
fd_install(argp->fd, file);
|
|
|
|
return 0;
|
|
|
|
err_put_file:
|
|
fput(file);
|
|
err_put_fd:
|
|
put_unused_fd(argp->fd);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int luo_session_finish(struct luo_session *session,
|
|
struct luo_ucmd *ucmd)
|
|
{
|
|
struct liveupdate_session_finish *argp = ucmd->cmd;
|
|
int err = luo_session_finish_one(session);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
return luo_ucmd_respond(ucmd, sizeof(*argp));
|
|
}
|
|
|
|
static int luo_session_get_name(struct luo_session *session,
|
|
struct luo_ucmd *ucmd)
|
|
{
|
|
struct liveupdate_session_get_name *argp = ucmd->cmd;
|
|
|
|
if (argp->reserved != 0)
|
|
return -EINVAL;
|
|
|
|
strscpy((char *)argp->name, session->name, sizeof(argp->name));
|
|
|
|
return luo_ucmd_respond(ucmd, sizeof(*argp));
|
|
}
|
|
|
|
union ucmd_buffer {
|
|
struct liveupdate_session_finish finish;
|
|
struct liveupdate_session_preserve_fd preserve;
|
|
struct liveupdate_session_retrieve_fd retrieve;
|
|
struct liveupdate_session_get_name get_name;
|
|
};
|
|
|
|
/* Type of sessions the ioctl applies to. */
|
|
enum luo_ioctl_type {
|
|
LUO_IOCTL_INCOMING,
|
|
LUO_IOCTL_OUTGOING,
|
|
LUO_IOCTL_ALL,
|
|
};
|
|
|
|
struct luo_ioctl_op {
|
|
unsigned int size;
|
|
unsigned int min_size;
|
|
unsigned int ioctl_num;
|
|
enum luo_ioctl_type type;
|
|
int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
|
|
};
|
|
|
|
#define IOCTL_OP(_ioctl, _fn, _struct, _last, _type) \
|
|
[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = { \
|
|
.size = sizeof(_struct) + \
|
|
BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \
|
|
sizeof(_struct)), \
|
|
.min_size = offsetofend(_struct, _last), \
|
|
.ioctl_num = _ioctl, \
|
|
.type = _type, \
|
|
.execute = _fn, \
|
|
}
|
|
|
|
static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
|
|
IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
|
|
struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
|
|
IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
|
|
struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
|
|
IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
|
|
struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
|
|
IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
|
|
struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_ALL),
|
|
};
|
|
|
|
static bool luo_ioctl_type_valid(struct luo_session *session,
|
|
const struct luo_ioctl_op *op)
|
|
{
|
|
switch (op->type) {
|
|
case LUO_IOCTL_INCOMING:
|
|
/* Retrieved is only set on incoming sessions */
|
|
return session->retrieved;
|
|
case LUO_IOCTL_OUTGOING:
|
|
return !session->retrieved;
|
|
case LUO_IOCTL_ALL:
|
|
return true;
|
|
}
|
|
|
|
/* Catch-all. */
|
|
return false;
|
|
}
|
|
|
|
static long luo_session_ioctl(struct file *filep, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct luo_session *session = filep->private_data;
|
|
const struct luo_ioctl_op *op;
|
|
struct luo_ucmd ucmd = {};
|
|
union ucmd_buffer buf;
|
|
unsigned int nr;
|
|
int ret;
|
|
|
|
nr = _IOC_NR(cmd);
|
|
if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >=
|
|
ARRAY_SIZE(luo_session_ioctl_ops)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ucmd.ubuffer = (void __user *)arg;
|
|
ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
|
|
if (op->ioctl_num != cmd)
|
|
return -ENOIOCTLCMD;
|
|
if (!luo_ioctl_type_valid(session, op))
|
|
return -EINVAL;
|
|
if (ucmd.user_size < op->min_size)
|
|
return -EINVAL;
|
|
|
|
ucmd.cmd = &buf;
|
|
ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
|
|
ucmd.user_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
guard(rwsem_read)(&luo_session_serialize_rwsem);
|
|
return op->execute(session, &ucmd);
|
|
}
|
|
|
|
static const struct file_operations luo_session_fops = {
|
|
.owner = THIS_MODULE,
|
|
.release = luo_session_release,
|
|
.unlocked_ioctl = luo_session_ioctl,
|
|
};
|
|
|
|
/* Create a "struct file" for session */
|
|
static int luo_session_getfile(struct luo_session *session, struct file **filep)
|
|
{
|
|
char name_buf[128];
|
|
struct file *file;
|
|
|
|
lockdep_assert_held(&session->mutex);
|
|
snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
|
|
file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
|
|
if (IS_ERR(file))
|
|
return PTR_ERR(file);
|
|
|
|
*filep = file;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int luo_session_create(const char *name, struct file **filep)
|
|
{
|
|
size_t len = strnlen(name, LIVEUPDATE_SESSION_NAME_LENGTH);
|
|
struct luo_session *session;
|
|
int err;
|
|
|
|
if (len == 0 || len > LIVEUPDATE_SESSION_NAME_LENGTH - 1)
|
|
return -EINVAL;
|
|
|
|
session = luo_session_alloc(name);
|
|
if (IS_ERR(session))
|
|
return PTR_ERR(session);
|
|
|
|
down_read(&luo_session_serialize_rwsem);
|
|
err = luo_session_insert(&luo_session_global.outgoing, session);
|
|
if (err)
|
|
goto err_free;
|
|
|
|
mutex_lock(&session->mutex);
|
|
err = luo_session_getfile(session, filep);
|
|
mutex_unlock(&session->mutex);
|
|
if (err)
|
|
goto err_remove;
|
|
up_read(&luo_session_serialize_rwsem);
|
|
|
|
return 0;
|
|
|
|
err_remove:
|
|
luo_session_remove(&luo_session_global.outgoing, session);
|
|
err_free:
|
|
luo_session_free(session);
|
|
up_read(&luo_session_serialize_rwsem);
|
|
|
|
return err;
|
|
}
|
|
|
|
int luo_session_retrieve(const char *name, struct file **filep)
|
|
{
|
|
struct luo_session_header *sh = &luo_session_global.incoming;
|
|
struct luo_session *session = NULL;
|
|
struct luo_session *it;
|
|
int err;
|
|
|
|
guard(rwsem_read)(&luo_session_serialize_rwsem);
|
|
guard(rwsem_read)(&sh->rwsem);
|
|
list_for_each_entry(it, &sh->list, list) {
|
|
if (!strncmp(it->name, name, sizeof(it->name))) {
|
|
session = it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!session)
|
|
return -ENOENT;
|
|
|
|
guard(mutex)(&session->mutex);
|
|
if (session->retrieved)
|
|
return -EINVAL;
|
|
|
|
err = luo_session_getfile(session, filep);
|
|
if (!err)
|
|
session->retrieved = true;
|
|
|
|
return err;
|
|
}
|
|
|
|
void __init luo_session_setup_outgoing(u64 *sessions_pa)
|
|
{
|
|
luo_session_global.outgoing.sessions_pa = sessions_pa;
|
|
luo_session_global.outgoing.active = true;
|
|
}
|
|
|
|
int __init luo_session_setup_incoming(u64 sessions_pa)
|
|
{
|
|
struct luo_session_header *sh = &luo_session_global.incoming;
|
|
int err;
|
|
|
|
if (!sessions_pa)
|
|
return 0;
|
|
|
|
err = kho_block_set_restore(&sh->block_set, sessions_pa);
|
|
if (err)
|
|
return err;
|
|
|
|
sh->active = true;
|
|
return 0;
|
|
}
|
|
|
|
static int luo_session_deserialize_one(struct luo_session_header *sh,
|
|
struct luo_session_ser *ser)
|
|
{
|
|
struct luo_session *session;
|
|
int err;
|
|
|
|
session = luo_session_alloc(ser->name);
|
|
if (IS_ERR(session)) {
|
|
pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
|
|
(int)sizeof(ser->name), ser->name, session);
|
|
return PTR_ERR(session);
|
|
}
|
|
|
|
err = luo_session_insert(sh, session);
|
|
if (err) {
|
|
pr_warn("Failed to insert session [%s] %pe\n",
|
|
session->name, ERR_PTR(err));
|
|
luo_session_free(session);
|
|
return err;
|
|
}
|
|
|
|
scoped_guard(mutex, &session->mutex) {
|
|
err = luo_file_deserialize(&session->file_set,
|
|
&ser->file_set_ser);
|
|
}
|
|
if (err) {
|
|
pr_warn("Failed to deserialize files for session [%s] %pe\n",
|
|
session->name, ERR_PTR(err));
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int luo_session_deserialize(void)
|
|
{
|
|
struct luo_session_header *sh = &luo_session_global.incoming;
|
|
static bool is_deserialized;
|
|
struct luo_session_ser *ser;
|
|
struct kho_block_set_it it;
|
|
static int saved_err;
|
|
int err;
|
|
|
|
/* If has been deserialized, always return the same error code */
|
|
if (is_deserialized)
|
|
return saved_err;
|
|
|
|
is_deserialized = true;
|
|
if (!sh->active)
|
|
return 0;
|
|
|
|
/*
|
|
* Note on error handling:
|
|
*
|
|
* If deserialization fails (e.g., allocation failure or corrupt data),
|
|
* we intentionally skip cleanup of sessions that were already restored.
|
|
*
|
|
* A partial failure leaves the preserved state inconsistent.
|
|
* Implementing a safe "undo" to unwind complex dependencies (sessions,
|
|
* files, hardware state) is error-prone and provides little value, as
|
|
* the system is effectively in a broken state.
|
|
*
|
|
* We treat these resources as leaked. The expected recovery path is for
|
|
* userspace to detect the failure and trigger a reboot, which will
|
|
* reliably reset devices and reclaim memory.
|
|
*/
|
|
kho_block_set_it_init(&it, &sh->block_set);
|
|
while ((ser = kho_block_set_it_read_entry(&it))) {
|
|
err = luo_session_deserialize_one(sh, ser);
|
|
if (err)
|
|
goto save_err;
|
|
}
|
|
|
|
kho_block_set_destroy(&sh->block_set);
|
|
|
|
return 0;
|
|
|
|
save_err:
|
|
kho_block_set_destroy(&sh->block_set);
|
|
saved_err = err;
|
|
return err;
|
|
}
|
|
|
|
int luo_session_serialize(void)
|
|
{
|
|
struct luo_session_header *sh = &luo_session_global.outgoing;
|
|
struct luo_session *session;
|
|
struct kho_block_set_it it;
|
|
int err;
|
|
|
|
down_write(&luo_session_serialize_rwsem);
|
|
down_write(&sh->rwsem);
|
|
*sh->sessions_pa = 0;
|
|
|
|
kho_block_set_it_init(&it, &sh->block_set);
|
|
|
|
list_for_each_entry(session, &sh->list, list) {
|
|
struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
|
|
|
|
/* This should not fail normally as blocks were pre-allocated */
|
|
if (WARN_ON_ONCE(!ser)) {
|
|
err = -ENOSPC;
|
|
goto err_undo;
|
|
}
|
|
|
|
err = luo_session_freeze_one(session, ser);
|
|
if (err) {
|
|
kho_block_set_it_prev(&it);
|
|
goto err_undo;
|
|
}
|
|
|
|
strscpy(ser->name, session->name, sizeof(ser->name));
|
|
}
|
|
|
|
if (sh->count > 0)
|
|
*sh->sessions_pa = kho_block_set_head_pa(&sh->block_set);
|
|
up_write(&sh->rwsem);
|
|
|
|
return 0;
|
|
|
|
err_undo:
|
|
list_for_each_entry_continue_reverse(session, &sh->list, list) {
|
|
struct luo_session_ser *ser = kho_block_set_it_prev(&it);
|
|
|
|
luo_session_unfreeze_one(session, ser);
|
|
memset(ser->name, 0, sizeof(ser->name));
|
|
}
|
|
up_write(&sh->rwsem);
|
|
up_write(&luo_session_serialize_rwsem);
|
|
|
|
return err;
|
|
}
|