xref: /aosp_15_r20/external/liburing/test/splice.c (revision 25da2bea747f3a93b4c30fd9708b0618ef55a0e6)
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