aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/liburing/test/across-fork.c
blob: 8d6d9a784ff2c3bbcbbbafd61b38ab19c18a0bbd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#include "../config-host.h"
/* SPDX-License-Identifier: MIT */
/*
 * Description: test sharing a ring across a fork
 */
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "liburing.h"
#include "helpers.h"


struct forktestmem
{
	struct io_uring ring;
	pthread_barrier_t barrier;
	pthread_barrierattr_t barrierattr;
};

static int open_tempfile(const char *dir, const char *fname)
{
	int fd;
	char buf[32];

	snprintf(buf, sizeof(buf), "%s/%s",
		 dir, fname);
	fd = open(buf, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	return fd;
}

static int submit_write(struct io_uring *ring, int fd, const char *str,
			int wait)
{
	struct io_uring_sqe *sqe;
	struct iovec iovec;
	int ret;

	sqe = io_uring_get_sqe(ring);
	if (!sqe) {
		fprintf(stderr, "could not get sqe\n");
		return 1;
	}

	iovec.iov_base = (char *) str;
	iovec.iov_len = strlen(str);
	io_uring_prep_writev(sqe, fd, &iovec, 1, 0);
	ret = io_uring_submit_and_wait(ring, wait);
	if (ret < 0) {
		fprintf(stderr, "submit failed: %s\n", strerror(-ret));
		return 1;
	}

	return 0;
}

static int wait_cqe(struct io_uring *ring, const char *stage)
{
	struct io_uring_cqe *cqe;
	int ret;

	ret = io_uring_wait_cqe(ring, &cqe);
	if (ret) {
		fprintf(stderr, "%s wait_cqe failed %d\n", stage, ret);
		return 1;
	}
	if (cqe->res < 0) {
		fprintf(stderr, "%s cqe failed %d\n", stage, cqe->res);
		return 1;
	}

	io_uring_cqe_seen(ring, cqe);
	return 0;
}

static int verify_file(const char *tmpdir, const char *fname, const char* expect)
{
	int fd;
	char buf[512];
	int err = 0;

	memset(buf, 0, sizeof(buf));

	fd = open_tempfile(tmpdir, fname);
	if (fd < 0)
		return 1;

	if (read(fd, buf, sizeof(buf) - 1) < 0)
		return 1;

	if (strcmp(buf, expect) != 0) {
		fprintf(stderr, "content mismatch for %s\n"
			"got:\n%s\n"
			"expected:\n%s\n",
			fname, buf, expect);
		err = 1;
	}

	close(fd);
	return err;
}

static void cleanup(const char *tmpdir)
{
	char buf[32];

	/* don't check errors, called during partial runs */

	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "shared");
	unlink(buf);

	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent1");
	unlink(buf);

	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent2");
	unlink(buf);

	snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "child");
	unlink(buf);

	rmdir(tmpdir);
}

int main(int argc, char *argv[])
{
	struct forktestmem *shmem;
	char tmpdir[] = "forktmpXXXXXX";
	int shared_fd;
	int ret;
	pid_t p;

	if (argc > 1)
		return T_EXIT_SKIP;

	shmem = mmap(0, sizeof(struct forktestmem), PROT_READ|PROT_WRITE,
		   MAP_SHARED | MAP_ANONYMOUS, 0, 0);
	if (!shmem) {
		fprintf(stderr, "mmap failed\n");
		exit(T_EXIT_FAIL);
	}

	pthread_barrierattr_init(&shmem->barrierattr);
	pthread_barrierattr_setpshared(&shmem->barrierattr, 1);
	pthread_barrier_init(&shmem->barrier, &shmem->barrierattr, 2);

	ret = io_uring_queue_init(10, &shmem->ring, 0);
	if (ret < 0) {
		fprintf(stderr, "queue init failed\n");
		exit(T_EXIT_FAIL);
	}

	if (mkdtemp(tmpdir) == NULL) {
		fprintf(stderr, "temp directory creation failed\n");
		exit(T_EXIT_FAIL);
	}

	shared_fd = open_tempfile(tmpdir, "shared");

	/*
	 * First do a write before the fork, to test whether child can
	 * reap that
	 */
	if (submit_write(&shmem->ring, shared_fd, "before fork: write shared fd\n", 0))
		goto errcleanup;

	p = fork();
	switch (p) {
	case -1:
		fprintf(stderr, "fork failed\n");
		goto errcleanup;

	default: {
		/* parent */
		int parent_fd1;
		int parent_fd2;
		int wstatus;

		/* wait till fork is started up */
		pthread_barrier_wait(&shmem->barrier);

		parent_fd1 = open_tempfile(tmpdir, "parent1");
		parent_fd2 = open_tempfile(tmpdir, "parent2");

		/* do a parent write to the shared fd */
		if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd\n", 0))
			goto errcleanup;

		/* do a parent write to an fd where same numbered fd exists in child */
		if (submit_write(&shmem->ring, parent_fd1, "parent: write parent fd 1\n", 0))
			goto errcleanup;

		/* do a parent write to an fd where no same numbered fd exists in child */
		if (submit_write(&shmem->ring, parent_fd2, "parent: write parent fd 2\n", 0))
			goto errcleanup;

		/* wait to switch read/writ roles with child */
		pthread_barrier_wait(&shmem->barrier);

		/* now wait for child to exit, to ensure we still can read completion */
		waitpid(p, &wstatus, 0);
		if (WEXITSTATUS(wstatus) != 0) {
			fprintf(stderr, "child failed\n");
			goto errcleanup;
		}

		if (wait_cqe(&shmem->ring, "p cqe 1"))
			goto errcleanup;

		if (wait_cqe(&shmem->ring, "p cqe 2"))
			goto errcleanup;

		/* check that IO can still be submitted after child exited */
		if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd after child exit\n", 0))
			goto errcleanup;

		if (wait_cqe(&shmem->ring, "p cqe 3"))
			goto errcleanup;

		break;
	}
	case 0: {
		/* child */
		int child_fd;

		/* wait till fork is started up */
		pthread_barrier_wait(&shmem->barrier);

		child_fd = open_tempfile(tmpdir, "child");

		if (wait_cqe(&shmem->ring, "c cqe shared"))
			exit(1);

		if (wait_cqe(&shmem->ring, "c cqe parent 1"))
			exit(1);

		if (wait_cqe(&shmem->ring, "c cqe parent 2"))
			exit(1);

		if (wait_cqe(&shmem->ring, "c cqe parent 3"))
			exit(1);

		/* wait to switch read/writ roles with parent */
		pthread_barrier_wait(&shmem->barrier);

		if (submit_write(&shmem->ring, child_fd, "child: write child fd\n", 0))
			exit(1);

		/* ensure both writes have finished before child exits */
		if (submit_write(&shmem->ring, shared_fd, "child: write shared fd\n", 2))
			exit(1);

		exit(0);
	}
	}

	if (verify_file(tmpdir, "shared",
			 "before fork: write shared fd\n"
			 "parent: write shared fd\n"
			 "child: write shared fd\n"
			 "parent: write shared fd after child exit\n") ||
	    verify_file(tmpdir, "parent1", "parent: write parent fd 1\n") ||
	    verify_file(tmpdir, "parent2", "parent: write parent fd 2\n") ||
	    verify_file(tmpdir, "child", "child: write child fd\n"))
		goto errcleanup;

	cleanup(tmpdir);
	exit(T_EXIT_PASS);

errcleanup:
	cleanup(tmpdir);
	exit(T_EXIT_FAIL);
}