public inbox for io-uring@vger.kernel.org
 help / color / mirror / Atom feed
From: Daniele Di Proietto <daniele.di.proietto@gmail.com>
To: io-uring@vger.kernel.org
Cc: Jens Axboe <axboe@kernel.dk>,
	Daniele Di Proietto <daniele.di.proietto@gmail.com>
Subject: [liburing v2] Add support for IORING_OP_DUP
Date: Fri, 20 Mar 2026 18:22:50 +0000	[thread overview]
Message-ID: <20260320182250.780251-1-daniele.di.proietto@gmail.com> (raw)

The new operation duplicates an existing file (regular fd or fixed
descriptor) into a specific fd or fixed descriptor.

It's like dup3(), but async and also supports fixed descriptors as
source and destination.

Signed-off-by: Daniele Di Proietto <daniele.di.proietto@gmail.com>
---
Changes since v1:
* New interface (to/from direct descriptors)
* More testing
v1: https://lore.kernel.org/io-uring/20260310155111.2501074-1-daniele.di.proietto@gmail.com/T/#u

 man/io_uring_enter.2            |  35 ++
 man/io_uring_prep_dup.3         |  68 ++++
 src/include/liburing.h          |   8 +
 src/include/liburing/io_uring.h |  17 +
 src/sanitize.c                  |   4 +-
 test/Makefile                   |   1 +
 test/dup.c                      | 559 ++++++++++++++++++++++++++++++++
 7 files changed, 691 insertions(+), 1 deletion(-)
 create mode 100644 man/io_uring_prep_dup.3
 create mode 100644 test/dup.c

diff --git a/man/io_uring_enter.2 b/man/io_uring_enter.2
index 7b99335e..c6c273c3 100644
--- a/man/io_uring_enter.2
+++ b/man/io_uring_enter.2
@@ -272,6 +272,8 @@ struct io_uring_sqe {
 		__u32		futex_flags;
 		__u32		install_fd_flags;
 		__u32		nop_flags;
+		__u32		pipe_flags;
+		__u32		dup_flags;
 	};
 	__u64	user_data;	/* data to be passed back at completion time */
 	/* pack this to avoid bogus arm OABI complaints */
