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