Merge patch series "Make liveupdate selftests library"

Vipin Sharma <vipinsh@google.com> says:

Refactor liveupdate selftests as a library.

The library exposes common liveupdate ioctls which can be used for
liveupdate testing in other subsystems like VFIO and IOMMU.

* patches from: https://patch.msgid.link/20260720203202.1964557-1-vipinsh@google.com
selftests/liveupdate: Use luo_test_utils.c for liveupdate ioctl APIs
selftests/liveupdate: Move luo_test_utils.* into a reusable library

Link: https://patch.msgid.link/20260720203202.1964557-1-vipinsh@google.com
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
This commit is contained in:
Mike Rapoport (Microsoft) 2026-07-26 11:29:04 +03:00
commit ccf287a39b
11 changed files with 122 additions and 107 deletions

View File

@ -3,6 +3,7 @@
!/**/
!*.c
!*.h
!*.mk
!*.sh
!.gitignore
!config

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
LIB_C += luo_test_utils.c
TEST_GEN_PROGS += liveupdate
TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
@ -12,25 +10,21 @@ TEST_GEN_PROGS_EXTENDED += luo_stress_files
TEST_FILES += do_kexec.sh
include ../lib.mk
include lib/libliveupdate.mk
CFLAGS += $(KHDR_INCLUDES)
CFLAGS += -Wall -O2 -Wno-unused-function
CFLAGS += -MD
LIB_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIB_C))
TEST_O := $(patsubst %, %.o, $(TEST_GEN_PROGS))
TEST_O += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED))
TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIB_O))
TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIBLIVEUPDATE_O))
TEST_DEP_FILES += $(patsubst %.o, %.d, $(TEST_O))
-include $(TEST_DEP_FILES)
$(LIB_O): $(OUTPUT)/%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIBLIVEUPDATE_O)
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIBLIVEUPDATE_O) $(LDLIBS) -o $@
$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIB_O)
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIB_O) $(LDLIBS) -o $@
EXTRA_CLEAN += $(LIB_O)
EXTRA_CLEAN += $(TEST_O)
EXTRA_CLEAN += $(TEST_DEP_FILES)

View File

@ -7,13 +7,13 @@
* Utility functions for LUO kselftests.
*/
#ifndef LUO_TEST_UTILS_H
#define LUO_TEST_UTILS_H
#ifndef SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H
#define SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H
#include <errno.h>
#include <string.h>
#include <linux/liveupdate.h>
#include "../kselftest.h"
#include "../../../kselftest.h"
#define LUO_DEVICE "/dev/liveupdate"
@ -25,8 +25,11 @@ int luo_open_device(void);
int luo_create_session(int luo_fd, const char *name);
int luo_retrieve_session(int luo_fd, const char *name);
int luo_session_finish(int session_fd);
int luo_get_session_name(int session_fd, char *name, size_t name_len);
int luo_ensure_nofile_limit(long min_limit);
int luo_session_preserve_fd(int session_fd, int fd, __u64 token);
int luo_session_retrieve_fd(int session_fd, __u64 token);
int create_and_preserve_memfd(int session_fd, int token, const char *data);
int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
@ -43,4 +46,4 @@ typedef void (*luo_test_stage2_fn)(int luo_fd, int state_session_fd);
int luo_test(int argc, char *argv[], const char *state_session_name,
luo_test_stage1_fn stage1, luo_test_stage2_fn stage2);
#endif /* LUO_TEST_UTILS_H */
#endif /* SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H */

View File

@ -0,0 +1,20 @@
include $(top_srcdir)/scripts/subarch.include
ARCH ?= $(SUBARCH)
LIBLIVEUPDATE_SRCDIR := $(selfdir)/liveupdate/lib
LIBLIVEUPDATE_C := lu_utils.c
LIBLIVEUPDATE_OUTPUT := $(OUTPUT)/libliveupdate
LIBLIVEUPDATE_O := $(patsubst %.c, $(LIBLIVEUPDATE_OUTPUT)/%.o, $(LIBLIVEUPDATE_C))
CFLAGS += -I$(LIBLIVEUPDATE_SRCDIR)/include
$(LIBLIVEUPDATE_OUTPUT):
$(Q)mkdir -p $@
$(LIBLIVEUPDATE_O): $(LIBLIVEUPDATE_OUTPUT)/%.o : $(LIBLIVEUPDATE_SRCDIR)/%.c | $(LIBLIVEUPDATE_OUTPUT)
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
EXTRA_CLEAN += $(LIBLIVEUPDATE_OUTPUT)

View File

@ -23,7 +23,7 @@
#include <stdarg.h>
#include <linux/unistd.h>
#include "luo_test_utils.h"
#include <libliveupdate.h>
int luo_open_device(void)
{
@ -60,7 +60,7 @@ int luo_create_session(int luo_fd, const char *name)
snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s",
LIVEUPDATE_SESSION_NAME_LENGTH - 1, name);
if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg) < 0)
if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg))
return -errno;
return arg.fd;
@ -73,15 +73,57 @@ int luo_retrieve_session(int luo_fd, const char *name)
snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s",
LIVEUPDATE_SESSION_NAME_LENGTH - 1, name);
if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg) < 0)
if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg))
return -errno;
return arg.fd;
}
int luo_session_preserve_fd(int session_fd, int fd, __u64 token)
{
struct liveupdate_session_preserve_fd arg = {
.size = sizeof(arg),
.fd = fd,
.token = token,
};
if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg))
return -errno;
return 0;
}
int luo_session_retrieve_fd(int session_fd, __u64 token)
{
struct liveupdate_session_retrieve_fd arg = {
.size = sizeof(arg),
.token = token,
};
if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg))
return -errno;
return arg.fd;
}
/* Helper function to get a session name via ioctl. */
int luo_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;
}
int create_and_preserve_memfd(int session_fd, int token, const char *data)
{
struct liveupdate_session_preserve_fd arg = { .size = sizeof(arg) };
long page_size = getpagesize();
void *map = MAP_FAILED;
int mfd = -1, ret = -1;
@ -100,9 +142,8 @@ int create_and_preserve_memfd(int session_fd, int token, const char *data)
snprintf(map, page_size, "%s", data);
munmap(map, page_size);
arg.fd = mfd;
arg.token = token;
if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg) < 0)
ret = luo_session_preserve_fd(session_fd, mfd, token);
if (ret)
goto out;
ret = 0;
@ -117,15 +158,13 @@ int create_and_preserve_memfd(int session_fd, int token, const char *data)
int restore_and_verify_memfd(int session_fd, int token,
const char *expected_data)
{
struct liveupdate_session_retrieve_fd arg = { .size = sizeof(arg) };
long page_size = getpagesize();
void *map = MAP_FAILED;
int mfd = -1, ret = -1;
arg.token = token;
if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg) < 0)
return -errno;
mfd = arg.fd;
mfd = luo_session_retrieve_fd(session_fd, token);
if (mfd < 0)
return mfd;
map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0);
if (map == MAP_FAILED)

View File

@ -24,9 +24,9 @@
#include <sys/ioctl.h>
#include <unistd.h>
#include <libliveupdate.h>
#include <linux/liveupdate.h>
#include "luo_test_utils.h"
#include "../kselftest.h"
#include "../kselftest_harness.h"
@ -89,36 +89,6 @@ TEST_F(liveupdate_device, exclusive_open)
EXPECT_EQ(errno, EBUSY);
}
/* Helper function to create a LUO session via ioctl. */
static int create_session(int lu_fd, const char *name)
{
struct liveupdate_ioctl_create_session args = {};
args.size = sizeof(args);
strncpy((char *)args.name, name, sizeof(args.name) - 1);
if (ioctl(lu_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args))
return -errno;
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
*
@ -135,10 +105,10 @@ TEST_F(liveupdate_device, create_duplicate_session)
ASSERT_GE(self->fd1, 0);
session_fd1 = create_session(self->fd1, "duplicate-session-test");
session_fd1 = luo_create_session(self->fd1, "duplicate-session-test");
ASSERT_GE(session_fd1, 0);
session_fd2 = create_session(self->fd1, "duplicate-session-test");
session_fd2 = luo_create_session(self->fd1, "duplicate-session-test");
EXPECT_LT(session_fd2, 0);
EXPECT_EQ(-session_fd2, EEXIST);
@ -160,30 +130,16 @@ TEST_F(liveupdate_device, create_distinct_sessions)
ASSERT_GE(self->fd1, 0);
session_fd1 = create_session(self->fd1, "distinct-session-1");
session_fd1 = luo_create_session(self->fd1, "distinct-session-1");
ASSERT_GE(session_fd1, 0);
session_fd2 = create_session(self->fd1, "distinct-session-2");
session_fd2 = luo_create_session(self->fd1, "distinct-session-2");
ASSERT_GE(session_fd2, 0);
ASSERT_EQ(close(session_fd1), 0);
ASSERT_EQ(close(session_fd2), 0);
}
static int preserve_fd(int session_fd, int fd_to_preserve, __u64 token)
{
struct liveupdate_session_preserve_fd args = {};
args.size = sizeof(args);
args.fd = fd_to_preserve;
args.token = token;
if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args))
return -errno;
return 0;
}
/*
* Test Case: Preserve MemFD
*
@ -201,14 +157,14 @@ TEST_F(liveupdate_device, preserve_memfd)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, "preserve-memfd-test");
session_fd = luo_create_session(self->fd1, "preserve-memfd-test");
ASSERT_GE(session_fd, 0);
mem_fd = memfd_create("test-memfd", 0);
ASSERT_GE(mem_fd, 0);
ASSERT_EQ(write(mem_fd, test_str, strlen(test_str)), strlen(test_str));
ASSERT_EQ(preserve_fd(session_fd, mem_fd, 0x1234), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd, 0x1234), 0);
ASSERT_EQ(close(session_fd), 0);
ASSERT_EQ(lseek(mem_fd, 0, SEEK_SET), 0);
@ -236,7 +192,7 @@ TEST_F(liveupdate_device, preserve_multiple_memfds)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, "preserve-multi-memfd-test");
session_fd = luo_create_session(self->fd1, "preserve-multi-memfd-test");
ASSERT_GE(session_fd, 0);
mem_fd1 = memfd_create("test-memfd-1", 0);
@ -247,8 +203,8 @@ TEST_F(liveupdate_device, preserve_multiple_memfds)
ASSERT_EQ(write(mem_fd1, test_str1, strlen(test_str1)), strlen(test_str1));
ASSERT_EQ(write(mem_fd2, test_str2, strlen(test_str2)), strlen(test_str2));
ASSERT_EQ(preserve_fd(session_fd, mem_fd1, 0xAAAA), 0);
ASSERT_EQ(preserve_fd(session_fd, mem_fd2, 0xBBBB), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd1, 0xAAAA), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd2, 0xBBBB), 0);
memset(read_buf, 0, sizeof(read_buf));
ASSERT_EQ(lseek(mem_fd1, 0, SEEK_SET), 0);
@ -284,9 +240,9 @@ TEST_F(liveupdate_device, preserve_complex_scenario)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd1 = create_session(self->fd1, "complex-session-1");
session_fd1 = luo_create_session(self->fd1, "complex-session-1");
ASSERT_GE(session_fd1, 0);
session_fd2 = create_session(self->fd1, "complex-session-2");
session_fd2 = luo_create_session(self->fd1, "complex-session-2");
ASSERT_GE(session_fd2, 0);
mem_fd_data1 = memfd_create("data1", 0);
@ -303,10 +259,10 @@ TEST_F(liveupdate_device, preserve_complex_scenario)
mem_fd_empty2 = memfd_create("empty2", 0);
ASSERT_GE(mem_fd_empty2, 0);
ASSERT_EQ(preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0);
ASSERT_EQ(preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0);
ASSERT_EQ(preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0);
ASSERT_EQ(preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0);
ASSERT_EQ(lseek(mem_fd_data1, 0, SEEK_SET), 0);
ASSERT_EQ(read(mem_fd_data1, read_buf, sizeof(read_buf)), strlen(data1));
@ -349,13 +305,13 @@ TEST_F(liveupdate_device, preserve_unsupported_fd)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, "unsupported-fd-test");
session_fd = luo_create_session(self->fd1, "unsupported-fd-test");
ASSERT_GE(session_fd, 0);
unsupported_fd = open("/dev/null", O_RDWR);
ASSERT_GE(unsupported_fd, 0);
ret = preserve_fd(session_fd, unsupported_fd, 0xDEAD);
ret = luo_session_preserve_fd(session_fd, unsupported_fd, 0xDEAD);
EXPECT_EQ(ret, -ENOENT);
ASSERT_EQ(close(unsupported_fd), 0);
@ -379,23 +335,23 @@ TEST_F(liveupdate_device, prevent_double_preservation)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd1 = create_session(self->fd1, "double-preserve-session-1");
session_fd1 = luo_create_session(self->fd1, "double-preserve-session-1");
ASSERT_GE(session_fd1, 0);
session_fd2 = create_session(self->fd1, "double-preserve-session-2");
session_fd2 = luo_create_session(self->fd1, "double-preserve-session-2");
ASSERT_GE(session_fd2, 0);
mem_fd = memfd_create("test-memfd", 0);
ASSERT_GE(mem_fd, 0);
/* First preservation should succeed */
ASSERT_EQ(preserve_fd(session_fd1, mem_fd, 0x1111), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd, 0x1111), 0);
/* Second preservation in a different session should fail with EBUSY */
ret = preserve_fd(session_fd2, mem_fd, 0x2222);
ret = luo_session_preserve_fd(session_fd2, mem_fd, 0x2222);
EXPECT_EQ(ret, -EBUSY);
/* Second preservation in the same session (different token) should fail with EBUSY */
ret = preserve_fd(session_fd1, mem_fd, 0x3333);
ret = luo_session_preserve_fd(session_fd1, mem_fd, 0x3333);
EXPECT_EQ(ret, -EBUSY);
ASSERT_EQ(close(mem_fd), 0);
@ -441,7 +397,7 @@ TEST_F(liveupdate_device, create_session_empty_name)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, "");
session_fd = luo_create_session(self->fd1, "");
EXPECT_EQ(session_fd, -EINVAL);
}
@ -462,10 +418,10 @@ TEST_F(liveupdate_device, get_session_name)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, session_name);
session_fd = luo_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_EQ(luo_get_session_name(session_fd, name_buf, sizeof(name_buf)), 0);
ASSERT_STREQ(name_buf, session_name);
ASSERT_EQ(close(session_fd), 0);
@ -491,10 +447,10 @@ TEST_F(liveupdate_device, get_session_name_max_length)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, long_name);
session_fd = luo_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_EQ(luo_get_session_name(session_fd, name_buf, sizeof(name_buf)), 0);
ASSERT_STREQ(name_buf, long_name);
ASSERT_EQ(close(session_fd), 0);
@ -528,7 +484,7 @@ TEST_F(liveupdate_device, preserve_many_sessions)
char name[64];
snprintf(name, sizeof(name), "many-session-%d", i);
session_fds[i] = create_session(self->fd1, name);
session_fds[i] = luo_create_session(self->fd1, name);
ASSERT_GE(session_fds[i], 0);
}
@ -554,7 +510,7 @@ TEST_F(liveupdate_device, preserve_many_files)
SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
ASSERT_GE(self->fd1, 0);
session_fd = create_session(self->fd1, "many-files-test");
session_fd = luo_create_session(self->fd1, "many-files-test");
ASSERT_GE(session_fd, 0);
ret = luo_ensure_nofile_limit(MANY_FILES + 10);
@ -565,7 +521,7 @@ TEST_F(liveupdate_device, preserve_many_files)
for (i = 0; i < MANY_FILES; i++) {
mem_fds[i] = memfd_create("test-memfd", 0);
ASSERT_GE(mem_fds[i], 0);
ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fds[i], i), 0);
}
for (i = 0; i < MANY_FILES; i++)

View File

@ -8,7 +8,7 @@
* across a single kexec reboot.
*/
#include "luo_test_utils.h"
#include <libliveupdate.h>
#define TEST_SESSION_NAME "test-session"
#define TEST_MEMFD_TOKEN 0x1A

View File

@ -9,7 +9,7 @@
* files.
*/
#include "luo_test_utils.h"
#include <libliveupdate.h>
#define SESSION_EMPTY_1 "multi-test-empty-1"
#define SESSION_EMPTY_2 "multi-test-empty-2"

View File

@ -10,7 +10,8 @@
#include <stdio.h>
#include <unistd.h>
#include "luo_test_utils.h"
#include <libliveupdate.h>
#define NUM_FILES 500
#define STATE_SESSION_NAME "kexec_many_files_state"

View File

@ -10,7 +10,8 @@
#include <stdio.h>
#include <unistd.h>
#include "luo_test_utils.h"
#include <libliveupdate.h>
#define NUM_SESSIONS 2000
#define STATE_SESSION_NAME "kexec_many_state"

View File

@ -131,9 +131,9 @@ function mkinitrd() {
"$CROSS_COMPILE"gcc -static -O2 -nostdinc -nostdlib \
-I "$headers_dir/include" \
-I "$kernel_dir/tools/include/nolibc" \
-I "$test_dir" \
-I "$test_dir/lib/include" \
-o "$workspace_dir/test_binary" \
"$test_dir/$test_name.c" "$test_dir/luo_test_utils.c"
"$test_dir/$test_name.c" "$test_dir/lib/lu_utils.c"
"$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \
-fno-asynchronous-unwind-tables -fno-ident \