Introduce a new selftest, luo_multi_file, to validate two key aspects of the Live Update Orchestrator file preservation mechanism: the ability to handle multiple files within a single session, and the correct cleanup of unreclaimed files. The test implements a full kexec cycle with the following flow: 1. Pre-kexec: - A single session is created. - Three distinct memfd files (A, B, and C) are created, populated with unique data, and preserved within this session. - The global LIVEUPDATE_PREPARE event is triggered, and the system reboots via kexec. 2. Post-kexec: - The preserved session is retrieved. - Files A and C are restored and their contents are verified to ensure that multiple files can be successfully restored from a single session. - File B is intentionally not restored. - The global LIVEUPDATE_FINISH event is triggered. 3. Verification: - The test is considered successful if files A and C are verified correctly. - The user is prompted to check the kernel log (dmesg) for a message confirming that the unreclaimed file (B) was identified and cleaned up by the LUO core, thus validating the cleanup path. Signed-off-by: Pasha Tatashin --- tools/testing/selftests/liveupdate/Makefile | 1 + .../selftests/liveupdate/luo_multi_file.c | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tools/testing/selftests/liveupdate/luo_multi_file.c diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile index 1cbc816ed5c5..f43b7d03e017 100644 --- a/tools/testing/selftests/liveupdate/Makefile +++ b/tools/testing/selftests/liveupdate/Makefile @@ -9,6 +9,7 @@ LDFLAGS += -static LUO_SHARED_SRCS := luo_test_utils.c LUO_SHARED_HDRS += luo_test_utils.h +LUO_MANUAL_TESTS += luo_multi_file LUO_MANUAL_TESTS += luo_multi_kexec TEST_FILES += do_kexec.sh diff --git a/tools/testing/selftests/liveupdate/luo_multi_file.c b/tools/testing/selftests/liveupdate/luo_multi_file.c new file mode 100644 index 000000000000..ae38fe8aba4c --- /dev/null +++ b/tools/testing/selftests/liveupdate/luo_multi_file.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ + +#include "luo_test_utils.h" + +#define KEXEC_SCRIPT "./do_kexec.sh" + +#define SESSION_NAME "multi_file_session" +#define TOKEN_A 101 +#define TOKEN_B 102 +#define TOKEN_C 103 + +#define DATA_A "Alpha file data" +#define DATA_B "Bravo file data which will be unreclaimed" +#define DATA_C "Charlie file data" + +static void run_pre_kexec(int luo_fd) +{ + int session_fd; + + ksft_print_msg("[PRE-KEXEC] Starting workload...\n"); + + session_fd = luo_create_session(luo_fd, SESSION_NAME); + if (session_fd < 0) + fail_exit("Failed to create session '%s'", SESSION_NAME); + + ksft_print_msg("[PRE-KEXEC] Preserving 3 memfds (A, B, C)...\n"); + if (create_and_preserve_memfd(session_fd, TOKEN_A, DATA_A) < 0) + fail_exit("Failed to preserve memfd A"); + if (create_and_preserve_memfd(session_fd, TOKEN_B, DATA_B) < 0) + fail_exit("Failed to preserve memfd B"); + if (create_and_preserve_memfd(session_fd, TOKEN_C, DATA_C) < 0) + fail_exit("Failed to preserve memfd C"); + ksft_print_msg("[PRE-KEXEC] All memfds preserved.\n"); + + if (luo_set_global_event(luo_fd, LIVEUPDATE_PREPARE) < 0) + fail_exit("Failed to set global PREPARE event"); + + ksft_print_msg("[PRE-KEXEC] System is ready. Executing kexec...\n"); + if (system(KEXEC_SCRIPT) != 0) + fail_exit("kexec script failed"); + + sleep(10); /* Should not be reached */ + exit(EXIT_FAILURE); +} + +static void run_post_kexec(int luo_fd) +{ + int session_fd, mfd_a, mfd_c; + + ksft_print_msg("[POST-KEXEC] Starting workload...\n"); + + session_fd = luo_retrieve_session(luo_fd, SESSION_NAME); + if (session_fd < 0) + fail_exit("Failed to retrieve session '%s'", SESSION_NAME); + + /* 1. VERIFY SUCCESS: Restore and verify memfd A. */ + ksft_print_msg("[POST-KEXEC] Restoring and verifying memfd A (token %d)...\n", + TOKEN_A); + mfd_a = restore_and_verify_memfd(session_fd, TOKEN_A, DATA_A); + if (mfd_a < 0) + fail_exit("Failed to restore or verify memfd A"); + close(mfd_a); + ksft_print_msg(" Success.\n"); + + /* 2. VERIFY SUCCESS: Restore and verify memfd C. */ + ksft_print_msg("[POST-KEXEC] Restoring and verifying memfd C (token %d)...\n", + TOKEN_C); + mfd_c = restore_and_verify_memfd(session_fd, TOKEN_C, DATA_C); + if (mfd_c < 0) + fail_exit("Failed to restore or verify memfd C"); + close(mfd_c); + ksft_print_msg(" Success.\n"); + + ksft_print_msg("[POST-KEXEC] NOT restoring memfd B (token %d) to test cleanup.\n", + TOKEN_B); + + if (luo_set_global_event(luo_fd, LIVEUPDATE_FINISH) < 0) + fail_exit("Failed to set global FINISH event"); + + close(session_fd); + + ksft_print_msg("\n--- TEST PASSED ---\n"); + ksft_print_msg("Check dmesg for cleanup log of token %d in session '%s'.\n", + TOKEN_B, SESSION_NAME); +} + +int main(int argc, char *argv[]) +{ + enum liveupdate_state state; + int luo_fd; + + luo_fd = luo_open_device(); + if (luo_fd < 0) { + ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n", + LUO_DEVICE); + } + + if (luo_get_global_state(luo_fd, &state) < 0) + fail_exit("Failed to get LUO state"); + + switch (state) { + case LIVEUPDATE_STATE_NORMAL: + run_pre_kexec(luo_fd); + break; + case LIVEUPDATE_STATE_UPDATED: + run_post_kexec(luo_fd); + break; + default: + fail_exit("Test started in an unexpected state: %d", state); + } + + close(luo_fd); + ksft_exit_pass(); +} -- 2.51.0.536.g15c5d4f767-goog