xref: /linux/tools/testing/selftests/liveupdate/luo_test_utils.c (revision 5ba3f30643cbdd79fb82e525aa1ca55b62fcc7ac)
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