Extend the multi-session Live Update test to specifically verify the new LIVEUPDATE_SESSION_RETRIEVE_INTO_FD ioctl path. In Stage 2 of the kexec workflow, the test explicitly delegates topology by allocating a clean tmpfs target and driving the folios into it through the new API, verifying that the data preserves identically to the native KHO mapping.
Signed-off-by: David Matlack <[email protected]> --- .../liveupdate/lib/include/libliveupdate.h | 1 + .../selftests/liveupdate/lib/lu_utils.c | 33 +++++++++++ .../selftests/liveupdate/luo_multi_session.c | 56 ++++++++++++++++--- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h index fa07fed08364..8ab1e662eeaa 100644 --- a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h +++ b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h @@ -33,6 +33,7 @@ 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); +int luo_verify_retrieve_into_memfd(int session_fd, int token, int mfd, const char *expected_data); void create_state_file(int luo_fd, const char *session_name, int token, int next_stage); diff --git a/tools/testing/selftests/liveupdate/lib/lu_utils.c b/tools/testing/selftests/liveupdate/lib/lu_utils.c index 74d41115c281..8ed9c367f92e 100644 --- a/tools/testing/selftests/liveupdate/lib/lu_utils.c +++ b/tools/testing/selftests/liveupdate/lib/lu_utils.c @@ -324,3 +324,36 @@ int luo_test(int argc, char *argv[], return 0; } + +int luo_verify_retrieve_into_memfd(int session_fd, int token, int mfd, + const char *expected_data) +{ + struct liveupdate_session_retrieve_into_fd retrieve_args = { + .size = sizeof(retrieve_args), + .token = token, + .fd = mfd, + }; + long page_size = getpagesize(); + void *map = MAP_FAILED; + int ret = -1; + + ret = ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_INTO_FD, &retrieve_args); + if (ret < 0) { + ksft_print_msg("ioctl LIVEUPDATE_SESSION_RETRIEVE_INTO_FD failed\n"); + return -errno; + } + + map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0); + if (map == MAP_FAILED) + return -errno; + + if (expected_data && strcmp(expected_data, map) != 0) { + ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n", + expected_data, (char *)map); + munmap(map, page_size); + return -EINVAL; + } + + munmap(map, page_size); + return 0; +} diff --git a/tools/testing/selftests/liveupdate/luo_multi_session.c b/tools/testing/selftests/liveupdate/luo_multi_session.c index aac24a5f5ce3..7882f49aadaf 100644 --- a/tools/testing/selftests/liveupdate/luo_multi_session.c +++ b/tools/testing/selftests/liveupdate/luo_multi_session.c @@ -1,4 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/mount.h> +#include <sys/stat.h> /* * Copyright (c) 2025, Google LLC. @@ -31,6 +36,8 @@ static void run_stage_1(int luo_fd) { int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd; + int tmpfs_fd; + void *map; ksft_print_msg("[STAGE 1] Starting pre-kexec setup for multi-session test...\n"); @@ -53,25 +60,50 @@ static void run_stage_1(int luo_fd) s_files1_fd = luo_create_session(luo_fd, SESSION_FILES_1); if (s_files1_fd < 0) fail_exit("luo_create_session for '%s'", SESSION_FILES_1); + ksft_print_msg(" -> Preserving memfd (token %#x, content: \"%s\")\n", + MFD1_TOKEN, MFD1_DATA); if (create_and_preserve_memfd(s_files1_fd, MFD1_TOKEN, MFD1_DATA) < 0) { fail_exit("create_and_preserve_memfd for token %#x", MFD1_TOKEN); } - ksft_print_msg("[STAGE 1] Creating session '%s' with two memfds...\n", + ksft_print_msg("[STAGE 1] Creating session '%s' with memfd and tmpfs file...\n", SESSION_FILES_2); s_files2_fd = luo_create_session(luo_fd, SESSION_FILES_2); if (s_files2_fd < 0) fail_exit("luo_create_session for '%s'", SESSION_FILES_2); + ksft_print_msg(" -> Preserving memfd (token %#x, content: \"%s\")\n", + MFD2_TOKEN, MFD2_DATA); if (create_and_preserve_memfd(s_files2_fd, MFD2_TOKEN, MFD2_DATA) < 0) { fail_exit("create_and_preserve_memfd for token %#x", MFD2_TOKEN); } - if (create_and_preserve_memfd(s_files2_fd, MFD3_TOKEN, MFD3_DATA) < 0) { - fail_exit("create_and_preserve_memfd for token %#x", - MFD3_TOKEN); - } + ksft_print_msg(" -> Populating custom tmpfs file at /mnt/mfd3_source...\n"); + mkdir("/mnt", 0777); + mount("tmpfs", "/mnt", "tmpfs", 0, NULL); + tmpfs_fd = open("/mnt/mfd3_source", O_CREAT | O_RDWR, 0666); + if (tmpfs_fd < 0) + fail_exit("failed to open tmpfs_fd"); + ksft_print_msg(" -> Preserving generic tmpfs file (token %#x, content: \"%s\")\n", + MFD3_TOKEN, MFD3_DATA); + if (ftruncate(tmpfs_fd, getpagesize()) < 0) + fail_exit("ftruncate tmpfs_fd"); + map = mmap(NULL, getpagesize(), PROT_WRITE, MAP_SHARED, tmpfs_fd, 0); + if (map == MAP_FAILED) + fail_exit("mmap tmpfs_fd"); + strcpy(map, MFD3_DATA); + munmap(map, getpagesize()); + + struct liveupdate_session_preserve_fd preserve_args = { + .size = sizeof(preserve_args), + .token = MFD3_TOKEN, + .fd = tmpfs_fd, + }; + if (ioctl(s_files2_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &preserve_args) < 0) + fail_exit("preserve tmpfs fd"); + + close(tmpfs_fd); close(luo_fd); daemonize_and_wait(); @@ -110,6 +142,7 @@ static void run_stage_2(int luo_fd, int state_session_fd) ksft_print_msg("[STAGE 2] Verifying contents of session '%s'...\n", SESSION_FILES_1); + ksft_print_msg(" -> Retrieving memfd (token %#x) from session...\n", MFD1_TOKEN); mfd1 = restore_and_verify_memfd(s_files1_fd, MFD1_TOKEN, MFD1_DATA); if (mfd1 < 0) fail_exit("restore_and_verify_memfd for token %#x", MFD1_TOKEN); @@ -118,14 +151,23 @@ static void run_stage_2(int luo_fd, int state_session_fd) ksft_print_msg("[STAGE 2] Verifying contents of session '%s'...\n", SESSION_FILES_2); + ksft_print_msg(" -> Retrieving memfd (token %#x) from session...\n", MFD2_TOKEN); mfd2 = restore_and_verify_memfd(s_files2_fd, MFD2_TOKEN, MFD2_DATA); if (mfd2 < 0) fail_exit("restore_and_verify_memfd for token %#x", MFD2_TOKEN); close(mfd2); - mfd3 = restore_and_verify_memfd(s_files2_fd, MFD3_TOKEN, MFD3_DATA); + ksft_print_msg(" -> Creating empty tmpfs file at /mnt/mfd3_target...\n"); + mkdir("/mnt", 0777); + mount("tmpfs", "/mnt", "tmpfs", 0, NULL); + mfd3 = open("/mnt/mfd3_target", O_CREAT | O_RDWR, 0666); if (mfd3 < 0) - fail_exit("restore_and_verify_memfd for token %#x", MFD3_TOKEN); + fail_exit("open tmpfs target for retrieve_into"); + + ksft_print_msg(" -> Validating LIVEUPDATE_SESSION_RETRIEVE_INTO_FD (token %#x)...\n", + MFD3_TOKEN); + if (luo_verify_retrieve_into_memfd(s_files2_fd, MFD3_TOKEN, mfd3, MFD3_DATA) < 0) + fail_exit("luo_verify_retrieve_into_memfd for token %#x", MFD3_TOKEN); close(mfd3); ksft_print_msg("[STAGE 2] Test data verified successfully.\n"); -- 2.55.0.966.g6673acef38-goog

