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