Add registered buffer copy support Also add test cases: regbuf-copy.c: test various register/unregister/copy functions read-write.c: add operations on copied buffers Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/man/io_uring_copy_buffers.3 b/man/io_uring_copy_buffers.3 new file mode 100644 index 0000000..749fdec --- /dev/null +++ b/man/io_uring_copy_buffers.3
@@ -0,0 +1,64 @@ +.\" Copyright (C) 2024 Jens Axboe <axboe@kernel.dk> +.\" +.\" SPDX-License-Identifier: LGPL-2.0-or-later +.\" +.TH io_uring_copy_buffers 3 "September 12, 2024" "liburing-2.8" "liburing Manual" +.SH NAME +io_uring_copy_buffers \- Copies registered buffers between rings +.SH SYNOPSIS +.nf +.B #include <liburing.h> +.PP +.BI "int io_uring_copy_buffers(struct io_uring *" dst "," +.BI " struct io_uring * " src ");" +.PP +.fi +.SH DESCRIPTION +.PP +The +.BR io_uring_copy_buffers (3) +function copies registered buffers from the ring indicated by +.I src +to the ring indicated by +.I dst . +Upon successful completion of this operation, +.I src +and +.I dst +will have the same set of registered buffers. This operation is identical to +performing a +.BR io_uring_register_buffers (3) +operation on the +.I dst +ring, if the +.I src +ring previously had that same buffer registration operating done. + +The +.I dst +ring must not have any buffers currently registered. If buffers are currently +registered on the destination ring, they must be unregistered with +.BR io_uring_unregister_buffers (3) +first. + +On success +.BR io_uring_copy_buffers (3) +returns 0. +On failure, it returns +.BR -errno , +specifically +.TP +.B -EBUSY +The destination ring already has buffers registered. +.TP +.B -ENOMEM +The kernel ran out of memory. +.TP +.B -ENXIO +The source ring doesn't have any buffers registered. +.SH SEE ALSO +.BR io_uring_register (2), +.BR io_uring_unregister_buffers (3), +.BR io_uring_register_buffers (3), +.BR io_uring_prep_read_fixed (3), +.BR io_uring_prep_write_fixed (3)
diff --git a/man/io_uring_register_buffers.3 b/man/io_uring_register_buffers.3 index 00861d9..04576b4 100644 --- a/man/io_uring_register_buffers.3 +++ b/man/io_uring_register_buffers.3
@@ -99,6 +99,7 @@ .BR io_uring_register (2), .BR io_uring_get_sqe (3), .BR io_uring_unregister_buffers (3), +.BR io_uring_copy_buffers (3), .BR io_uring_register_buf_ring (3), .BR io_uring_prep_read_fixed (3), .BR io_uring_prep_write_fixed (3)
diff --git a/src/include/liburing.h b/src/include/liburing.h index 0dc445d..7c198f3 100644 --- a/src/include/liburing.h +++ b/src/include/liburing.h
@@ -196,6 +196,7 @@ unsigned min_wait, sigset_t *sigmask); +int io_uring_copy_buffers(struct io_uring *dst, struct io_uring *src); int io_uring_register_buffers(struct io_uring *ring, const struct iovec *iovecs, unsigned nr_iovecs); int io_uring_register_buffers_tags(struct io_uring *ring,
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h index 4c5a17e..3372e4f 100644 --- a/src/include/liburing/io_uring.h +++ b/src/include/liburing/io_uring.h
@@ -609,6 +609,9 @@ IORING_REGISTER_CLOCK = 29, + /* copy registered buffers from source ring to current ring */ + IORING_REGISTER_COPY_BUFFERS = 30, + /* this goes last */ IORING_REGISTER_LAST, @@ -694,6 +697,16 @@ __u32 __resv[3]; }; +enum { + IORING_REGISTER_SRC_REGISTERED = 1, +}; + +struct io_uring_copy_buffers { + __u32 src_fd; + __u32 flags; + __u32 pad[6]; +}; + struct io_uring_buf { __u64 addr; __u32 len;
diff --git a/src/liburing-ffi.map b/src/liburing-ffi.map index f711692..12fb450 100644 --- a/src/liburing-ffi.map +++ b/src/liburing-ffi.map
@@ -210,4 +210,5 @@ io_uring_register_clock; io_uring_submit_and_wait_min_timeout; io_uring_wait_cqes_min_timeout; + io_uring_copy_buffers; } LIBURING_2.7;
diff --git a/src/liburing.map b/src/liburing.map index 22498bc..b48b48f 100644 --- a/src/liburing.map +++ b/src/liburing.map
@@ -101,4 +101,5 @@ io_uring_register_clock; io_uring_submit_and_wait_min_timeout; io_uring_wait_cqes_min_timeout; + io_uring_copy_buffers; } LIBURING_2.7;
diff --git a/src/register.c b/src/register.c index b370f07..c81bc4d 100644 --- a/src/register.c +++ b/src/register.c
@@ -373,3 +373,17 @@ { return do_register(ring, IORING_REGISTER_CLOCK, arg, 0); } + +int io_uring_copy_buffers(struct io_uring *dst, struct io_uring *src) +{ + struct io_uring_copy_buffers buf = { .src_fd = src->ring_fd, }; + + if (src->int_flags & INT_FLAG_REG_REG_RING) { + buf.src_fd = src->enter_ring_fd; + buf.flags = IORING_REGISTER_SRC_REGISTERED; + } else { + buf.src_fd = src->ring_fd; + } + + return do_register(dst, IORING_REGISTER_COPY_BUFFERS, &buf, 1); +}
diff --git a/test/Makefile b/test/Makefile index b1bc396..1e1a861 100644 --- a/test/Makefile +++ b/test/Makefile
@@ -167,6 +167,7 @@ reg-fd-only.c \ reg-hint.c \ reg-reg-ring.c \ + regbuf-copy.c \ regbuf-merge.c \ register-restrictions.c \ rename.c \
diff --git a/test/read-write.c b/test/read-write.c index 27b0cc7..98c1edd 100644 --- a/test/read-write.c +++ b/test/read-write.c
@@ -15,6 +15,7 @@ #include "helpers.h" #include "liburing.h" +#include "../src/syscall.h" #define FILE_SIZE (256 * 1024) #define BS 8192 @@ -23,6 +24,7 @@ static struct iovec *vecs; static int no_read; static int no_buf_select; +static int no_buf_copy; static int warned; static int create_nonaligned_buffers(void) @@ -42,9 +44,9 @@ return 0; } -static int __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) +static int _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) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; @@ -64,7 +66,7 @@ if (!buffered) open_flags |= O_DIRECT; - if (fixed) { + if (fixed == 1) { ret = t_register_buffers(ring, vecs, BUFFERS); if (ret == T_SETUP_SKIP) return 0; @@ -201,13 +203,6 @@ io_uring_cqe_seen(ring, cqe); } - if (fixed) { - ret = io_uring_unregister_buffers(ring); - if (ret) { - fprintf(stderr, "buffer unreg failed: %d\n", ret); - goto err; - } - } if (sqthread) { ret = io_uring_unregister_files(ring); if (ret) { @@ -229,6 +224,65 @@ close(fd); return 1; } + +static int __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) +{ + int ret; + + ret = _test_io(file, ring, write, buffered, sqthread, fixed, nonvec, + buf_select, seq, exp_len); + if (ret) + return ret; + + if (fixed) { + struct io_uring ring2; + int ring_flags = 0; + + if (no_buf_copy) + return 0; + if (sqthread) + ring_flags = IORING_SETUP_SQPOLL; + ret = t_create_ring(64, &ring2, ring_flags); + if (ret == T_SETUP_SKIP) + return 0; + if (ret != T_SETUP_OK) { + fprintf(stderr, "ring create failed: %d\n", ret); + return 1; + } + + ret = io_uring_copy_buffers(&ring2, ring); + if (ret) { + if (ret == -EINVAL) { + no_buf_copy = 1; + io_uring_queue_exit(&ring2); + return 0; + } + fprintf(stderr, "copy buffers: %d\n", ret); + return ret; + } + ret = _test_io(file, &ring2, write, buffered, sqthread, 2, + nonvec, buf_select, seq, exp_len); + if (ret) + return ret; + + ret = io_uring_unregister_buffers(ring); + if (ret) { + fprintf(stderr, "buffer unreg failed: %d\n", ret); + return ret; + } + ret = io_uring_unregister_buffers(&ring2); + if (ret) { + fprintf(stderr, "buffer copy unreg failed: %d\n", ret); + return ret; + } + io_uring_queue_exit(&ring2); + } + + return ret; +} + static int test_io(const char *file, int write, int buffered, int sqthread, int fixed, int nonvec, int exp_len) {
diff --git a/test/regbuf-copy.c b/test/regbuf-copy.c new file mode 100644 index 0000000..5080b31 --- /dev/null +++ b/test/regbuf-copy.c
@@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Description: test buffer copying between rings + * + */ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/uio.h> + +#include "liburing.h" +#include "helpers.h" + +#define NR_VECS 64 +#define BUF_SIZE 8192 + +static int test(int reg_src, int reg_dst) +{ + struct iovec vecs[NR_VECS]; + struct io_uring src, dst; + int ret, i; + + ret = io_uring_queue_init(1, &src, 0); + if (ret) { + fprintf(stderr, "ring_init: %d\n", ret); + return T_EXIT_FAIL; + } + ret = io_uring_queue_init(1, &dst, 0); + if (ret) { + fprintf(stderr, "ring_init: %d\n", ret); + return T_EXIT_FAIL; + } + if (reg_src) { + ret = io_uring_register_ring_fd(&src); + if (ret < 0) { + if (ret == -EINVAL) + return T_EXIT_SKIP; + fprintf(stderr, "register ring: %d\n", ret); + return T_EXIT_FAIL; + } + } + if (reg_dst) { + ret = io_uring_register_ring_fd(&dst); + if (ret < 0) { + if (ret == -EINVAL) + return T_EXIT_SKIP; + fprintf(stderr, "register ring: %d\n", ret); + return T_EXIT_FAIL; + } + } + + /* test fail with no buffers in src */ + ret = io_uring_copy_buffers(&dst, &src); + if (ret == -EINVAL) { + /* no buffer copy support */ + return T_EXIT_SKIP; + } else if (ret != -ENXIO) { + fprintf(stderr, "empty copy: %d\n", ret); + return T_EXIT_FAIL; + } + + for (i = 0; i < NR_VECS; i++) { + if (posix_memalign(&vecs[i].iov_base, 4096, BUF_SIZE)) + return T_EXIT_FAIL; + vecs[i].iov_len = BUF_SIZE; + } + + ret = io_uring_register_buffers(&src, vecs, NR_VECS); + if (ret < 0) { + if (ret == -ENOMEM) + return T_EXIT_SKIP; + return T_EXIT_FAIL; + } + + /* copy should work now */ + ret = io_uring_copy_buffers(&dst, &src); + if (ret) { + fprintf(stderr, "buffer copy: %d\n", ret); + return T_EXIT_FAIL; + } + + /* try copy again, should get -EBUSY */ + ret = io_uring_copy_buffers(&dst, &src); + if (ret != -EBUSY) { + fprintf(stderr, "busy copy: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_unregister_buffers(&dst); + if (ret) { + fprintf(stderr, "dst unregister buffers: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_unregister_buffers(&dst); + if (ret != -ENXIO) { + fprintf(stderr, "dst unregister empty buffers: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_unregister_buffers(&src); + if (ret) { + fprintf(stderr, "src unregister buffers: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_register_buffers(&dst, vecs, NR_VECS); + if (ret < 0) { + fprintf(stderr, "register buffers dst; %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_copy_buffers(&src, &dst); + if (ret) { + fprintf(stderr, "buffer copy reverse: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_unregister_buffers(&dst); + if (ret) { + fprintf(stderr, "dst unregister buffers: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_unregister_buffers(&dst); + if (ret != -ENXIO) { + fprintf(stderr, "dst unregister empty buffers: %d\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_unregister_buffers(&src); + if (ret) { + fprintf(stderr, "src unregister buffers: %d\n", ret); + return T_EXIT_FAIL; + } + + io_uring_queue_exit(&src); + io_uring_queue_exit(&dst); + + for (i = 0; i < NR_VECS; i++) + free(vecs[i].iov_base); + + return T_EXIT_PASS; +} + +int main(int argc, char *argv[]) +{ + int ret; + + if (argc > 1) + return T_EXIT_SKIP; + + ret = test(0, 0); + if (ret == T_EXIT_SKIP) { + return T_EXIT_SKIP; + } else if (ret != T_EXIT_PASS) { + fprintf(stderr, "test 0 0 failed\n"); + return T_EXIT_FAIL; + } + + ret = test(0, 1); + if (ret == T_EXIT_SKIP) { + return T_EXIT_SKIP; + } else if (ret != T_EXIT_PASS) { + fprintf(stderr, "test 0 1 failed\n"); + return T_EXIT_FAIL; + } + + ret = test(1, 0); + if (ret == T_EXIT_SKIP) { + return T_EXIT_SKIP; + } else if (ret != T_EXIT_PASS) { + fprintf(stderr, "test 1 0 failed\n"); + return T_EXIT_FAIL; + } + + ret = test(1, 1); + if (ret == T_EXIT_SKIP) { + return T_EXIT_SKIP; + } else if (ret != T_EXIT_PASS) { + fprintf(stderr, "test 1 1 failed\n"); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +}