1 /* SPDX-License-Identifier: MIT */
2 /*
3 * Description: basic read/write tests with buffered, O_DIRECT, and SQPOLL
4 */
5 #include <errno.h>
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <fcntl.h>
11 #include <sys/types.h>
12 #include <poll.h>
13 #include <sys/eventfd.h>
14 #include <sys/resource.h>
15
16 #include "helpers.h"
17 #include "liburing.h"
18
19 #define FILE_SIZE (256 * 1024)
20 #define BS 8192
21 #define BUFFERS (FILE_SIZE / BS)
22
23 static struct iovec *vecs;
24 static int no_read;
25 static int warned;
26
fdinfo_read(struct io_uring * ring)27 static void fdinfo_read(struct io_uring *ring)
28 {
29 char fd_name[128];
30 char *buf;
31 int fd;
32
33 buf = malloc(4096);
34
35 sprintf(fd_name, "/proc/self/fdinfo/%d", ring->ring_fd);
36 fd = open(fd_name, O_RDONLY);
37 if (fd < 0) {
38 perror("open");
39 return;
40 }
41
42 do {
43 int ret = read(fd, buf, 4096);
44
45 if (ret < 0) {
46 perror("fdinfo read");
47 break;
48 } else if (ret == 4096) {
49 continue;
50 }
51 break;
52 } while (1);
53
54 close(fd);
55 free(buf);
56 }
57
__test_io(const char * file,struct io_uring * ring,int write,int buffered,int sqthread,int fixed,int nonvec,int buf_select,int seq,int exp_len)58 static int __test_io(const char *file, struct io_uring *ring, int write,
59 int buffered, int sqthread, int fixed, int nonvec,
60 int buf_select, int seq, int exp_len)
61 {
62 struct io_uring_sqe *sqe;
63 struct io_uring_cqe *cqe;
64 int open_flags;
65 int i, fd = -1, ret;
66 off_t offset;
67
68 #ifdef VERBOSE
69 fprintf(stdout, "%s: start %d/%d/%d/%d/%d: ", __FUNCTION__, write,
70 buffered, sqthread,
71 fixed, nonvec);
72 #endif
73 if (write)
74 open_flags = O_WRONLY;
75 else
76 open_flags = O_RDONLY;
77 if (!buffered)
78 open_flags |= O_DIRECT;
79
80 if (fixed) {
81 ret = t_register_buffers(ring, vecs, BUFFERS);
82 if (ret == T_SETUP_SKIP)
83 return T_EXIT_SKIP;
84 if (ret != T_SETUP_OK) {
85 fprintf(stderr, "buffer reg failed: %d\n", ret);
86 goto err;
87 }
88 }
89
90 fd = open(file, open_flags);
91 if (fd < 0) {
92 if (errno == EINVAL)
93 return 0;
94 if (errno == EPERM || errno == EACCES)
95 return T_EXIT_SKIP;
96 perror("file open");
97 goto err;
98 }
99
100 if (sqthread) {
101 ret = io_uring_register_files(ring, &fd, 1);
102 if (ret) {
103 fprintf(stderr, "file reg failed: %d\n", ret);
104 goto err;
105 }
106 }
107
108 offset = 0;
109 for (i = 0; i < BUFFERS; i++) {
110 sqe = io_uring_get_sqe(ring);
111 if (!sqe) {
112 fprintf(stderr, "sqe get failed\n");
113 goto err;
114 }
115 if (!seq)
116 offset = BS * (rand() % BUFFERS);
117 if (write) {
118 int do_fixed = fixed;
119 int use_fd = fd;
120
121 if (sqthread)
122 use_fd = 0;
123 if (fixed && (i & 1))
124 do_fixed = 0;
125 if (do_fixed) {
126 io_uring_prep_write_fixed(sqe, use_fd, vecs[i].iov_base,
127 vecs[i].iov_len,
128 offset, i);
129 } else if (nonvec) {
130 io_uring_prep_write(sqe, use_fd, vecs[i].iov_base,
131 vecs[i].iov_len, offset);
132 } else {
133 io_uring_prep_writev(sqe, use_fd, &vecs[i], 1,
134 offset);
135 }
136 } else {
137 int do_fixed = fixed;
138 int use_fd = fd;
139
140 if (sqthread)
141 use_fd = 0;
142 if (fixed && (i & 1))
143 do_fixed = 0;
144 if (do_fixed) {
145 io_uring_prep_read_fixed(sqe, use_fd, vecs[i].iov_base,
146 vecs[i].iov_len,
147 offset, i);
148 } else if (nonvec) {
149 io_uring_prep_read(sqe, use_fd, vecs[i].iov_base,
150 vecs[i].iov_len, offset);
151 } else {
152 io_uring_prep_readv(sqe, use_fd, &vecs[i], 1,
153 offset);
154 }
155
156 }
157 sqe->user_data = i;
158 if (sqthread)
159 sqe->flags |= IOSQE_FIXED_FILE;
160 if (buf_select) {
161 if (nonvec)
162 sqe->addr = 0;
163 sqe->flags |= IOSQE_BUFFER_SELECT;
164 sqe->buf_group = buf_select;
165 }
166 if (seq)
167 offset += BS;
168 }
169
170 fdinfo_read(ring);
171
172 ret = io_uring_submit(ring);
173 if (ret != BUFFERS) {
174 fprintf(stderr, "submit got %d, wanted %d\n", ret, BUFFERS);
175 goto err;
176 }
177
178 for (i = 0; i < 10; i++) {
179 fdinfo_read(ring);
180 usleep(2);
181 }
182
183 for (i = 0; i < BUFFERS; i++) {
184 ret = io_uring_wait_cqe(ring, &cqe);
185 if (ret) {
186 fprintf(stderr, "wait_cqe=%d\n", ret);
187 goto err;
188 }
189 if (cqe->res == -EINVAL && nonvec) {
190 if (!warned) {
191 fprintf(stdout, "Non-vectored IO not "
192 "supported, skipping\n");
193 warned = 1;
194 no_read = 1;
195 }
196 } else if (exp_len == -1) {
197 int iov_len = vecs[cqe->user_data].iov_len;
198
199 if (cqe->res != iov_len) {
200 fprintf(stderr, "cqe res %d, wanted %d\n",
201 cqe->res, iov_len);
202 goto err;
203 }
204 } else if (cqe->res != exp_len) {
205 fprintf(stderr, "cqe res %d, wanted %d\n", cqe->res, exp_len);
206 goto err;
207 }
208 if (buf_select && exp_len == BS) {
209 int bid = cqe->flags >> 16;
210 unsigned char *ptr = vecs[bid].iov_base;
211 int j;
212
213 for (j = 0; j < BS; j++) {
214 if (ptr[j] == cqe->user_data)
215 continue;
216
217 fprintf(stderr, "Data mismatch! bid=%d, "
218 "wanted=%d, got=%d\n", bid,
219 (int)cqe->user_data, ptr[j]);
220 return 1;
221 }
222 }
223 io_uring_cqe_seen(ring, cqe);
224 }
225
226 if (fixed) {
227 ret = io_uring_unregister_buffers(ring);
228 if (ret) {
229 fprintf(stderr, "buffer unreg failed: %d\n", ret);
230 goto err;
231 }
232 }
233 if (sqthread) {
234 ret = io_uring_unregister_files(ring);
235 if (ret) {
236 fprintf(stderr, "file unreg failed: %d\n", ret);
237 goto err;
238 }
239 }
240
241 close(fd);
242 #ifdef VERBOSE
243 fprintf(stdout, "PASS\n");
244 #endif
245 return 0;
246 err:
247 #ifdef VERBOSE
248 fprintf(stderr, "FAILED\n");
249 #endif
250 if (fd != -1)
251 close(fd);
252 return 1;
253 }
test_io(const char * file,int write,int buffered,int sqthread,int fixed,int nonvec,int exp_len)254 static int test_io(const char *file, int write, int buffered, int sqthread,
255 int fixed, int nonvec, int exp_len)
256 {
257 struct io_uring ring;
258 int ret, ring_flags = 0;
259
260 if (sqthread)
261 ring_flags = IORING_SETUP_SQPOLL;
262
263 ret = t_create_ring(64, &ring, ring_flags);
264 if (ret == T_SETUP_SKIP)
265 return 0;
266 if (ret != T_SETUP_OK) {
267 fprintf(stderr, "ring create failed: %d\n", ret);
268 return 1;
269 }
270
271 ret = __test_io(file, &ring, write, buffered, sqthread, fixed, nonvec,
272 0, 0, exp_len);
273 io_uring_queue_exit(&ring);
274 return ret;
275 }
276
has_nonvec_read(void)277 static int has_nonvec_read(void)
278 {
279 struct io_uring_probe *p;
280 struct io_uring ring;
281 int ret;
282
283 ret = io_uring_queue_init(1, &ring, 0);
284 if (ret) {
285 fprintf(stderr, "queue init failed: %d\n", ret);
286 exit(ret);
287 }
288
289 p = t_calloc(1, sizeof(*p) + 256 * sizeof(struct io_uring_probe_op));
290 ret = io_uring_register_probe(&ring, p, 256);
291 /* if we don't have PROBE_REGISTER, we don't have OP_READ/WRITE */
292 if (ret == -EINVAL) {
293 out:
294 io_uring_queue_exit(&ring);
295 free(p);
296 return 0;
297 } else if (ret) {
298 fprintf(stderr, "register_probe: %d\n", ret);
299 goto out;
300 }
301
302 if (p->ops_len <= IORING_OP_READ)
303 goto out;
304 if (!(p->ops[IORING_OP_READ].flags & IO_URING_OP_SUPPORTED))
305 goto out;
306 io_uring_queue_exit(&ring);
307 free(p);
308 return 1;
309 }
310
test_eventfd_read(int flags)311 static int test_eventfd_read(int flags)
312 {
313 struct io_uring ring;
314 int fd, ret;
315 eventfd_t event;
316 struct io_uring_sqe *sqe;
317 struct io_uring_cqe *cqe;
318
319 if (no_read)
320 return 0;
321 ret = t_create_ring(64, &ring, flags);
322 if (ret == T_SETUP_SKIP)
323 return 0;
324 if (ret != T_SETUP_OK) {
325 if (ret == -EINVAL)
326 return 0;
327 fprintf(stderr, "ring create failed: %d\n", ret);
328 return 1;
329 }
330
331 fd = eventfd(1, 0);
332 if (fd < 0) {
333 perror("eventfd");
334 return 1;
335 }
336 sqe = io_uring_get_sqe(&ring);
337 io_uring_prep_read(sqe, fd, &event, sizeof(eventfd_t), 0);
338 ret = io_uring_submit(&ring);
339 if (ret != 1) {
340 fprintf(stderr, "submitted %d\n", ret);
341 return 1;
342 }
343 fdinfo_read(&ring);
344 eventfd_write(fd, 1);
345 ret = io_uring_wait_cqe(&ring, &cqe);
346 if (ret) {
347 fprintf(stderr, "wait_cqe=%d\n", ret);
348 return 1;
349 }
350 if (cqe->res == -EINVAL) {
351 fprintf(stdout, "eventfd IO not supported, skipping\n");
352 } else if (cqe->res != sizeof(eventfd_t)) {
353 fprintf(stderr, "cqe res %d, wanted %d\n", cqe->res,
354 (int) sizeof(eventfd_t));
355 return 1;
356 }
357 io_uring_cqe_seen(&ring, cqe);
358 return 0;
359 }
360
main(int argc,char * argv[])361 int main(int argc, char *argv[])
362 {
363 int i, ret, nr;
364 char buf[256];
365 char *fname;
366
367 if (argc > 1) {
368 fname = argv[1];
369 } else {
370 srand((unsigned)time(NULL));
371 snprintf(buf, sizeof(buf), ".basic-rw-%u-%u",
372 (unsigned)rand(), (unsigned)getpid());
373 fname = buf;
374 t_create_file(fname, FILE_SIZE);
375 }
376
377 signal(SIGXFSZ, SIG_IGN);
378
379 vecs = t_create_buffers(BUFFERS, BS);
380
381 /* if we don't have nonvec read, skip testing that */
382 nr = has_nonvec_read() ? 32 : 16;
383
384 for (i = 0; i < nr; i++) {
385 int write = (i & 1) != 0;
386 int buffered = (i & 2) != 0;
387 int sqthread = (i & 4) != 0;
388 int fixed = (i & 8) != 0;
389 int nonvec = (i & 16) != 0;
390
391 ret = test_io(fname, write, buffered, sqthread, fixed, nonvec,
392 BS);
393 if (ret == T_EXIT_SKIP)
394 continue;
395 if (ret) {
396 fprintf(stderr, "test_io failed %d/%d/%d/%d/%d\n",
397 write, buffered, sqthread, fixed, nonvec);
398 goto err;
399 }
400 }
401
402 ret = test_eventfd_read(0);
403 if (ret) {
404 fprintf(stderr, "eventfd read 0 failed\n");
405 goto err;
406 }
407
408 ret = test_eventfd_read(IORING_SETUP_DEFER_TASKRUN|IORING_SETUP_SINGLE_ISSUER);
409 if (ret) {
410 fprintf(stderr, "eventfd read defer failed\n");
411 goto err;
412 }
413
414 ret = test_eventfd_read(IORING_SETUP_SQPOLL);
415 if (ret) {
416 fprintf(stderr, "eventfd read sqpoll failed\n");
417 goto err;
418 }
419
420 if (fname != argv[1])
421 unlink(fname);
422 return 0;
423 err:
424 if (fname != argv[1])
425 unlink(fname);
426 return 1;
427 }
428