@@ -286,7 +288,9 @@ struct io_uring_sqe {
 	union {
 		__s32	splice_fd_in;
 		__u32	file_index;
+		__u32	zcrx_ifq_idx;
 		__u32	optlen;
+		__s32	dup_new_fd;
 		struct {
 			__u16	addr_len;
 			__u16	__pad3[1];
@@ -1729,6 +1733,37 @@ for general usage details.
 
 Available since 6.19.
 
+.TP
+.B IORING_OP_DUP
+Used to duplicate a file. The source and destination can be in the regular
+process file descriptor table or in the fixed files table.
+.I fd
+points to the source file, while
+.I dup_new_fd
+points to the destination.
+Additional flags may be passed in via
+.IR dup_flags .
+If
+.BR IORING_DUP_OLD_FIXED ,
+is set,
+.I fd
+is a registered file, otherwise it's a regular fd.
+If
+.BR IORING_DUP_NEW_FIXED ,
+.I dup_new_fd
+is a registered file, otherwise it's a regular fd.
+.BR IORING_DUP_NO_CLOEXEC ,
+is the opposite of
+.BR O_CLOEXEC .
+only valid if
+.BR IORING_DUP_NEW_FIXED ,
+is not set.
+.I dup_new_fd can be
+.BR IORING_FILE_INDEX_ALLOC,
+in which case a slot will be allocated.
+
+Available since 7.1.
+
 .PP
 The
 .I flags
diff --git a/man/io_uring_prep_dup.3 b/man/io_uring_prep_dup.3
new file mode 100644
index 00000000..d6782ec6
--- /dev/null
+++ b/man/io_uring_prep_dup.3
@@ -0,0 +1,68 @@
+.TH io_uring_prep_dup 3 "March 20, 2026" "liburing-2.15" "liburing Manual"
+.SH NAME
+io_uring_prep_dup \- prepare file duplication request
+.SH SYNOPSIS
+.nf
+.B #include <liburing.h>
+.PP
+.BI "void io_uring_prep_dup(struct io_uring_sqe *" sqe ","
+.BI "                       int " oldfd ","
+.BI "                       int " newfd ","
+.BI "                       unsigned int " dup_flags ");"
+.fi
+.SH DESCRIPTION
+.PP
+The
+.BR io_uring_prep_dup (3)
+helper prepares a file duplication request. The submission queue entry
+.I sqe
+is setup to duplicate the duplicate the file
+.I oldfd
+with the specified
+.I dup_flags
+into the file
+.IR newfd .
+
+When
+.I dup_flags
+has
+.B IORING_DUP_OLD_FIXED
+set,
+.I oldfd
+must be a fixed file descriptor index. Otherwise it must be a regular
+file descriptor.
+
+When
+.I dup_flags
+has
+.B IORING_DUP_NEW_FIXED
+set,
+.I newfd
+must be a fixed file descriptor index (or
+.BR IORING_FILE_INDEX_ALLOC ).
+Otherwise it must be a regular file descriptor.
+
+When
+.I dup_flags
+has
+.B IORING_DUP_NO_CLOEXEC
+set,
+the new regular file descriptor should not be closed during exec. By default,
+.B O_CLOEXEC
+will be set on
+.I newfd
+otherwise.
+
+.SH RETURN VALUE
+None
+.SH ERRORS
+The CQE
+.I res
+field will contain the result of the operation, which in this case will be the
+new file descripor (or index). In case of failure, a negative value is returned.
+.SH SEE ALSO
+.BR io_uring_get_sqe (3),
+.BR io_uring_submit (3),
+.BR io_uring_prep_fixed_fd_install (3),
+.BR io_uring_prep_files_update (3),
+.BR dup3 (2),
diff --git a/src/include/liburing.h b/src/include/liburing.h
index c056e71c..debf9e34 100644
--- a/src/include/liburing.h
+++ b/src/include/liburing.h
@@ -1688,6 +1688,14 @@ IOURINGINLINE void io_uring_prep_pipe_direct(struct io_uring_sqe *sqe, int *fds,
 	__io_uring_set_target_fixed_file(sqe, file_index);
 }
 
+IOURINGINLINE void io_uring_prep_dup(struct io_uring_sqe *sqe, int oldfd,
+				     int newfd, unsigned int flags)
+{
+	io_uring_prep_rw(IORING_OP_DUP, sqe, oldfd, 0, 0, 0);
+	sqe->dup_new_fd = newfd;
+	sqe->dup_flags = flags;
+}
+
 /* Read the kernel's SQ head index with appropriate memory ordering */
 IOURINGINLINE unsigned io_uring_load_sq_head(const struct io_uring *ring)
 	LIBURING_NOEXCEPT
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h
index 1e58bc72..0083bf18 100644
--- a/src/include/liburing/io_uring.h
+++ b/src/include/liburing/io_uring.h
@@ -74,6 +74,7 @@ struct io_uring_sqe {
 		__u32		install_fd_flags;
 		__u32		nop_flags;
 		__u32		pipe_flags;
+		__u32		dup_flags;
 	};
 	__u64	user_data;	/* data to be passed back at completion time */
 	/* pack this to avoid bogus arm OABI complaints */
@@ -90,6 +91,7 @@ struct io_uring_sqe {
 		__u32	file_index;
 		__u32	zcrx_ifq_idx;
 		__u32	optlen;
+		__s32	dup_new_fd;
 		struct {
 			__u16	addr_len;
 			__u16	__pad3[1];
@@ -312,6 +314,7 @@ enum io_uring_op {
 	IORING_OP_PIPE,
 	IORING_OP_NOP128,
 	IORING_OP_URING_CMD128,
+	IORING_OP_DUP,
 
 	/* this goes last, obviously */
 	IORING_OP_LAST,
@@ -472,6 +475,20 @@ enum io_uring_msg_ring_flags {
  */
 #define IORING_FIXED_FD_NO_CLOEXEC	(1U << 0)
 
+/*
+ * IORING_OP_DUP flags (sqe->dup_flags)
+ *
+ * IORING_DUP_NO_CLOEXEC	Don't mark the new fd as O_CLOEXEC. Only valid
+ *				if IORING_DUP_NEW_FIXED is not set.
+ * IORING_DUP_OLD_FIXED		sqe->fd (the source) is a fixed descriptor.
+ *				Otherwise it's a regular fd.
+ * IORING_DUP_NEW_FIXED		sqe->dup_new_fd (the destination) is a fixed
+ *				descriptor. Otherwise is a regular fd.
+ */
+#define IORING_DUP_NO_CLOEXEC	(1U << 0)
+#define IORING_DUP_OLD_FIXED	(1U << 1)
+#define IORING_DUP_NEW_FIXED	(1U << 2)
+
 /*
  * IORING_OP_NOP flags (sqe->nop_flags)
  *
diff --git a/src/sanitize.c b/src/sanitize.c
index 6d8465a5..08b79a3f 100644
--- a/src/sanitize.c
+++ b/src/sanitize.c
@@ -122,7 +122,9 @@ static inline void initialize_sanitize_handlers()
 	sanitize_handlers[IORING_OP_PIPE] = sanitize_sqe_addr;
 	sanitize_handlers[IORING_OP_NOP128] = sanitize_sqe_nop;
 	sanitize_handlers[IORING_OP_URING_CMD128] = sanitize_sqe_optval;
-	_Static_assert(IORING_OP_URING_CMD128 + 1 == IORING_OP_LAST, "Need an implementation for all IORING_OP_* codes");
+	sanitize_handlers[IORING_OP_DUP] = sanitize_sqe_nop;
+	_Static_assert(IORING_OP_DUP + 1 == IORING_OP_LAST,
+		       "Need an implementation for all IORING_OP_* codes");
 	sanitize_handlers_initialized = true;
 }
 
diff --git a/test/Makefile b/test/Makefile
index 9f45b6a0..4031f2b0 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -96,6 +96,7 @@ test_srcs := \
 	defer-tw-timeout.c \
 	double-poll-crash.c \
 	drop-submit.c \
+	dup.c \
 	eeed8b54e0df.c \
 	empty-eownerdead.c \
 	eploop.c \
diff --git a/test/dup.c b/test/dup.c
new file mode 100644
index 00000000..01bab4ce
--- /dev/null
+++ b/test/dup.c
@@ -0,0 +1,559 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: test io_uring_prep_dup
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+#include "helpers.h"
+
+struct fixture {
+	/* file descriptor of a pipe connected to fd_pipe_w */
+	int fd_pipe_r;
+	/* direct descriptor of a pipe connected to fd_pipe_w */
+	int fixed_pipe_r;
+
+	/* file descriptor of a pipe connected to fd_pipe_r and fixed_pipe_r */
+	int fd_pipe_w;
+};
+
+static int probe(struct io_uring *ring)
+{
+	struct io_uring_probe *p;
+	int ret = T_EXIT_PASS;
+
+	p = io_uring_get_probe_ring(ring);
+	if (!p)
+		return T_EXIT_SKIP;
+	if (!io_uring_opcode_supported(p, IORING_OP_DUP))
+		ret = T_EXIT_SKIP;
+	io_uring_free_probe(p);
+	return ret;
+}
+
+static int setup(struct io_uring *ring, struct fixture *fixture)
+{
+	int ret, fds[2];
+
+	ret = io_uring_queue_init(4, ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring setup failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	ret = probe(ring);
+	if (ret)
+		return ret;
+
+	if (pipe(fds) < 0) {
+		perror("pipe");
+		return T_EXIT_FAIL;
+	}
+	fixture->fd_pipe_r = fds[0];
+	fixture->fd_pipe_w = fds[1];
+	ret = io_uring_register_files_sparse(ring, 5);
+	if (ret) {
+		fprintf(stderr, "failed register files %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	ret = io_uring_register_files_update(ring, 0, &fixture->fd_pipe_r, 1);
+	if (ret != 1) {
+		fprintf(stderr, "failed register files %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	fixture->fixed_pipe_r = 0;
+
+	return T_EXIT_PASS;
+}
+
+static int test_same_fd(struct io_uring *ring, struct fixture *fixture,
+			int fixed)
+{
+	unsigned int dup_flags = 0;
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	int fd, err;
+
+	sqe = io_uring_get_sqe(ring);
+	if (fixed) {
+		dup_flags |= IORING_DUP_NEW_FIXED | IORING_DUP_OLD_FIXED;
+		fd = fixture->fixed_pipe_r;
+	} else {
+		fd = fixture->fd_pipe_r;
+	}
+	io_uring_prep_dup(sqe, fd, fd, dup_flags);
+	io_uring_submit(ring);
+	err = io_uring_wait_cqe(ring, &cqe);
+	if (err) {
+		fprintf(stderr, "wait cqe %d\n", err);
+		return T_EXIT_FAIL;
+	}
+	err = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+
+	if (err != -EINVAL) {
+		fprintf(stderr, "dup expected -EINVAL got %d\n", err);
+		return T_EXIT_FAIL;
+	}
+
+	return T_EXIT_PASS;
+}
+
+static int are_pipes_connected(struct io_uring *ring, int fd_r, int fd_w,
+			       int r_fixed)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	char buf[32];
+	int err;
+
+	err = write(fd_w, "Hello", 5);
+	if (err < 0) {
+		perror("write");
+		return T_EXIT_FAIL;
+	} else if (err != 5) {
+		fprintf(stderr, "short write %d\n", err);
+		return T_EXIT_FAIL;
+	}
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_read(sqe, fd_r, buf, sizeof(buf), 0);
+	if (r_fixed)
+		sqe->flags |= IOSQE_FIXED_FILE;
+	io_uring_submit(ring);
+	err = io_uring_wait_cqe(ring, &cqe);
+	if (err) {
+		fprintf(stderr, "read wait cqe %d\n", err);
+		return T_EXIT_FAIL;
+	}
+	err = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+
+	if (err != 5) {
+		fprintf(stderr, "unexpected read ret %d\n", err);
+		return T_EXIT_FAIL;
+	}
+
+	return T_EXIT_PASS;
+}
+
+static int cloexec_check(int fd, int nocloexec)
+{
+	int flags;
+
+	flags = fcntl(fd, F_GETFD, 0);
+	if (flags < 0) {
+		perror("fcntl");
+		return T_EXIT_FAIL;
+	}
+
+	if (nocloexec)
+		return (flags & FD_CLOEXEC) ? T_EXIT_FAIL : T_EXIT_PASS;
+	else
+		return (flags & FD_CLOEXEC) ? T_EXIT_PASS : T_EXIT_FAIL;
+}
+
+static int close_fixed(struct io_uring *ring, int fixed)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	int err;
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_close_direct(sqe, fixed);
+	io_uring_submit(ring);
+	err = io_uring_wait_cqe(ring, &cqe);
+	if (err) {
+		fprintf(stderr, "close wait cqe %d\n", err);
+		return T_EXIT_FAIL;
+	}
+	err = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	if (err < 0) {
+		fprintf(stderr, "close failed: %d\n", err);
+		return T_EXIT_FAIL;
+	}
+	return T_EXIT_PASS;
+}
+
+static int full_fd(void)
+{
+	int fds[2];
+
+	if (pipe(fds) < 0) {
+		perror("pipe");
+		return -1;
+	}
+
+	close(fds[1]);
+	return fds[0];
+}
+
+static int full_fd_flush(void)
+{
+	return open("/sys/kernel/debug/tracing/per_cpu/cpu0/trace_pipe_raw",
+		    O_RDONLY);
+}
+
+static int full_fixed(struct io_uring *ring, unsigned int fd)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	int fds[2], err;
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_pipe_direct(sqe, fds, 0, fd);
+	io_uring_submit(ring);
+	err = io_uring_wait_cqe(ring, &cqe);
+	if (err) {
+		fprintf(stderr, "wait cqe %d\n", err);
+		return T_EXIT_FAIL;
+	}
+
+	err = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	if (err) {
+		fprintf(stderr, "pipe direct %d\n", err);
+		return T_EXIT_FAIL;
+	}
+	return close_fixed(ring, fds[1]);
+}
+
+static int full_fixed_flush(struct io_uring *ring, unsigned int fd)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	int err;
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_open_direct(
+		sqe, "/sys/kernel/debug/tracing/per_cpu/cpu0/trace_pipe_raw", 0,
+		0, fd);
+	io_uring_submit(ring);
+	err = io_uring_wait_cqe(ring, &cqe);
+	if (err) {
+		fprintf(stderr, "wait cqe %d\n", err);
+		return T_EXIT_FAIL;
+	}
+	err = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	return err == 0 ? T_EXIT_PASS : T_EXIT_FAIL;
+}
+
+struct test_params {
+	/* The src file is a direct descriptor (otherwise it's a regular fd). */
+	unsigned old_fixed : 1;
+	/* The dst file is a direct descriptor (otherwise it's a regular fd). */
+	unsigned new_fixed : 1;
+	/* If set, allocates a direct descriptor slot. Only valid if new_fixed is set. */
+	unsigned alloc : 1;
+	/* Pass the nocloexec flag. */
+	unsigned nocloexec : 1;
+	/* Pass the IOSQE_ASYNC flag. */
+	unsigned async : 1;
+	/* The dst file already contains something that will be closed. */
+	unsigned new_full : 1;
+	/* The dst file already contains something that requires flushing before closing. */
+	unsigned new_full_flush : 1;
+	/* The src file is a io_uring regular fd. */
+	unsigned old_ring : 1;
+	/* The dst file is a io_uring regular fd. */
+	unsigned new_ring : 1;
+	/* The src file doesn't exist. */
+	unsigned old_nx : 1;
+	/* If non-zero, expect this error */
+	int expected_error;
+};
+
+static int do_test(struct io_uring *ring, struct fixture *fixture,
+		   struct test_params params)
+{
+	unsigned int dup_flags = 0;
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	int ret, oldfd, newfd;
+
+	if (params.old_fixed) {
+		dup_flags |= IORING_DUP_OLD_FIXED;
+		oldfd = fixture->fixed_pipe_r;
+		if (params.old_nx)
+			oldfd = 10;
+	} else {
+		oldfd = fixture->fd_pipe_r;
+		if (params.old_ring)
+			oldfd = ring->ring_fd;
+		else if (params.old_nx)
+			oldfd = 10;
+	}
+	if (params.new_fixed) {
+		dup_flags |= IORING_DUP_NEW_FIXED;
+		if (params.alloc) {
+			newfd = IORING_FILE_INDEX_ALLOC;
+		} else {
+			newfd = 3;
+			if (params.new_full) {
+				if (params.new_full_flush)
+					ret = full_fixed_flush(ring, newfd);
+				else
+					ret = full_fixed(ring, newfd);
+				if (ret)
+					return T_EXIT_SKIP;
+			}
+		}
+	} else {
+		if (params.new_ring) {
+			newfd = ring->ring_fd;
+		} else {
+			if (params.new_full_flush)
+				newfd = full_fd_flush();
+			else
+				newfd = full_fd();
+			if (newfd == -1)
+				return T_EXIT_SKIP;
+			if (!params.new_full)
+				close(newfd);
+		}
+	}
+	if (params.nocloexec)
+		dup_flags |= IORING_DUP_NO_CLOEXEC;
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_dup(sqe, oldfd, newfd, dup_flags);
+	if (params.async)
+		sqe->flags |= IOSQE_ASYNC;
+
+	io_uring_submit(ring);
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret) {
+		fprintf(stderr, "wait cqe %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+	ret = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	if (params.expected_error != 0) {
+		if (ret != params.expected_error) {
+			fprintf(stderr, "wrong expected error: %d\n", ret);
+			return T_EXIT_FAIL;
+		}
+		goto cleanup;
+	}
+	if (ret < 0) {
+		fprintf(stderr, "failed dup: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	if (params.new_fixed && params.alloc) {
+		newfd = ret;
+	} else {
+		if (ret != newfd) {
+			fprintf(stderr, "wrong dup return value: %d\n", ret);
+			return T_EXIT_FAIL;
+		}
+	}
+
+	if (!params.old_ring) {
+		ret = are_pipes_connected(ring, newfd, fixture->fd_pipe_w,
+					  params.new_fixed);
+		if (ret != T_EXIT_PASS) {
+			fprintf(stderr, "dup pipes not connected\n");
+			return ret;
+		}
+	}
+	if (!params.new_fixed) {
+		ret = cloexec_check(newfd, params.nocloexec);
+		if (ret != T_EXIT_PASS) {
+			fprintf(stderr, "cloexec mismatch\n");
+			return ret;
+		}
+	}
+
+	if (params.new_fixed) {
+		ret = close_fixed(ring, newfd);
+		if (ret != T_EXIT_PASS) {
+			fprintf(stderr, "close_fixed error\n");
+			return ret;
+		}
+	} else {
+		close(newfd);
+	}
+
+	return T_EXIT_PASS;
+
+cleanup:
+	if (params.new_full) {
+		if (params.new_fixed)
+			close_fixed(ring, newfd);
+		else if (!params.new_ring)
+			close(newfd);
+	}
+
+	return T_EXIT_PASS;
+}
+
+static int test_large_fd(struct io_uring *ring, struct fixture *fixture)
+{
+	unsigned int dup_flags = 0;
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	int newfd, oldfd, ret;
+
+	sqe = io_uring_get_sqe(ring);
+	dup_flags |= IORING_DUP_OLD_FIXED;
+	oldfd = fixture->fixed_pipe_r;
+	newfd = 200;
+	io_uring_prep_dup(sqe, oldfd, newfd, dup_flags);
+	io_uring_submit(ring);
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret) {
+		fprintf(stderr, "wait cqe %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+	ret = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+
+	if (ret != newfd) {
+		fprintf(stderr, "dup expected %d got %d\n", newfd, ret);
+		return T_EXIT_FAIL;
+	}
+
+	ret = are_pipes_connected(ring, newfd, fixture->fd_pipe_w, false);
+	if (ret != T_EXIT_PASS) {
+		fprintf(stderr, "dup pipes not connected\n");
+		return ret;
+	}
+	close(newfd);
+
+	return T_EXIT_PASS;
+}
+
+int main(int argc, char *argv[])
+{
+	struct fixture fixture;
+	struct io_uring ring;
+	unsigned int i;
+	int ret;
+
+	if (argc > 1)
+		return T_EXIT_SKIP;
+
+	ret = setup(&ring, &fixture);
+	if (ret != T_EXIT_PASS) {
+		if (ret == T_EXIT_FAIL)
+			fprintf(stderr, "fixture setup failed\n");
+		return ret;
+	}
+
+	for (i = 0; i < (1 << 10); i++) {
+		struct test_params params;
+
+		params.old_fixed = (i & (1 << 0)) != 0;
+		params.new_fixed = (i & (1 << 1)) != 0;
+		params.nocloexec = (i & (1 << 2)) != 0;
+		params.async = (i & (1 << 3)) != 0;
+		params.alloc = (i & (1 << 4)) != 0;
+		params.new_full = (i & (1 << 5)) != 0;
+		params.new_full_flush = (i & (1 << 6)) != 0;
+		params.old_ring = (i & (1 << 7)) != 0;
+		params.new_ring = (i & (1 << 8)) != 0;
+		params.old_nx = (i & (1 << 9)) != 0;
+
+		params.expected_error = 0;
+
+		if (params.alloc && !params.new_fixed)
+			continue;
+
+		if (params.alloc && params.new_full)
+			continue;
+
+		if (params.old_fixed && params.old_ring)
+			continue;
+
+		if (params.new_fixed && params.new_ring)
+			continue;
+
+		if (!params.new_full && params.new_full_flush)
+			continue;
+
+		if (params.old_ring && params.new_ring)
+			continue;
+
+		if (params.old_ring && params.old_nx)
+			continue;
+
+		if (params.new_ring &&
+		    (!params.new_full || params.new_full_flush))
+			continue;
+
+		if (params.old_ring && params.new_fixed)
+			params.expected_error = -EBADF;
+
+		if (params.new_ring)
+			params.expected_error = -EBADF;
+
+		if (params.old_nx)
+			params.expected_error = -EBADF;
+
+		if (params.new_fixed && params.nocloexec)
+			params.expected_error = -EINVAL;
+
+		ret = do_test(&ring, &fixture, params);
+		if (ret != T_EXIT_PASS) {
+			if (ret == T_EXIT_SKIP && params.new_full_flush) {
+				fprintf(stderr, "Skipping!\n");
+				/* tracefs might not be accessible. Ignore */
+				continue;
+			}
+			if (ret == T_EXIT_FAIL)
+				fprintf(stderr,
+					"do_test (%u) "
+					"old_fixedio_rsrc_ : %u "
+					"new_fixed: %u "
+					"alloc: %u "
+					"nocloexec: %u "
+					"async: %u "
+					"new_full: %u "
+					"new_full_flush: %u "
+					"old_ring: %u "
+					"new_ring:%u "
+					"old_nx: %u "
+					"expected_error:%d "
+					"failed\n",
+					i, params.old_fixed, params.new_fixed,
+					params.alloc, params.nocloexec,
+					params.async, params.new_full,
+					params.new_full_flush, params.old_ring,
+					params.new_ring, params.old_nx,
+					params.expected_error);
+			return ret;
+		}
+	}
+
+	ret = test_same_fd(&ring, &fixture, 0);
+	if (ret != T_EXIT_PASS) {
+		if (ret == T_EXIT_FAIL)
+			fprintf(stderr, "test_same_fd fixed: 0 failed");
+		return ret;
+	}
+
+	ret = test_same_fd(&ring, &fixture, 1);
+	if (ret != T_EXIT_PASS) {
+		if (ret == T_EXIT_FAIL)
+			fprintf(stderr, "test_same_fd fixed: 1 failed");
+		return ret;
+	}
+
+	ret = test_large_fd(&ring, &fixture);
+	if (ret != T_EXIT_PASS) {
+		if (ret == T_EXIT_FAIL)
+			fprintf(stderr, "test_large_fd: 1 failed");
+		return ret;
+	}
+
+	return T_EXIT_PASS;
+}
-- 
2.43.0


                 reply	other threads:[~2026-03-20 18:29 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260320182250.780251-1-daniele.di.proietto@gmail.com \
    --to=daniele.di.proietto@gmail.com \
    --cc=axboe@kernel.dk \
    --cc=io-uring@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox