Files
linux-stable-mirror/tools/testing/selftests/liveupdate/luo_test_utils.c
T
Pasha TatashinandMike Rapoport (Microsoft) 5ba3f30643 selftests/liveupdate: Test session and file limit removal
With the removal of static limits on the number of sessions and files per
session, the orchestrator now uses dynamic allocation.

Add new test cases to verify that the system can handle a large number of
sessions and files. These tests ensure that the dynamic block allocation
and reuse logic for session metadata and outgoing files work correctly
beyond the previous static limits.

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-12-pasha.tatashin@soleen.com
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
2026-06-03 21:15:46 +03:00

291 lines
6.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2025, Google LLC.
* Pasha Tatashin <pasha.tatashin@soleen.com>
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
#include "luo_test_utils.h"
int luo_open_device(void)
{
return open(LUO_DEVICE, O_RDWR);
}
int luo_ensure_nofile_limit(long min_limit)
{
struct rlimit hl;
/* Allow to extra files to be used by test itself */
min_limit += 32;
if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
return -errno;
if (hl.rlim_cur >= min_limit)
return 0;
hl.rlim_cur = min_limit;
if (hl.rlim_cur > hl.rlim_max)
hl.rlim_max = hl.rlim_cur;
if (setrlimit(RLIMIT_NOFILE, &hl) < 0)
return -errno;
return 0;
}
int luo_create_session(int luo_fd, const char *name)
{
struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
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)
return -errno;
return arg.fd;
}
int luo_retrieve_session(int luo_fd, const char *name)
{
struct liveupdate_ioctl_retrieve_session arg = { .size = sizeof(arg) };
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)
return -errno;
return arg.fd;
}
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 = sysconf(_SC_PAGE_SIZE);
void *map = MAP_FAILED;
int mfd = -1, ret = -1;
mfd = memfd_create("test_mfd", 0);
if (mfd < 0)
return -errno;
if (ftruncate(mfd, page_size) != 0)
goto out;
map = mmap(NULL, page_size, PROT_WRITE, MAP_SHARED, mfd, 0);
if (map == MAP_FAILED)
goto out;
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)
goto out;
ret = 0;
out:
if (ret != 0 && errno != 0)
ret = -errno;
if (mfd >= 0)
close(mfd);
return ret;
}
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 = sysconf(_SC_PAGE_SIZE);
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;
map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0);
if (map == MAP_FAILED)
goto out;
if (expected_data && strcmp(expected_data, map) != 0) {
ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n",
expected_data, (char *)map);
ret = -EINVAL;
goto out_munmap;
}
ret = mfd;
out_munmap:
munmap(map, page_size);
out:
if (ret < 0 && errno != 0)
ret = -errno;
if (ret < 0 && mfd >= 0)
close(mfd);
return ret;
}
int luo_session_finish(int session_fd)
{
struct liveupdate_session_finish arg = { .size = sizeof(arg) };
if (ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &arg) < 0)
return -errno;
return 0;
}
void create_state_file(int luo_fd, const char *session_name, int token,
int next_stage)
{
char buf[32];
int state_session_fd;
state_session_fd = luo_create_session(luo_fd, session_name);
if (state_session_fd < 0)
fail_exit("luo_create_session for state tracking");
snprintf(buf, sizeof(buf), "%d", next_stage);
if (create_and_preserve_memfd(state_session_fd, token, buf) < 0)
fail_exit("create_and_preserve_memfd for state tracking");
/*
* DO NOT close session FD, otherwise it is going to be unpreserved
*/
}
void restore_and_read_stage(int state_session_fd, int token, int *stage)
{
char buf[32] = {0};
int mfd;
mfd = restore_and_verify_memfd(state_session_fd, token, NULL);
if (mfd < 0)
fail_exit("failed to restore state memfd");
if (read(mfd, buf, sizeof(buf) - 1) < 0)
fail_exit("failed to read state mfd");
*stage = atoi(buf);
close(mfd);
}
void daemonize_and_wait(void)
{
pid_t pid;
ksft_print_msg("[STAGE 1] Forking persistent child to hold sessions...\n");
pid = fork();
if (pid < 0)
fail_exit("fork failed");
if (pid > 0) {
ksft_print_msg("[STAGE 1] Child PID: %d. Resources are pinned.\n", pid);
ksft_print_msg("[STAGE 1] You may now perform kexec reboot.\n");
exit(EXIT_SUCCESS);
}
/* Detach from terminal so closing the window doesn't kill us */
if (setsid() < 0)
fail_exit("setsid failed");
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* Change dir to root to avoid locking filesystems */
if (chdir("/") < 0)
exit(EXIT_FAILURE);
while (1)
sleep(60);
}
static int parse_stage_args(int argc, char *argv[])
{
static struct option long_options[] = {
{"stage", required_argument, 0, 's'},
{0, 0, 0, 0}
};
int option_index = 0;
int stage = 1;
int opt;
optind = 1;
while ((opt = getopt_long(argc, argv, "s:", long_options, &option_index)) != -1) {
switch (opt) {
case 's':
stage = atoi(optarg);
if (stage != 1 && stage != 2)
fail_exit("Invalid stage argument");
break;
default:
fail_exit("Unknown argument");
}
}
return stage;
}
int luo_test(int argc, char *argv[],
const char *state_session_name,
luo_test_stage1_fn stage1,
luo_test_stage2_fn stage2)
{
int target_stage = parse_stage_args(argc, argv);
int luo_fd = luo_open_device();
int state_session_fd;
int detected_stage;
if (luo_fd < 0) {
ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n",
LUO_DEVICE);
}
state_session_fd = luo_retrieve_session(luo_fd, state_session_name);
if (state_session_fd == -ENOENT)
detected_stage = 1;
else if (state_session_fd >= 0)
detected_stage = 2;
else
fail_exit("Failed to check for state session");
if (target_stage != detected_stage) {
ksft_exit_fail_msg("Stage mismatch Requested --stage %d, but system is in stage %d.\n"
"(State session %s: %s)\n",
target_stage, detected_stage, state_session_name,
(detected_stage == 2) ? "EXISTS" : "MISSING");
}
if (target_stage == 1)
stage1(luo_fd);
else
stage2(luo_fd, state_session_fd);
return 0;
}