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