xref: /aosp_15_r20/external/liburing/test/across-fork.c (revision 25da2bea747f3a93b4c30fd9708b0618ef55a0e6)
1 /* SPDX-License-Identifier: MIT */
2 /*
3  * Description: test sharing a ring across a fork
4  */
5 #include <fcntl.h>
6 #include <pthread.h>
7 #include <signal.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/mman.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <unistd.h>
16 
17 #include "liburing.h"
18 
19 
20 struct forktestmem
21 {
22 	struct io_uring ring;
23 	pthread_barrier_t barrier;
24 	pthread_barrierattr_t barrierattr;
25 };
26 
open_tempfile(const char * dir,const char * fname)27 static int open_tempfile(const char *dir, const char *fname)
28 {
29 	int fd;
30 	char buf[32];
31 
32 	snprintf(buf, sizeof(buf), "%s/%s",
33 		 dir, fname);
34 	fd = open(buf, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
35 	if (fd < 0) {
36 		perror("open");
37 		exit(1);
38 	}
39 
40 	return fd;
41 }
42 
submit_write(struct io_uring * ring,int fd,const char * str,int wait)43 static int submit_write(struct io_uring *ring, int fd, const char *str,
44 			int wait)
45 {
46 	struct io_uring_sqe *sqe;
47 	struct iovec iovec;
48 	int ret;
49 
50 	sqe = io_uring_get_sqe(ring);
51 	if (!sqe) {
52 		fprintf(stderr, "could not get sqe\n");
53 		return 1;
54 	}
55 
56 	iovec.iov_base = (char *) str;
57 	iovec.iov_len = strlen(str);
58 	io_uring_prep_writev(sqe, fd, &iovec, 1, 0);
59 	ret = io_uring_submit_and_wait(ring, wait);
60 	if (ret < 0) {
61 		fprintf(stderr, "submit failed: %s\n", strerror(-ret));
62 		return 1;
63 	}
64 
65 	return 0;
66 }
67 
wait_cqe(struct io_uring * ring,const char * stage)68 static int wait_cqe(struct io_uring *ring, const char *stage)
69 {
70 	struct io_uring_cqe *cqe;
71 	int ret;
72 
73 	ret = io_uring_wait_cqe(ring, &cqe);
74 	if (ret) {
75 		fprintf(stderr, "%s wait_cqe failed %d\n", stage, ret);
76 		return 1;
77 	}
78 	if (cqe->res < 0) {
79 		fprintf(stderr, "%s cqe failed %d\n", stage, cqe->res);
80 		return 1;
81 	}
82 
83 	io_uring_cqe_seen(ring, cqe);
84 	return 0;
85 }
86 
verify_file(const char * tmpdir,const char * fname,const char * expect)87 static int verify_file(const char *tmpdir, const char *fname, const char* expect)
88 {
89 	int fd;
90 	char buf[512];
91 	int err = 0;
92 
93 	memset(buf, 0, sizeof(buf));
94 
95 	fd = open_tempfile(tmpdir, fname);
96 	if (fd < 0)
97 		return 1;
98 
99 	if (read(fd, buf, sizeof(buf) - 1) < 0)
100 		return 1;
101 
102 	if (strcmp(buf, expect) != 0) {
103 		fprintf(stderr, "content mismatch for %s\n"
104 			"got:\n%s\n"
105 			"expected:\n%s\n",
106 			fname, buf, expect);
107 		err = 1;
108 	}
109 
110 	close(fd);
111 	return err;
112 }
113 
cleanup(const char * tmpdir)114 static void cleanup(const char *tmpdir)
115 {
116 	char buf[32];
117 
118 	/* don't check errors, called during partial runs */
119 
120 	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "shared");
121 	unlink(buf);
122 
123 	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent1");
124 	unlink(buf);
125 
126 	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent2");
127 	unlink(buf);
128 
129 	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "child");
130 	unlink(buf);
131 
132 	rmdir(tmpdir);
133 }
134 
main(int argc,char * argv[])135 int main(int argc, char *argv[])
136 {
137 	struct forktestmem *shmem;
138 	char tmpdir[] = "forktmpXXXXXX";
139 	int shared_fd;
140 	int ret;
141 	pid_t p;
142 
143 	if (argc > 1)
144 		return 0;
145 
146 	shmem = mmap(0, sizeof(struct forktestmem), PROT_READ|PROT_WRITE,
147 		   MAP_SHARED | MAP_ANONYMOUS, 0, 0);
148 	if (!shmem) {
149 		fprintf(stderr, "mmap failed\n");
150 		exit(1);
151 	}
152 
153 	pthread_barrierattr_init(&shmem->barrierattr);
154 	pthread_barrierattr_setpshared(&shmem->barrierattr, 1);
155 	pthread_barrier_init(&shmem->barrier, &shmem->barrierattr, 2);
156 
157 	ret = io_uring_queue_init(10, &shmem->ring, 0);
158 	if (ret < 0) {
159 		fprintf(stderr, "queue init failed\n");
160 		exit(1);
161 	}
162 
163 	if (mkdtemp(tmpdir) == NULL) {
164 		fprintf(stderr, "temp directory creation failed\n");
165 		exit(1);
166 	}
167 
168 	shared_fd = open_tempfile(tmpdir, "shared");
169 
170 	/*
171 	 * First do a write before the fork, to test whether child can
172 	 * reap that
173 	 */
174 	if (submit_write(&shmem->ring, shared_fd, "before fork: write shared fd\n", 0))
175 		goto errcleanup;
176 
177 	p = fork();
178 	switch (p) {
179 	case -1:
180 		fprintf(stderr, "fork failed\n");
181 		goto errcleanup;
182 
183 	default: {
184 		/* parent */
185 		int parent_fd1;
186 		int parent_fd2;
187 		int wstatus;
188 
189 		/* wait till fork is started up */
190 		pthread_barrier_wait(&shmem->barrier);
191 
192 		parent_fd1 = open_tempfile(tmpdir, "parent1");
193 		parent_fd2 = open_tempfile(tmpdir, "parent2");
194 
195 		/* do a parent write to the shared fd */
196 		if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd\n", 0))
197 			goto errcleanup;
198 
199 		/* do a parent write to an fd where same numbered fd exists in child */
200 		if (submit_write(&shmem->ring, parent_fd1, "parent: write parent fd 1\n", 0))
201 			goto errcleanup;
202 
203 		/* do a parent write to an fd where no same numbered fd exists in child */
204 		if (submit_write(&shmem->ring, parent_fd2, "parent: write parent fd 2\n", 0))
205 			goto errcleanup;
206 
207 		/* wait to switch read/writ roles with child */
208 		pthread_barrier_wait(&shmem->barrier);
209 
210 		/* now wait for child to exit, to ensure we still can read completion */
211 		waitpid(p, &wstatus, 0);
212 		if (WEXITSTATUS(wstatus) != 0) {
213 			fprintf(stderr, "child failed\n");
214 			goto errcleanup;
215 		}
216 
217 		if (wait_cqe(&shmem->ring, "p cqe 1"))
218 			goto errcleanup;
219 
220 		if (wait_cqe(&shmem->ring, "p cqe 2"))
221 			goto errcleanup;
222 
223 		/* check that IO can still be submitted after child exited */
224 		if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd after child exit\n", 0))
225 			goto errcleanup;
226 
227 		if (wait_cqe(&shmem->ring, "p cqe 3"))
228 			goto errcleanup;
229 
230 		break;
231 	}
232 	case 0: {
233 		/* child */
234 		int child_fd;
235 
236 		/* wait till fork is started up */
237 		pthread_barrier_wait(&shmem->barrier);
238 
239 		child_fd = open_tempfile(tmpdir, "child");
240 
241 		if (wait_cqe(&shmem->ring, "c cqe shared"))
242 			exit(1);
243 
244 		if (wait_cqe(&shmem->ring, "c cqe parent 1"))
245 			exit(1);
246 
247 		if (wait_cqe(&shmem->ring, "c cqe parent 2"))
248 			exit(1);
249 
250 		if (wait_cqe(&shmem->ring, "c cqe parent 3"))
251 			exit(1);
252 
253 		/* wait to switch read/writ roles with parent */
254 		pthread_barrier_wait(&shmem->barrier);
255 
256 		if (submit_write(&shmem->ring, child_fd, "child: write child fd\n", 0))
257 			exit(1);
258 
259 		/* ensure both writes have finished before child exits */
260 		if (submit_write(&shmem->ring, shared_fd, "child: write shared fd\n", 2))
261 			exit(1);
262 
263 		exit(0);
264 	}
265 	}
266 
267 	if (verify_file(tmpdir, "shared",
268 			 "before fork: write shared fd\n"
269 			 "parent: write shared fd\n"
270 			 "child: write shared fd\n"
271 			 "parent: write shared fd after child exit\n") ||
272 	    verify_file(tmpdir, "parent1", "parent: write parent fd 1\n") ||
273 	    verify_file(tmpdir, "parent2", "parent: write parent fd 2\n") ||
274 	    verify_file(tmpdir, "child", "child: write child fd\n"))
275 		goto errcleanup;
276 
277 	cleanup(tmpdir);
278 	exit(0);
279 
280 errcleanup:
281 	cleanup(tmpdir);
282 	exit(1);
283 }
284