1 /* SPDX-License-Identifier: MIT */
2 #include <errno.h>
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <fcntl.h>
8 #include <sys/mman.h>
9
10 #include "helpers.h"
11 #include "liburing.h"
12
13 #define BUF_SIZE (16 * 4096)
14
15 struct test_ctx {
16 int real_pipe1[2];
17 int real_pipe2[2];
18 int real_fd_in;
19 int real_fd_out;
20
21 /* fds or for registered files */
22 int pipe1[2];
23 int pipe2[2];
24 int fd_in;
25 int fd_out;
26
27 void *buf_in;
28 void *buf_out;
29 };
30
31 static unsigned int splice_flags = 0;
32 static unsigned int sqe_flags = 0;
33 static int has_splice = 0;
34 static int has_tee = 0;
35
read_buf(int fd,void * buf,int len)36 static int read_buf(int fd, void *buf, int len)
37 {
38 int ret;
39
40 while (len) {
41 ret = read(fd, buf, len);
42 if (ret < 0)
43 return ret;
44 len -= ret;
45 buf += ret;
46 }
47 return 0;
48 }
49
write_buf(int fd,const void * buf,int len)50 static int write_buf(int fd, const void *buf, int len)
51 {
52 int ret;
53
54 while (len) {
55 ret = write(fd, buf, len);
56 if (ret < 0)
57 return ret;
58 len -= ret;
59 buf += ret;
60 }
61 return 0;
62 }
63
check_content(int fd,void * buf,int len,const void * src)64 static int check_content(int fd, void *buf, int len, const void *src)
65 {
66 int ret;
67
68 ret = read_buf(fd, buf, len);
69 if (ret)
70 return ret;
71
72 ret = memcmp(buf, src, len);
73 return (ret != 0) ? -1 : 0;
74 }
75
create_file(const char * filename)76 static int create_file(const char *filename)
77 {
78 int fd, save_errno;
79
80 fd = open(filename, O_RDWR | O_CREAT, 0644);
81 save_errno = errno;
82 unlink(filename);
83 errno = save_errno;
84 return fd;
85 }
86
init_splice_ctx(struct test_ctx * ctx)87 static int init_splice_ctx(struct test_ctx *ctx)
88 {
89 int ret, rnd_fd;
90
91 ctx->buf_in = t_calloc(BUF_SIZE, 1);
92 ctx->buf_out = t_calloc(BUF_SIZE, 1);
93
94 ctx->fd_in = create_file(".splice-test-in");
95 if (ctx->fd_in < 0) {
96 perror("file open");
97 return 1;
98 }
99
100 ctx->fd_out = create_file(".splice-test-out");
101 if (ctx->fd_out < 0) {
102 perror("file open");
103 return 1;
104 }
105
106 /* get random data */
107 rnd_fd = open("/dev/urandom", O_RDONLY);
108 if (rnd_fd < 0)
109 return 1;
110
111 ret = read_buf(rnd_fd, ctx->buf_in, BUF_SIZE);
112 if (ret != 0)
113 return 1;
114 close(rnd_fd);
115
116 /* populate file */
117 ret = write_buf(ctx->fd_in, ctx->buf_in, BUF_SIZE);
118 if (ret)
119 return ret;
120
121 if (pipe(ctx->pipe1) < 0)
122 return 1;
123 if (pipe(ctx->pipe2) < 0)
124 return 1;
125
126 ctx->real_pipe1[0] = ctx->pipe1[0];
127 ctx->real_pipe1[1] = ctx->pipe1[1];
128 ctx->real_pipe2[0] = ctx->pipe2[0];
129 ctx->real_pipe2[1] = ctx->pipe2[1];
130 ctx->real_fd_in = ctx->fd_in;
131 ctx->real_fd_out = ctx->fd_out;
132 return 0;
133 }
134
do_splice_op(struct io_uring * ring,int fd_in,loff_t off_in,int fd_out,loff_t off_out,unsigned int len,__u8 opcode)135 static int do_splice_op(struct io_uring *ring,
136 int fd_in, loff_t off_in,
137 int fd_out, loff_t off_out,
138 unsigned int len,
139 __u8 opcode)
140 {
141 struct io_uring_cqe *cqe;
142 struct io_uring_sqe *sqe;
143 int ret = -1;
144
145 do {
146 sqe = io_uring_get_sqe(ring);
147 if (!sqe) {
148 fprintf(stderr, "get sqe failed\n");
149 return -1;
150 }
151 io_uring_prep_splice(sqe, fd_in, off_in, fd_out, off_out,
152 len, splice_flags);
153 sqe->flags |= sqe_flags;
154 sqe->user_data = 42;
155 sqe->opcode = opcode;
156
157 ret = io_uring_submit(ring);
158 if (ret != 1) {
159 fprintf(stderr, "sqe submit failed: %d\n", ret);
160 return ret;
161 }
162
163 ret = io_uring_wait_cqe(ring, &cqe);
164 if (ret < 0) {
165 fprintf(stderr, "wait completion %d\n", cqe->res);
166 return ret;
167 }
168
169 if (cqe->res <= 0) {
170 io_uring_cqe_seen(ring, cqe);
171 return cqe->res;
172 }
173
174 len -= cqe->res;
175 if (off_in != -1)
176 off_in += cqe->res;
177 if (off_out != -1)
178 off_out += cqe->res;
179 io_uring_cqe_seen(ring, cqe);
180 } while (len);
181
182 return 0;
183 }
184
do_splice(struct io_uring * ring,int fd_in,loff_t off_in,int fd_out,loff_t off_out,unsigned int len)185 static int do_splice(struct io_uring *ring,
186 int fd_in, loff_t off_in,
187 int fd_out, loff_t off_out,
188 unsigned int len)
189 {
190 return do_splice_op(ring, fd_in, off_in, fd_out, off_out, len,
191 IORING_OP_SPLICE);
192 }
193
do_tee(struct io_uring * ring,int fd_in,int fd_out,unsigned int len)194 static int do_tee(struct io_uring *ring, int fd_in, int fd_out,
195 unsigned int len)
196 {
197 return do_splice_op(ring, fd_in, 0, fd_out, 0, len, IORING_OP_TEE);
198 }
199
check_splice_support(struct io_uring * ring,struct test_ctx * ctx)200 static void check_splice_support(struct io_uring *ring, struct test_ctx *ctx)
201 {
202 int ret;
203
204 ret = do_splice(ring, -1, 0, -1, 0, BUF_SIZE);
205 has_splice = (ret == -EBADF);
206 }
207
check_tee_support(struct io_uring * ring,struct test_ctx * ctx)208 static void check_tee_support(struct io_uring *ring, struct test_ctx *ctx)
209 {
210 int ret;
211
212 ret = do_tee(ring, -1, -1, BUF_SIZE);
213 has_tee = (ret == -EBADF);
214 }
215
check_zero_splice(struct io_uring * ring,struct test_ctx * ctx)216 static int check_zero_splice(struct io_uring *ring, struct test_ctx *ctx)
217 {
218 int ret;
219
220 ret = do_splice(ring, ctx->fd_in, -1, ctx->pipe1[1], -1, 0);
221 if (ret)
222 return ret;
223
224 ret = do_splice(ring, ctx->pipe2[0], -1, ctx->pipe1[1], -1, 0);
225 if (ret)
226 return ret;
227
228 return 0;
229 }
230
splice_to_pipe(struct io_uring * ring,struct test_ctx * ctx)231 static int splice_to_pipe(struct io_uring *ring, struct test_ctx *ctx)
232 {
233 int ret;
234
235 ret = lseek(ctx->real_fd_in, 0, SEEK_SET);
236 if (ret)
237 return ret;
238
239 /* implicit file offset */
240 ret = do_splice(ring, ctx->fd_in, -1, ctx->pipe1[1], -1, BUF_SIZE);
241 if (ret)
242 return ret;
243
244 ret = check_content(ctx->real_pipe1[0], ctx->buf_out, BUF_SIZE,
245 ctx->buf_in);
246 if (ret)
247 return ret;
248
249 /* explicit file offset */
250 ret = do_splice(ring, ctx->fd_in, 0, ctx->pipe1[1], -1, BUF_SIZE);
251 if (ret)
252 return ret;
253
254 return check_content(ctx->real_pipe1[0], ctx->buf_out, BUF_SIZE,
255 ctx->buf_in);
256 }
257
splice_from_pipe(struct io_uring * ring,struct test_ctx * ctx)258 static int splice_from_pipe(struct io_uring *ring, struct test_ctx *ctx)
259 {
260 int ret;
261
262 ret = write_buf(ctx->real_pipe1[1], ctx->buf_in, BUF_SIZE);
263 if (ret)
264 return ret;
265 ret = do_splice(ring, ctx->pipe1[0], -1, ctx->fd_out, 0, BUF_SIZE);
266 if (ret)
267 return ret;
268 ret = check_content(ctx->real_fd_out, ctx->buf_out, BUF_SIZE,
269 ctx->buf_in);
270 if (ret)
271 return ret;
272
273 ret = ftruncate(ctx->real_fd_out, 0);
274 if (ret)
275 return ret;
276 return lseek(ctx->real_fd_out, 0, SEEK_SET);
277 }
278
splice_pipe_to_pipe(struct io_uring * ring,struct test_ctx * ctx)279 static int splice_pipe_to_pipe(struct io_uring *ring, struct test_ctx *ctx)
280 {
281 int ret;
282
283 ret = do_splice(ring, ctx->fd_in, 0, ctx->pipe1[1], -1, BUF_SIZE);
284 if (ret)
285 return ret;
286 ret = do_splice(ring, ctx->pipe1[0], -1, ctx->pipe2[1], -1, BUF_SIZE);
287 if (ret)
288 return ret;
289
290 return check_content(ctx->real_pipe2[0], ctx->buf_out, BUF_SIZE,
291 ctx->buf_in);
292 }
293
fail_splice_pipe_offset(struct io_uring * ring,struct test_ctx * ctx)294 static int fail_splice_pipe_offset(struct io_uring *ring, struct test_ctx *ctx)
295 {
296 int ret;
297
298 ret = do_splice(ring, ctx->fd_in, 0, ctx->pipe1[1], 0, BUF_SIZE);
299 if (ret != -ESPIPE && ret != -EINVAL)
300 return ret;
301
302 ret = do_splice(ring, ctx->pipe1[0], 0, ctx->fd_out, 0, BUF_SIZE);
303 if (ret != -ESPIPE && ret != -EINVAL)
304 return ret;
305
306 return 0;
307 }
308
fail_tee_nonpipe(struct io_uring * ring,struct test_ctx * ctx)309 static int fail_tee_nonpipe(struct io_uring *ring, struct test_ctx *ctx)
310 {
311 int ret;
312
313 ret = do_tee(ring, ctx->fd_in, ctx->pipe1[1], BUF_SIZE);
314 if (ret != -ESPIPE && ret != -EINVAL)
315 return ret;
316
317 return 0;
318 }
319
fail_tee_offset(struct io_uring * ring,struct test_ctx * ctx)320 static int fail_tee_offset(struct io_uring *ring, struct test_ctx *ctx)
321 {
322 int ret;
323
324 ret = do_splice_op(ring, ctx->pipe2[0], -1, ctx->pipe1[1], 0,
325 BUF_SIZE, IORING_OP_TEE);
326 if (ret != -ESPIPE && ret != -EINVAL)
327 return ret;
328
329 ret = do_splice_op(ring, ctx->pipe2[0], 0, ctx->pipe1[1], -1,
330 BUF_SIZE, IORING_OP_TEE);
331 if (ret != -ESPIPE && ret != -EINVAL)
332 return ret;
333
334 return 0;
335 }
336
check_tee(struct io_uring * ring,struct test_ctx * ctx)337 static int check_tee(struct io_uring *ring, struct test_ctx *ctx)
338 {
339 int ret;
340
341 ret = write_buf(ctx->real_pipe1[1], ctx->buf_in, BUF_SIZE);
342 if (ret)
343 return ret;
344 ret = do_tee(ring, ctx->pipe1[0], ctx->pipe2[1], BUF_SIZE);
345 if (ret)
346 return ret;
347
348 ret = check_content(ctx->real_pipe1[0], ctx->buf_out, BUF_SIZE,
349 ctx->buf_in);
350 if (ret) {
351 fprintf(stderr, "tee(), invalid src data\n");
352 return ret;
353 }
354
355 ret = check_content(ctx->real_pipe2[0], ctx->buf_out, BUF_SIZE,
356 ctx->buf_in);
357 if (ret) {
358 fprintf(stderr, "tee(), invalid dst data\n");
359 return ret;
360 }
361
362 return 0;
363 }
364
check_zero_tee(struct io_uring * ring,struct test_ctx * ctx)365 static int check_zero_tee(struct io_uring *ring, struct test_ctx *ctx)
366 {
367 return do_tee(ring, ctx->pipe2[0], ctx->pipe1[1], 0);
368 }
369
test_splice(struct io_uring * ring,struct test_ctx * ctx)370 static int test_splice(struct io_uring *ring, struct test_ctx *ctx)
371 {
372 int ret;
373
374 if (has_splice) {
375 ret = check_zero_splice(ring, ctx);
376 if (ret) {
377 fprintf(stderr, "check_zero_splice failed %i %i\n",
378 ret, errno);
379 return ret;
380 }
381
382 ret = splice_to_pipe(ring, ctx);
383 if (ret) {
384 fprintf(stderr, "splice_to_pipe failed %i %i\n",
385 ret, errno);
386 return ret;
387 }
388
389 ret = splice_from_pipe(ring, ctx);
390 if (ret) {
391 fprintf(stderr, "splice_from_pipe failed %i %i\n",
392 ret, errno);
393 return ret;
394 }
395
396 ret = splice_pipe_to_pipe(ring, ctx);
397 if (ret) {
398 fprintf(stderr, "splice_pipe_to_pipe failed %i %i\n",
399 ret, errno);
400 return ret;
401 }
402
403 ret = fail_splice_pipe_offset(ring, ctx);
404 if (ret) {
405 fprintf(stderr, "fail_splice_pipe_offset failed %i %i\n",
406 ret, errno);
407 return ret;
408 }
409 }
410
411 if (has_tee) {
412 ret = check_zero_tee(ring, ctx);
413 if (ret) {
414 fprintf(stderr, "check_zero_tee() failed %i %i\n",
415 ret, errno);
416 return ret;
417 }
418
419 ret = fail_tee_nonpipe(ring, ctx);
420 if (ret) {
421 fprintf(stderr, "fail_tee_nonpipe() failed %i %i\n",
422 ret, errno);
423 return ret;
424 }
425
426 ret = fail_tee_offset(ring, ctx);
427 if (ret) {
428 fprintf(stderr, "fail_tee_offset failed %i %i\n",
429 ret, errno);
430 return ret;
431 }
432
433 ret = check_tee(ring, ctx);
434 if (ret) {
435 fprintf(stderr, "check_tee() failed %i %i\n",
436 ret, errno);
437 return ret;
438 }
439 }
440
441 return 0;
442 }
443
main(int argc,char * argv[])444 int main(int argc, char *argv[])
445 {
446 struct io_uring ring;
447 struct io_uring_params p = { };
448 struct test_ctx ctx;
449 int ret;
450 int reg_fds[6];
451
452 if (argc > 1)
453 return 0;
454
455 ret = io_uring_queue_init_params(8, &ring, &p);
456 if (ret) {
457 fprintf(stderr, "ring setup failed\n");
458 return 1;
459 }
460 if (!(p.features & IORING_FEAT_FAST_POLL)) {
461 fprintf(stdout, "No splice support, skipping\n");
462 return 0;
463 }
464
465 ret = init_splice_ctx(&ctx);
466 if (ret) {
467 fprintf(stderr, "init failed %i %i\n", ret, errno);
468 return 1;
469 }
470
471 check_splice_support(&ring, &ctx);
472 if (!has_splice)
473 fprintf(stdout, "skip, doesn't support splice()\n");
474 check_tee_support(&ring, &ctx);
475 if (!has_tee)
476 fprintf(stdout, "skip, doesn't support tee()\n");
477
478 ret = test_splice(&ring, &ctx);
479 if (ret) {
480 fprintf(stderr, "basic splice tests failed\n");
481 return ret;
482 }
483
484 reg_fds[0] = ctx.real_pipe1[0];
485 reg_fds[1] = ctx.real_pipe1[1];
486 reg_fds[2] = ctx.real_pipe2[0];
487 reg_fds[3] = ctx.real_pipe2[1];
488 reg_fds[4] = ctx.real_fd_in;
489 reg_fds[5] = ctx.real_fd_out;
490 ret = io_uring_register_files(&ring, reg_fds, 6);
491 if (ret) {
492 fprintf(stderr, "%s: register ret=%d\n", __FUNCTION__, ret);
493 return 1;
494 }
495
496 /* remap fds to registered */
497 ctx.pipe1[0] = 0;
498 ctx.pipe1[1] = 1;
499 ctx.pipe2[0] = 2;
500 ctx.pipe2[1] = 3;
501 ctx.fd_in = 4;
502 ctx.fd_out = 5;
503
504 splice_flags = SPLICE_F_FD_IN_FIXED;
505 sqe_flags = IOSQE_FIXED_FILE;
506 ret = test_splice(&ring, &ctx);
507 if (ret) {
508 fprintf(stderr, "registered fds splice tests failed\n");
509 return ret;
510 }
511 return 0;
512 }
513