projects/linux·blob master · refs

326 lines · 6.8 KiB

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