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;
+}