1*df1a0d06SVipin Sharma // SPDX-License-Identifier: GPL-2.0-only 2*df1a0d06SVipin Sharma 3*df1a0d06SVipin Sharma /* 4*df1a0d06SVipin Sharma * Copyright (c) 2025, Google LLC. 5*df1a0d06SVipin Sharma * Pasha Tatashin <pasha.tatashin@soleen.com> 6*df1a0d06SVipin Sharma */ 7*df1a0d06SVipin Sharma 8*df1a0d06SVipin Sharma #define _GNU_SOURCE 9*df1a0d06SVipin Sharma 10*df1a0d06SVipin Sharma #include <stdio.h> 11*df1a0d06SVipin Sharma #include <stdlib.h> 12*df1a0d06SVipin Sharma #include <string.h> 13*df1a0d06SVipin Sharma #include <getopt.h> 14*df1a0d06SVipin Sharma #include <fcntl.h> 15*df1a0d06SVipin Sharma #include <unistd.h> 16*df1a0d06SVipin Sharma #include <sys/ioctl.h> 17*df1a0d06SVipin Sharma #include <sys/syscall.h> 18*df1a0d06SVipin Sharma #include <sys/mman.h> 19*df1a0d06SVipin Sharma #include <sys/types.h> 20*df1a0d06SVipin Sharma #include <sys/resource.h> 21*df1a0d06SVipin Sharma #include <sys/stat.h> 22*df1a0d06SVipin Sharma #include <errno.h> 23*df1a0d06SVipin Sharma #include <stdarg.h> 24*df1a0d06SVipin Sharma #include <linux/unistd.h> 25*df1a0d06SVipin Sharma 26*df1a0d06SVipin Sharma #include <libliveupdate.h> 27*df1a0d06SVipin Sharma 28*df1a0d06SVipin Sharma int luo_open_device(void) 29*df1a0d06SVipin Sharma { 30*df1a0d06SVipin Sharma return open(LUO_DEVICE, O_RDWR); 31*df1a0d06SVipin Sharma } 32*df1a0d06SVipin Sharma 33*df1a0d06SVipin Sharma int luo_ensure_nofile_limit(long min_limit) 34*df1a0d06SVipin Sharma { 35*df1a0d06SVipin Sharma struct rlimit hl; 36*df1a0d06SVipin Sharma 37*df1a0d06SVipin Sharma /* Allow to extra files to be used by test itself */ 38*df1a0d06SVipin Sharma min_limit += 32; 39*df1a0d06SVipin Sharma 40*df1a0d06SVipin Sharma if (getrlimit(RLIMIT_NOFILE, &hl) < 0) 41*df1a0d06SVipin Sharma return -errno; 42*df1a0d06SVipin Sharma 43*df1a0d06SVipin Sharma if (hl.rlim_cur >= min_limit) 44*df1a0d06SVipin Sharma return 0; 45*df1a0d06SVipin Sharma 46*df1a0d06SVipin Sharma hl.rlim_cur = min_limit; 47*df1a0d06SVipin Sharma if (hl.rlim_cur > hl.rlim_max) 48*df1a0d06SVipin Sharma hl.rlim_max = hl.rlim_cur; 49*df1a0d06SVipin Sharma 50*df1a0d06SVipin Sharma if (setrlimit(RLIMIT_NOFILE, &hl) < 0) 51*df1a0d06SVipin Sharma return -errno; 52*df1a0d06SVipin Sharma 53*df1a0d06SVipin Sharma return 0; 54*df1a0d06SVipin Sharma } 55*df1a0d06SVipin Sharma 56*df1a0d06SVipin Sharma int luo_create_session(int luo_fd, const char *name) 57*df1a0d06SVipin Sharma { 58*df1a0d06SVipin Sharma struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) }; 59*df1a0d06SVipin Sharma 60*df1a0d06SVipin Sharma snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s", 61*df1a0d06SVipin Sharma LIVEUPDATE_SESSION_NAME_LENGTH - 1, name); 62*df1a0d06SVipin Sharma 63*df1a0d06SVipin Sharma if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg)) 64*df1a0d06SVipin Sharma return -errno; 65*df1a0d06SVipin Sharma 66*df1a0d06SVipin Sharma return arg.fd; 67*df1a0d06SVipin Sharma } 68*df1a0d06SVipin Sharma 69*df1a0d06SVipin Sharma int luo_retrieve_session(int luo_fd, const char *name) 70*df1a0d06SVipin Sharma { 71*df1a0d06SVipin Sharma struct liveupdate_ioctl_retrieve_session arg = { .size = sizeof(arg) }; 72*df1a0d06SVipin Sharma 73*df1a0d06SVipin Sharma snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s", 74*df1a0d06SVipin Sharma LIVEUPDATE_SESSION_NAME_LENGTH - 1, name); 75*df1a0d06SVipin Sharma 76*df1a0d06SVipin Sharma if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg)) 77*df1a0d06SVipin Sharma return -errno; 78*df1a0d06SVipin Sharma 79*df1a0d06SVipin Sharma return arg.fd; 80*df1a0d06SVipin Sharma } 81*df1a0d06SVipin Sharma 82*df1a0d06SVipin Sharma int luo_session_preserve_fd(int session_fd, int fd, __u64 token) 83*df1a0d06SVipin Sharma { 84*df1a0d06SVipin Sharma struct liveupdate_session_preserve_fd arg = { 85*df1a0d06SVipin Sharma .size = sizeof(arg), 86*df1a0d06SVipin Sharma .fd = fd, 87*df1a0d06SVipin Sharma .token = token, 88*df1a0d06SVipin Sharma }; 89*df1a0d06SVipin Sharma 90*df1a0d06SVipin Sharma if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg)) 91*df1a0d06SVipin Sharma return -errno; 92*df1a0d06SVipin Sharma 93*df1a0d06SVipin Sharma return 0; 94*df1a0d06SVipin Sharma } 95*df1a0d06SVipin Sharma 96*df1a0d06SVipin Sharma int luo_session_retrieve_fd(int session_fd, __u64 token) 97*df1a0d06SVipin Sharma { 98*df1a0d06SVipin Sharma struct liveupdate_session_retrieve_fd arg = { 99*df1a0d06SVipin Sharma .size = sizeof(arg), 100*df1a0d06SVipin Sharma .token = token, 101*df1a0d06SVipin Sharma }; 102*df1a0d06SVipin Sharma 103*df1a0d06SVipin Sharma if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg)) 104*df1a0d06SVipin Sharma return -errno; 105*df1a0d06SVipin Sharma 106*df1a0d06SVipin Sharma return arg.fd; 107*df1a0d06SVipin Sharma } 108*df1a0d06SVipin Sharma 109*df1a0d06SVipin Sharma /* Helper function to get a session name via ioctl. */ 110*df1a0d06SVipin Sharma int luo_get_session_name(int session_fd, char *name, size_t name_len) 111*df1a0d06SVipin Sharma { 112*df1a0d06SVipin Sharma struct liveupdate_session_get_name args = {}; 113*df1a0d06SVipin Sharma 114*df1a0d06SVipin Sharma args.size = sizeof(args); 115*df1a0d06SVipin Sharma 116*df1a0d06SVipin Sharma if (ioctl(session_fd, LIVEUPDATE_SESSION_GET_NAME, &args)) 117*df1a0d06SVipin Sharma return -errno; 118*df1a0d06SVipin Sharma 119*df1a0d06SVipin Sharma strncpy(name, (char *)args.name, name_len - 1); 120*df1a0d06SVipin Sharma name[name_len - 1] = '\0'; 121*df1a0d06SVipin Sharma 122*df1a0d06SVipin Sharma return 0; 123*df1a0d06SVipin Sharma } 124*df1a0d06SVipin Sharma 125*df1a0d06SVipin Sharma int create_and_preserve_memfd(int session_fd, int token, const char *data) 126*df1a0d06SVipin Sharma { 127*df1a0d06SVipin Sharma long page_size = getpagesize(); 128*df1a0d06SVipin Sharma void *map = MAP_FAILED; 129*df1a0d06SVipin Sharma int mfd = -1, ret = -1; 130*df1a0d06SVipin Sharma 131*df1a0d06SVipin Sharma mfd = memfd_create("test_mfd", 0); 132*df1a0d06SVipin Sharma if (mfd < 0) 133*df1a0d06SVipin Sharma return -errno; 134*df1a0d06SVipin Sharma 135*df1a0d06SVipin Sharma if (ftruncate(mfd, page_size) != 0) 136*df1a0d06SVipin Sharma goto out; 137*df1a0d06SVipin Sharma 138*df1a0d06SVipin Sharma map = mmap(NULL, page_size, PROT_WRITE, MAP_SHARED, mfd, 0); 139*df1a0d06SVipin Sharma if (map == MAP_FAILED) 140*df1a0d06SVipin Sharma goto out; 141*df1a0d06SVipin Sharma 142*df1a0d06SVipin Sharma snprintf(map, page_size, "%s", data); 143*df1a0d06SVipin Sharma munmap(map, page_size); 144*df1a0d06SVipin Sharma 145*df1a0d06SVipin Sharma ret = luo_session_preserve_fd(session_fd, mfd, token); 146*df1a0d06SVipin Sharma if (ret) 147*df1a0d06SVipin Sharma goto out; 148*df1a0d06SVipin Sharma 149*df1a0d06SVipin Sharma ret = 0; 150*df1a0d06SVipin Sharma out: 151*df1a0d06SVipin Sharma if (ret != 0 && errno != 0) 152*df1a0d06SVipin Sharma ret = -errno; 153*df1a0d06SVipin Sharma if (mfd >= 0) 154*df1a0d06SVipin Sharma close(mfd); 155*df1a0d06SVipin Sharma return ret; 156*df1a0d06SVipin Sharma } 157*df1a0d06SVipin Sharma 158*df1a0d06SVipin Sharma int restore_and_verify_memfd(int session_fd, int token, 159*df1a0d06SVipin Sharma const char *expected_data) 160*df1a0d06SVipin Sharma { 161*df1a0d06SVipin Sharma long page_size = getpagesize(); 162*df1a0d06SVipin Sharma void *map = MAP_FAILED; 163*df1a0d06SVipin Sharma int mfd = -1, ret = -1; 164*df1a0d06SVipin Sharma 165*df1a0d06SVipin Sharma mfd = luo_session_retrieve_fd(session_fd, token); 166*df1a0d06SVipin Sharma if (mfd < 0) 167*df1a0d06SVipin Sharma return mfd; 168*df1a0d06SVipin Sharma 169*df1a0d06SVipin Sharma map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0); 170*df1a0d06SVipin Sharma if (map == MAP_FAILED) 171*df1a0d06SVipin Sharma goto out; 172*df1a0d06SVipin Sharma 173*df1a0d06SVipin Sharma if (expected_data && strcmp(expected_data, map) != 0) { 174*df1a0d06SVipin Sharma ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n", 175*df1a0d06SVipin Sharma expected_data, (char *)map); 176*df1a0d06SVipin Sharma ret = -EINVAL; 177*df1a0d06SVipin Sharma goto out_munmap; 178*df1a0d06SVipin Sharma } 179*df1a0d06SVipin Sharma 180*df1a0d06SVipin Sharma ret = mfd; 181*df1a0d06SVipin Sharma out_munmap: 182*df1a0d06SVipin Sharma munmap(map, page_size); 183*df1a0d06SVipin Sharma out: 184*df1a0d06SVipin Sharma if (ret < 0 && errno != 0) 185*df1a0d06SVipin Sharma ret = -errno; 186*df1a0d06SVipin Sharma if (ret < 0 && mfd >= 0) 187*df1a0d06SVipin Sharma close(mfd); 188*df1a0d06SVipin Sharma return ret; 189*df1a0d06SVipin Sharma } 190*df1a0d06SVipin Sharma 191*df1a0d06SVipin Sharma int luo_session_finish(int session_fd) 192*df1a0d06SVipin Sharma { 193*df1a0d06SVipin Sharma struct liveupdate_session_finish arg = { .size = sizeof(arg) }; 194*df1a0d06SVipin Sharma 195*df1a0d06SVipin Sharma if (ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &arg) < 0) 196*df1a0d06SVipin Sharma return -errno; 197*df1a0d06SVipin Sharma 198*df1a0d06SVipin Sharma return 0; 199*df1a0d06SVipin Sharma } 200*df1a0d06SVipin Sharma 201*df1a0d06SVipin Sharma void create_state_file(int luo_fd, const char *session_name, int token, 202*df1a0d06SVipin Sharma int next_stage) 203*df1a0d06SVipin Sharma { 204*df1a0d06SVipin Sharma char buf[32]; 205*df1a0d06SVipin Sharma int state_session_fd; 206*df1a0d06SVipin Sharma 207*df1a0d06SVipin Sharma state_session_fd = luo_create_session(luo_fd, session_name); 208*df1a0d06SVipin Sharma if (state_session_fd < 0) 209*df1a0d06SVipin Sharma fail_exit("luo_create_session for state tracking"); 210*df1a0d06SVipin Sharma 211*df1a0d06SVipin Sharma snprintf(buf, sizeof(buf), "%d", next_stage); 212*df1a0d06SVipin Sharma if (create_and_preserve_memfd(state_session_fd, token, buf) < 0) 213*df1a0d06SVipin Sharma fail_exit("create_and_preserve_memfd for state tracking"); 214*df1a0d06SVipin Sharma 215*df1a0d06SVipin Sharma /* 216*df1a0d06SVipin Sharma * DO NOT close session FD, otherwise it is going to be unpreserved 217*df1a0d06SVipin Sharma */ 218*df1a0d06SVipin Sharma } 219*df1a0d06SVipin Sharma 220*df1a0d06SVipin Sharma void restore_and_read_stage(int state_session_fd, int token, int *stage) 221*df1a0d06SVipin Sharma { 222*df1a0d06SVipin Sharma char buf[32] = {0}; 223*df1a0d06SVipin Sharma int mfd; 224*df1a0d06SVipin Sharma 225*df1a0d06SVipin Sharma mfd = restore_and_verify_memfd(state_session_fd, token, NULL); 226*df1a0d06SVipin Sharma if (mfd < 0) 227*df1a0d06SVipin Sharma fail_exit("failed to restore state memfd"); 228*df1a0d06SVipin Sharma 229*df1a0d06SVipin Sharma if (read(mfd, buf, sizeof(buf) - 1) < 0) 230*df1a0d06SVipin Sharma fail_exit("failed to read state mfd"); 231*df1a0d06SVipin Sharma 232*df1a0d06SVipin Sharma *stage = atoi(buf); 233*df1a0d06SVipin Sharma 234*df1a0d06SVipin Sharma close(mfd); 235*df1a0d06SVipin Sharma } 236*df1a0d06SVipin Sharma 237*df1a0d06SVipin Sharma void daemonize_and_wait(void) 238*df1a0d06SVipin Sharma { 239*df1a0d06SVipin Sharma pid_t pid; 240*df1a0d06SVipin Sharma 241*df1a0d06SVipin Sharma ksft_print_msg("[STAGE 1] Forking persistent child to hold sessions...\n"); 242*df1a0d06SVipin Sharma 243*df1a0d06SVipin Sharma pid = fork(); 244*df1a0d06SVipin Sharma if (pid < 0) 245*df1a0d06SVipin Sharma fail_exit("fork failed"); 246*df1a0d06SVipin Sharma 247*df1a0d06SVipin Sharma if (pid > 0) { 248*df1a0d06SVipin Sharma ksft_print_msg("[STAGE 1] Child PID: %d. Resources are pinned.\n", pid); 249*df1a0d06SVipin Sharma ksft_print_msg("[STAGE 1] You may now perform kexec reboot.\n"); 250*df1a0d06SVipin Sharma exit(EXIT_SUCCESS); 251*df1a0d06SVipin Sharma } 252*df1a0d06SVipin Sharma 253*df1a0d06SVipin Sharma /* Detach from terminal so closing the window doesn't kill us */ 254*df1a0d06SVipin Sharma if (setsid() < 0) 255*df1a0d06SVipin Sharma fail_exit("setsid failed"); 256*df1a0d06SVipin Sharma 257*df1a0d06SVipin Sharma close(STDIN_FILENO); 258*df1a0d06SVipin Sharma close(STDOUT_FILENO); 259*df1a0d06SVipin Sharma close(STDERR_FILENO); 260*df1a0d06SVipin Sharma 261*df1a0d06SVipin Sharma /* Change dir to root to avoid locking filesystems */ 262*df1a0d06SVipin Sharma if (chdir("/") < 0) 263*df1a0d06SVipin Sharma exit(EXIT_FAILURE); 264*df1a0d06SVipin Sharma 265*df1a0d06SVipin Sharma while (1) 266*df1a0d06SVipin Sharma sleep(60); 267*df1a0d06SVipin Sharma } 268*df1a0d06SVipin Sharma 269*df1a0d06SVipin Sharma static int parse_stage_args(int argc, char *argv[]) 270*df1a0d06SVipin Sharma { 271*df1a0d06SVipin Sharma int stage = 1; 272*df1a0d06SVipin Sharma int opt; 273*df1a0d06SVipin Sharma 274*df1a0d06SVipin Sharma optind = 1; 275*df1a0d06SVipin Sharma while ((opt = getopt(argc, argv, "s:")) != -1) { 276*df1a0d06SVipin Sharma switch (opt) { 277*df1a0d06SVipin Sharma case 's': 278*df1a0d06SVipin Sharma stage = atoi(optarg); 279*df1a0d06SVipin Sharma if (stage != 1 && stage != 2) 280*df1a0d06SVipin Sharma fail_exit("Invalid stage argument"); 281*df1a0d06SVipin Sharma break; 282*df1a0d06SVipin Sharma default: 283*df1a0d06SVipin Sharma fail_exit("Unknown argument"); 284*df1a0d06SVipin Sharma } 285*df1a0d06SVipin Sharma } 286*df1a0d06SVipin Sharma 287*df1a0d06SVipin Sharma return stage; 288*df1a0d06SVipin Sharma } 289*df1a0d06SVipin Sharma 290*df1a0d06SVipin Sharma int luo_test(int argc, char *argv[], 291*df1a0d06SVipin Sharma const char *state_session_name, 292*df1a0d06SVipin Sharma luo_test_stage1_fn stage1, 293*df1a0d06SVipin Sharma luo_test_stage2_fn stage2) 294*df1a0d06SVipin Sharma { 295*df1a0d06SVipin Sharma int target_stage = parse_stage_args(argc, argv); 296*df1a0d06SVipin Sharma int luo_fd = luo_open_device(); 297*df1a0d06SVipin Sharma int state_session_fd; 298*df1a0d06SVipin Sharma int detected_stage; 299*df1a0d06SVipin Sharma 300*df1a0d06SVipin Sharma if (luo_fd < 0) { 301*df1a0d06SVipin Sharma ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n", 302*df1a0d06SVipin Sharma LUO_DEVICE); 303*df1a0d06SVipin Sharma } 304*df1a0d06SVipin Sharma 305*df1a0d06SVipin Sharma state_session_fd = luo_retrieve_session(luo_fd, state_session_name); 306*df1a0d06SVipin Sharma if (state_session_fd == -ENOENT) 307*df1a0d06SVipin Sharma detected_stage = 1; 308*df1a0d06SVipin Sharma else if (state_session_fd >= 0) 309*df1a0d06SVipin Sharma detected_stage = 2; 310*df1a0d06SVipin Sharma else 311*df1a0d06SVipin Sharma fail_exit("Failed to check for state session"); 312*df1a0d06SVipin Sharma 313*df1a0d06SVipin Sharma if (target_stage != detected_stage) { 314*df1a0d06SVipin Sharma ksft_exit_fail_msg("Stage mismatch Requested stage %d, but system is in stage %d.\n" 315*df1a0d06SVipin Sharma "(State session %s: %s)\n", 316*df1a0d06SVipin Sharma target_stage, detected_stage, state_session_name, 317*df1a0d06SVipin Sharma (detected_stage == 2) ? "EXISTS" : "MISSING"); 318*df1a0d06SVipin Sharma } 319*df1a0d06SVipin Sharma 320*df1a0d06SVipin Sharma if (target_stage == 1) 321*df1a0d06SVipin Sharma stage1(luo_fd); 322*df1a0d06SVipin Sharma else 323*df1a0d06SVipin Sharma stage2(luo_fd, state_session_fd); 324*df1a0d06SVipin Sharma 325*df1a0d06SVipin Sharma return 0; 326*df1a0d06SVipin Sharma } 327