* [PATCH v5 1/4] liveupdate: add LUO_SESSION_MAGIC magic inode type
@ 2026-04-17 22:05 luca.boccassi
2026-04-17 22:05 ` [PATCH v5 2/4] selftests/liveupdate: add test case for LUO_SESSION_MAGIC luca.boccassi
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: luca.boccassi @ 2026-04-17 22:05 UTC (permalink / raw)
To: kexec
Cc: linux-mm, graf, rppt, pasha.tatashin, pratyush, brauner,
linux-kernel, Luca Boccassi
From: Luca Boccassi <luca.boccassi@gmail.com>
In userspace when managing LUO sessions we want to be able to identify
a FD as a LUO session, in order to be able to do the special handling
that they require in order to function as intended on kexec.
Currently this requires scraping procfs and doing string matching on
the prefix of the dname, which is not an ideal interface.
Add a singleton inode type with a magic value, so that we can
programmatically identify a fd as a LUO session via fstat().
Signed-off-by: Luca Boccassi <luca.boccassi@gmail.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
This was requested by Lennart and Christian when discussing integration
with systemd for LUO management.
v2: apply one fix from bot review about cleanup on error path:
https://sashiko.dev/#/patchset/20260415184536.1155220-1-luca.boccassi%40gmail.com
the other comments are invalid: luo is not a kmod, and the write hooks are not set up
v3: add test case to liveupdate selftest
v4: split test case in separate follow-up patch
v5: add r-b tag, merge series with LIVEUPDATE_SESSION_GET_NAME ioctl
as they both change the same unit test source file, to avoid merge conflicts
include/uapi/linux/magic.h | 1 +
kernel/liveupdate/luo_core.c | 10 ++++-
kernel/liveupdate/luo_internal.h | 2 +
kernel/liveupdate/luo_session.c | 73 +++++++++++++++++++++++++++++---
4 files changed, 80 insertions(+), 6 deletions(-)
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index 4f2da935a76cc..4f51005522ffe 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -105,5 +105,6 @@
#define PID_FS_MAGIC 0x50494446 /* "PIDF" */
#define GUEST_MEMFD_MAGIC 0x474d454d /* "GMEM" */
#define NULL_FS_MAGIC 0x4E554C4C /* "NULL" */
+#define LUO_SESSION_MAGIC 0x4c554f53 /* "LUOS" */
#endif /* __LINUX_MAGIC_H__ */
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index dda7bb57d421c..f1a63ebe4fa44 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -197,9 +197,17 @@ static int __init luo_late_startup(void)
if (!liveupdate_enabled())
return 0;
+ err = luo_session_fs_init();
+ if (err) {
+ luo_global.enabled = false;
+ return err;
+ }
+
err = luo_fdt_setup();
- if (err)
+ if (err) {
+ luo_session_fs_cleanup();
luo_global.enabled = false;
+ }
return err;
}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 8083d8739b093..d4ac7b4c58829 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,6 +79,8 @@ struct luo_session {
int luo_session_create(const char *name, struct file **filep);
int luo_session_retrieve(const char *name, struct file **filep);
+int __init luo_session_fs_init(void);
+void __init luo_session_fs_cleanup(void);
int __init luo_session_setup_outgoing(void *fdt);
int __init luo_session_setup_incoming(void *fdt);
int luo_session_serialize(void);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 25ae704d77873..5e315cc0dfd52 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -50,7 +50,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/anon_inodes.h>
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/errno.h>
@@ -62,7 +61,10 @@
#include <linux/libfdt.h>
#include <linux/list.h>
#include <linux/liveupdate.h>
+#include <linux/magic.h>
+#include <linux/mount.h>
#include <linux/mutex.h>
+#include <linux/pseudo_fs.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
@@ -363,18 +365,58 @@ static const struct file_operations luo_session_fops = {
.unlocked_ioctl = luo_session_ioctl,
};
+static struct vfsmount *luo_session_mnt __ro_after_init;
+static struct inode *luo_session_inode __ro_after_init;
+
+static char *luo_session_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+ return dynamic_dname(buffer, buflen, "luo_session:%s",
+ dentry->d_name.name);
+}
+
+static const struct dentry_operations luo_session_dentry_operations = {
+ .d_dname = luo_session_dname,
+};
+
+static int luo_session_init_fs_context(struct fs_context *fc)
+{
+ struct pseudo_fs_context *ctx;
+
+ ctx = init_pseudo(fc, LUO_SESSION_MAGIC);
+ if (!ctx)
+ return -ENOMEM;
+
+ fc->s_iflags |= SB_I_NOEXEC;
+ fc->s_iflags |= SB_I_NODEV;
+ ctx->dops = &luo_session_dentry_operations;
+ return 0;
+}
+
+static struct file_system_type luo_session_fs_type = {
+ .name = "luo_session",
+ .init_fs_context = luo_session_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
/* Create a "struct file" for session */
static int luo_session_getfile(struct luo_session *session, struct file **filep)
{
- char name_buf[128];
+ char name_buf[LIVEUPDATE_SESSION_NAME_LENGTH + 1];
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))
+
+ ihold(luo_session_inode);
+
+ snprintf(name_buf, sizeof(name_buf), "%s", session->name);
+ file = alloc_file_pseudo(luo_session_inode, luo_session_mnt, name_buf,
+ O_RDWR, &luo_session_fops);
+ if (IS_ERR(file)) {
+ iput(luo_session_inode);
return PTR_ERR(file);
+ }
+ file->private_data = session;
*filep = file;
return 0;
@@ -649,3 +691,24 @@ void luo_session_resume(void)
up_write(&luo_session_global.outgoing.rwsem);
up_write(&luo_session_global.incoming.rwsem);
}
+
+int __init luo_session_fs_init(void)
+{
+ luo_session_mnt = kern_mount(&luo_session_fs_type);
+ if (IS_ERR(luo_session_mnt))
+ return PTR_ERR(luo_session_mnt);
+
+ luo_session_inode = alloc_anon_inode(luo_session_mnt->mnt_sb);
+ if (IS_ERR(luo_session_inode)) {
+ kern_unmount(luo_session_mnt);
+ return PTR_ERR(luo_session_inode);
+ }
+
+ return 0;
+}
+
+void __init luo_session_fs_cleanup(void)
+{
+ iput(luo_session_inode);
+ kern_unmount(luo_session_mnt);
+}
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v5 2/4] selftests/liveupdate: add test case for LUO_SESSION_MAGIC
2026-04-17 22:05 [PATCH v5 1/4] liveupdate: add LUO_SESSION_MAGIC magic inode type luca.boccassi
@ 2026-04-17 22:05 ` luca.boccassi
2026-04-17 22:05 ` [PATCH v5 3/4] liveupdate: add LIVEUPDATE_SESSION_GET_NAME ioctl luca.boccassi
2026-04-17 22:05 ` [PATCH v5 4/4] selftests/liveupdate: add test cases for LIVEUPDATE_SESSION_GET_NAME luca.boccassi
2 siblings, 0 replies; 4+ messages in thread
From: luca.boccassi @ 2026-04-17 22:05 UTC (permalink / raw)
To: kexec
Cc: linux-mm, graf, rppt, pasha.tatashin, pratyush, brauner,
linux-kernel, Luca Boccassi
From: Luca Boccassi <luca.boccassi@gmail.com>
Verify that fstat works as expected after the switch from anon_inode
to the new magic number.
Signed-off-by: Luca Boccassi <luca.boccassi@gmail.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
v4: split test case in separate patch, reorder variable declarations
v5: add r-b tag, merge series with LIVEUPDATE_SESSION_GET_NAME ioctl
as they both change the same unit test source file, to avoid merge conflicts
.../testing/selftests/liveupdate/liveupdate.c | 40 +++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index c2878e3d5ef90..d132b4685f767 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -22,9 +22,12 @@
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
#include <unistd.h>
#include <linux/liveupdate.h>
+#include <linux/magic.h>
#include "../kselftest.h"
#include "../kselftest_harness.h"
@@ -345,4 +348,41 @@ TEST_F(liveupdate_device, preserve_unsupported_fd)
ASSERT_EQ(close(session_fd), 0);
}
+/*
+ * Test Case: Session fstat
+ *
+ * Verifies that fstatfs() on a session file descriptor reports the
+ * LUO_SESSION_MAGIC filesystem type, and that fstat() returns consistent
+ * inode numbers across different sessions (shared singleton inode).
+ */
+TEST_F(liveupdate_device, session_fstat)
+{
+ int session_fd1, session_fd2;
+ struct stat st1, st2;
+ struct statfs sfs;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd1 = create_session(self->fd1, "fstat-session-1");
+ ASSERT_GE(session_fd1, 0);
+
+ session_fd2 = create_session(self->fd1, "fstat-session-2");
+ ASSERT_GE(session_fd2, 0);
+
+ /* Verify the filesystem type is LUO_SESSION_MAGIC */
+ ASSERT_EQ(fstatfs(session_fd1, &sfs), 0);
+ EXPECT_EQ(sfs.f_type, LUO_SESSION_MAGIC);
+
+ /* Verify both sessions share the same inode number */
+ ASSERT_EQ(fstat(session_fd1, &st1), 0);
+ ASSERT_EQ(fstat(session_fd2, &st2), 0);
+ EXPECT_EQ(st1.st_ino, st2.st_ino);
+
+ ASSERT_EQ(close(session_fd1), 0);
+ ASSERT_EQ(close(session_fd2), 0);
+}
+
TEST_HARNESS_MAIN
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v5 3/4] liveupdate: add LIVEUPDATE_SESSION_GET_NAME ioctl
2026-04-17 22:05 [PATCH v5 1/4] liveupdate: add LUO_SESSION_MAGIC magic inode type luca.boccassi
2026-04-17 22:05 ` [PATCH v5 2/4] selftests/liveupdate: add test case for LUO_SESSION_MAGIC luca.boccassi
@ 2026-04-17 22:05 ` luca.boccassi
2026-04-17 22:05 ` [PATCH v5 4/4] selftests/liveupdate: add test cases for LIVEUPDATE_SESSION_GET_NAME luca.boccassi
2 siblings, 0 replies; 4+ messages in thread
From: luca.boccassi @ 2026-04-17 22:05 UTC (permalink / raw)
To: kexec
Cc: linux-mm, graf, rppt, pasha.tatashin, pratyush, brauner,
linux-kernel, Luca Boccassi
From: Luca Boccassi <luca.boccassi@gmail.com>
Userspace when requesting a session via the ioctl specifies a name and
gets a FD, but then there is no ioctl to go back the other way and get
the name given a LUO session FD. This is problematic especially when
there is a userspace orchestrator that wants to check what FDs it is
handling for clients without having to do manual string scraping of
procfs, or without procfs at all.
Add a ioctl to simply get the name from an FD.
Signed-off-by: Luca Boccassi <luca.boccassi@gmail.com>
---
v5: merge with LUO_SESSION_MAGIC series as they both change the same
unit test file, to avoid merge conflicts
add '__u32 reserved' to the UAPI struct
include/uapi/linux/liveupdate.h | 20 ++++++++++++++++++++
kernel/liveupdate/luo_session.c | 13 +++++++++++++
2 files changed, 33 insertions(+)
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 30bc66ee9436a..079ee6afb15a7 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -59,6 +59,7 @@ enum {
LIVEUPDATE_CMD_SESSION_PRESERVE_FD = LIVEUPDATE_CMD_SESSION_BASE,
LIVEUPDATE_CMD_SESSION_RETRIEVE_FD = 0x41,
LIVEUPDATE_CMD_SESSION_FINISH = 0x42,
+ LIVEUPDATE_CMD_SESSION_GET_NAME = 0x43,
};
/**
@@ -213,4 +214,23 @@ struct liveupdate_session_finish {
#define LIVEUPDATE_SESSION_FINISH \
_IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FINISH)
+/**
+ * struct liveupdate_session_get_name - ioctl(LIVEUPDATE_SESSION_GET_NAME)
+ * @size: Input; sizeof(struct liveupdate_session_get_name)
+ * @name: Output; A null-terminated string with the full session name.
+ *
+ * Retrieves the full name of the session associated with this file descriptor.
+ * This is useful because the kernel may truncate the name shown in /proc.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+struct liveupdate_session_get_name {
+ __u32 size;
+ __u32 reserved;
+ __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH];
+};
+
+#define LIVEUPDATE_SESSION_GET_NAME \
+ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_GET_NAME)
+
#endif /* _UAPI_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 5e315cc0dfd52..f30151c37bd43 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -291,10 +291,21 @@ static int luo_session_finish(struct luo_session *session,
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;
+
+ 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;
};
struct luo_ioctl_op {
@@ -321,6 +332,8 @@ static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
struct liveupdate_session_preserve_fd, token),
IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
struct liveupdate_session_retrieve_fd, token),
+ IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
+ struct liveupdate_session_get_name, name),
};
static long luo_session_ioctl(struct file *filep, unsigned int cmd,
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v5 4/4] selftests/liveupdate: add test cases for LIVEUPDATE_SESSION_GET_NAME
2026-04-17 22:05 [PATCH v5 1/4] liveupdate: add LUO_SESSION_MAGIC magic inode type luca.boccassi
2026-04-17 22:05 ` [PATCH v5 2/4] selftests/liveupdate: add test case for LUO_SESSION_MAGIC luca.boccassi
2026-04-17 22:05 ` [PATCH v5 3/4] liveupdate: add LIVEUPDATE_SESSION_GET_NAME ioctl luca.boccassi
@ 2026-04-17 22:05 ` luca.boccassi
2 siblings, 0 replies; 4+ messages in thread
From: luca.boccassi @ 2026-04-17 22:05 UTC (permalink / raw)
To: kexec
Cc: linux-mm, graf, rppt, pasha.tatashin, pratyush, brauner,
linux-kernel, Luca Boccassi
From: Luca Boccassi <luca.boccassi@gmail.com>
Signed-off-by: Luca Boccassi <luca.boccassi@gmail.com>
---
v5: merge with LUO_SESSION_MAGIC series as they both change the same
unit test file, to avoid merge conflicts
split into separate patch
.../testing/selftests/liveupdate/liveupdate.c | 71 +++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index d132b4685f767..f81201b363cc1 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -105,6 +105,22 @@ static int create_session(int lu_fd, const char *name)
return args.fd;
}
+/* Helper function to get a session name via ioctl. */
+static int get_session_name(int session_fd, char *name, size_t name_len)
+{
+ struct liveupdate_session_get_name args = {};
+
+ args.size = sizeof(args);
+
+ if (ioctl(session_fd, LIVEUPDATE_SESSION_GET_NAME, &args))
+ return -errno;
+
+ strncpy(name, (char *)args.name, name_len - 1);
+ name[name_len - 1] = '\0';
+
+ return 0;
+}
+
/*
* Test Case: Create Duplicate Session
*
@@ -385,4 +401,59 @@ TEST_F(liveupdate_device, session_fstat)
ASSERT_EQ(close(session_fd2), 0);
}
+/*
+ * Test Case: Get Session Name
+ *
+ * Verifies that the full session name can be retrieved from a session file
+ * descriptor via ioctl.
+ */
+TEST_F(liveupdate_device, get_session_name)
+{
+ char name_buf[LIVEUPDATE_SESSION_NAME_LENGTH] = {};
+ const char *session_name = "get-name-test-session";
+ int session_fd;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = create_session(self->fd1, session_name);
+ ASSERT_GE(session_fd, 0);
+
+ ASSERT_EQ(get_session_name(session_fd, name_buf, sizeof(name_buf)), 0);
+ ASSERT_STREQ(name_buf, session_name);
+
+ ASSERT_EQ(close(session_fd), 0);
+}
+
+/*
+ * Test Case: Get Session Name at Maximum Length
+ *
+ * Verifies that a session name using the full LIVEUPDATE_SESSION_NAME_LENGTH
+ * (minus the null terminator) can be correctly retrieved.
+ */
+TEST_F(liveupdate_device, get_session_name_max_length)
+{
+ char name_buf[LIVEUPDATE_SESSION_NAME_LENGTH] = {};
+ char long_name[LIVEUPDATE_SESSION_NAME_LENGTH];
+ int session_fd;
+
+ memset(long_name, 'A', sizeof(long_name) - 1);
+ long_name[sizeof(long_name) - 1] = '\0';
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = create_session(self->fd1, long_name);
+ ASSERT_GE(session_fd, 0);
+
+ ASSERT_EQ(get_session_name(session_fd, name_buf, sizeof(name_buf)), 0);
+ ASSERT_STREQ(name_buf, long_name);
+
+ ASSERT_EQ(close(session_fd), 0);
+}
+
TEST_HARNESS_MAIN
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-17 22:08 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-17 22:05 [PATCH v5 1/4] liveupdate: add LUO_SESSION_MAGIC magic inode type luca.boccassi
2026-04-17 22:05 ` [PATCH v5 2/4] selftests/liveupdate: add test case for LUO_SESSION_MAGIC luca.boccassi
2026-04-17 22:05 ` [PATCH v5 3/4] liveupdate: add LIVEUPDATE_SESSION_GET_NAME ioctl luca.boccassi
2026-04-17 22:05 ` [PATCH v5 4/4] selftests/liveupdate: add test cases for LIVEUPDATE_SESSION_GET_NAME luca.boccassi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox