Add the header / definitions and tests for the query interface. Signed-off-by: Pavel Begunkov --- src/include/liburing.h | 1 + src/include/liburing/io_uring.h | 3 + src/include/liburing/io_uring/query.h | 41 ++++ test/Makefile | 1 + test/ring-query.c | 322 ++++++++++++++++++++++++++ 5 files changed, 368 insertions(+) create mode 100644 src/include/liburing/io_uring/query.h create mode 100644 test/ring-query.c diff --git a/src/include/liburing.h b/src/include/liburing.h index e3f394ea..46d3cccf 100644 --- a/src/include/liburing.h +++ b/src/include/liburing.h @@ -16,6 +16,7 @@ #include #include "liburing/compat.h" #include "liburing/io_uring.h" +#include "liburing/io_uring/query.h" #include "liburing/io_uring_version.h" #include "liburing/barrier.h" diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h index 212b5874..55b69f9d 100644 --- a/src/include/liburing/io_uring.h +++ b/src/include/liburing/io_uring.h @@ -641,6 +641,9 @@ enum io_uring_register_op { IORING_REGISTER_MEM_REGION = 34, + /* query various aspects of io_uring, see linux/io_uring/query.h */ + IORING_REGISTER_QUERY = 35, + /* this goes last */ IORING_REGISTER_LAST, diff --git a/src/include/liburing/io_uring/query.h b/src/include/liburing/io_uring/query.h new file mode 100644 index 00000000..5d754322 --- /dev/null +++ b/src/include/liburing/io_uring/query.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT */ +/* + * Header file for the io_uring query interface. + */ +#ifndef LINUX_IO_URING_QUERY_H +#define LINUX_IO_URING_QUERY_H + +#include + +struct io_uring_query_hdr { + __u64 next_entry; + __u64 query_data; + __u32 query_op; + __u32 size; + __s32 result; + __u32 __resv[3]; +}; + +enum { + IO_URING_QUERY_OPCODES = 0, + + __IO_URING_QUERY_MAX, +}; + +/* Doesn't require a ring */ +struct io_uring_query_opcode { + /* The number of supported IORING_OP_* opcodes */ + __u32 nr_request_opcodes; + /* The number of supported IORING_[UN]REGISTER_* opcodes */ + __u32 nr_register_opcodes; + /* Bitmask of all supported IORING_FEAT_* flags */ + __u64 feature_flags; + /* Bitmask of all supported IORING_SETUP_* flags */ + __u64 ring_setup_flags; + /* Bitmask of all supported IORING_ENTER_** flags */ + __u64 enter_flags; + /* Bitmask of all supported IOSQE_* flags */ + __u64 sqe_flags; +}; + +#endif diff --git a/test/Makefile b/test/Makefile index edfc0df7..626ae674 100644 --- a/test/Makefile +++ b/test/Makefile @@ -255,6 +255,7 @@ test_srcs := \ zcrx.c \ vec-regbuf.c \ timestamp.c \ + ring-query.c \ # EOL # Please keep this list sorted alphabetically. diff --git a/test/ring-query.c b/test/ring-query.c new file mode 100644 index 00000000..46080c77 --- /dev/null +++ b/test/ring-query.c @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: MIT */ +#include +#include +#include +#include +#include +#include + +#include "liburing.h" +#include "test.h" +#include "helpers.h" + +struct io_uring_query_opcode_short { + __u32 nr_request_opcodes; + __u32 nr_register_opcodes; +}; + +struct io_uring_query_opcode_large { + __u32 nr_request_opcodes; + __u32 nr_register_opcodes; + __u64 feature_flags; + __u64 ring_setup_flags; + __u64 enter_flags; + __u64 sqe_flags; + __u64 placeholder[8]; +}; + +static struct io_uring_query_opcode sys_ops; + +static int io_uring_query(struct io_uring *ring, struct io_uring_query_hdr *arg) +{ + int fd = ring ? ring->ring_fd : -1; + + return io_uring_register(fd, IORING_REGISTER_QUERY, arg, 0); +} + +static int test_basic_query(void) +{ + struct io_uring_query_opcode op; + struct io_uring_query_hdr hdr = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op), + .size = sizeof(op), + }; + int ret; + + ret = io_uring_query(NULL, &hdr); + if (ret == -EINVAL) + return T_EXIT_SKIP; + + if (ret != 0) { + fprintf(stderr, "query failed %d\n", ret); + return T_EXIT_FAIL; + } + + if (hdr.size != sizeof(op)) { + fprintf(stderr, "unexpected size %i vs %i\n", + (int)hdr.size, (int)sizeof(op)); + return T_EXIT_FAIL; + } + + if (hdr.result) { + fprintf(stderr, "unexpected result %i\n", hdr.result); + return T_EXIT_FAIL; + } + + if (op.nr_register_opcodes <= IORING_REGISTER_QUERY) { + fprintf(stderr, "too few opcodes (%i)\n", op.nr_register_opcodes); + return T_EXIT_FAIL; + } + + memcpy(&sys_ops, &op, sizeof(sys_ops)); + return T_EXIT_PASS; +} + +static int test_invalid(void) +{ + int ret; + struct io_uring_query_opcode op; + struct io_uring_query_hdr invalid_hdr = { + .query_op = -1U, + .query_data = uring_ptr_to_u64(&op), + .size = sizeof(struct io_uring_query_opcode), + }; + struct io_uring_query_hdr invalid_next_hdr = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op), + .size = sizeof(struct io_uring_query_opcode), + .next_entry = 0xdeadbeefUL, + }; + struct io_uring_query_hdr invalid_data_hdr = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = 0xdeadbeefUL, + .size = sizeof(struct io_uring_query_opcode), + }; + + ret = io_uring_query(NULL, &invalid_hdr); + if (ret || invalid_hdr.result != -EOPNOTSUPP) { + fprintf(stderr, "failed invalid opcode %i (%i)\n", + ret, invalid_hdr.result); + return T_EXIT_FAIL; + } + + ret = io_uring_query(NULL, &invalid_next_hdr); + if (ret != -EFAULT) { + fprintf(stderr, "invalid next %i\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_query(NULL, &invalid_data_hdr); + if (ret != -EFAULT) { + fprintf(stderr, "invalid next %i\n", ret); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +} + +static int test_chain(void) +{ + int ret; + struct io_uring_query_opcode op1, op2, op3; + struct io_uring_query_hdr hdr3 = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op3), + .size = sizeof(struct io_uring_query_opcode), + }; + struct io_uring_query_hdr hdr2 = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op2), + .size = sizeof(struct io_uring_query_opcode), + .next_entry = uring_ptr_to_u64(&hdr3), + }; + struct io_uring_query_hdr hdr1 = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op1), + .size = sizeof(struct io_uring_query_opcode), + .next_entry = uring_ptr_to_u64(&hdr2), + }; + + ret = io_uring_query(NULL, &hdr1); + if (ret) { + fprintf(stderr, "chain failed %i\n", ret); + return T_EXIT_FAIL; + } + + if (hdr1.result || hdr2.result || hdr3.result) { + fprintf(stderr, "chain invalid result entries %i %i %i\n", + hdr1.result, hdr2.result, hdr3.result); + return T_EXIT_FAIL; + } + + if (op1.nr_register_opcodes != sys_ops.nr_register_opcodes || + op2.nr_register_opcodes != sys_ops.nr_register_opcodes || + op3.nr_register_opcodes != sys_ops.nr_register_opcodes) { + fprintf(stderr, "chain invalid register opcodes\n"); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +} + +static int test_chain_loop(void) +{ + int ret; + struct io_uring_query_opcode op1, op2; + struct io_uring_query_hdr hdr2 = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op2), + .size = sizeof(struct io_uring_query_opcode), + }; + struct io_uring_query_hdr hdr1 = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op1), + .size = sizeof(struct io_uring_query_opcode), + }; + struct io_uring_query_hdr hdr_self_circular = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op1), + .size = sizeof(struct io_uring_query_opcode), + .next_entry = uring_ptr_to_u64(&hdr_self_circular), + }; + + hdr1.next_entry = uring_ptr_to_u64(&hdr2); + hdr2.next_entry = uring_ptr_to_u64(&hdr1); + ret = io_uring_query(NULL, &hdr1); + if (!ret) { + fprintf(stderr, "chain loop failed %i\n", ret); + return T_EXIT_FAIL; + } + + ret = io_uring_query(NULL, &hdr_self_circular); + if (!ret) { + fprintf(stderr, "chain loop failed %i\n", ret); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +} + +static int test_compatibile_shorter(void) +{ + int ret; + struct io_uring_query_opcode_short op; + struct io_uring_query_hdr hdr = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op), + .size = sizeof(op), + }; + + ret = io_uring_query(NULL, &hdr); + if (ret || hdr.result) { + fprintf(stderr, "failed invalid short result %i (%i)\n", + ret, hdr.result); + return T_EXIT_FAIL; + } + + if (hdr.size != sizeof(struct io_uring_query_opcode_short)) { + fprintf(stderr, "unexpected short query size %i %i\n", + (int)hdr.size, + (int)sizeof(struct io_uring_query_opcode_short)); + return T_EXIT_FAIL; + } + + if (sys_ops.nr_register_opcodes != op.nr_register_opcodes || + sys_ops.nr_request_opcodes != op.nr_request_opcodes) { + fprintf(stderr, "invalid short data\n"); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +} + +static int test_compatibile_larger(void) +{ + int ret; + struct io_uring_query_opcode_large op; + struct io_uring_query_hdr hdr = { + .query_op = IO_URING_QUERY_OPCODES, + .query_data = uring_ptr_to_u64(&op), + .size = sizeof(op), + }; + + ret = io_uring_query(NULL, &hdr); + if (ret || hdr.result) { + fprintf(stderr, "failed invalid large result %i (%i)\n", + ret, hdr.result); + return T_EXIT_FAIL; + } + + if (hdr.size < sizeof(struct io_uring_query_opcode)) { + fprintf(stderr, "unexpected large query size %i %i\n", + (int)hdr.size, + (int)sizeof(struct io_uring_query_opcode)); + return T_EXIT_FAIL; + } + + if (sys_ops.nr_register_opcodes != op.nr_register_opcodes || + sys_ops.nr_request_opcodes != op.nr_request_opcodes || + sys_ops.ring_setup_flags != op.ring_setup_flags || + sys_ops.feature_flags != op.feature_flags) { + fprintf(stderr, "invalid large data\n"); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +} + +int main(int argc, char *argv[]) +{ + struct io_uring ring; + int ret; + + if (argc > 1) + return 0; + + ret = test_basic_query(); + if (ret != T_EXIT_PASS) { + if (ret == T_EXIT_SKIP) + fprintf(stderr, "ring query not supported, skip\n"); + else + fprintf(stderr, "test_basic_query failed\n"); + + return T_EXIT_SKIP; + } + + ret = io_uring_queue_init(8, &ring, 0); + if (ret) { + fprintf(stderr, "init failed\n"); + return T_EXIT_FAIL; + } + + ret = test_invalid(); + if (ret) + return T_EXIT_FAIL; + + ret = test_chain(); + if (ret) { + fprintf(stderr, "test_chain failed\n"); + return T_EXIT_FAIL; + } + + ret = test_chain_loop(); + if (ret) { + fprintf(stderr, "test_chain_loop failed\n"); + return T_EXIT_FAIL; + } + + ret = test_compatibile_shorter(); + if (ret) { + fprintf(stderr, "test_compatibile_shorter failed\n"); + return T_EXIT_FAIL; + } + + ret = test_compatibile_larger(); + if (ret) { + fprintf(stderr, "test_compatibile_larger failed\n"); + return T_EXIT_FAIL; + } + + return 0; +} -- 2.49.0 Signed-off-by: Pavel Begunkov --- test/helpers.c | 17 +++++++++++++++++ test/helpers.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/test/helpers.c b/test/helpers.c index 05895486..18af2be8 100644 --- a/test/helpers.c +++ b/test/helpers.c @@ -509,3 +509,20 @@ void t_clear_nonblock(int fd) { __t_toggle_nonblock(fd, 0); } + +int t_submit_and_wait_single(struct io_uring *ring, struct io_uring_cqe **cqe) +{ + int ret; + + ret = io_uring_submit(ring); + if (ret <= 0) { + fprintf(stderr, "sqe submit failed: %d\n", ret); + return -1; + } + ret = io_uring_wait_cqe(ring, cqe); + if (ret < 0) { + fprintf(stderr, "wait completion %d\n", ret); + return ret; + } + return 0; +} diff --git a/test/helpers.h b/test/helpers.h index 3f0c11ab..b7465890 100644 --- a/test/helpers.h +++ b/test/helpers.h @@ -122,6 +122,8 @@ unsigned long long mtime_since_now(struct timeval *tv); unsigned long long utime_since(const struct timeval *s, const struct timeval *e); unsigned long long utime_since_now(struct timeval *tv); +int t_submit_and_wait_single(struct io_uring *ring, struct io_uring_cqe **cqe); + #ifdef __cplusplus } #endif -- 2.49.0 Signed-off-by: Pavel Begunkov --- test/helpers.c | 10 ++++++++++ test/helpers.h | 2 ++ test/vec-regbuf.c | 6 ++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/test/helpers.c b/test/helpers.c index 18af2be8..15ceb17a 100644 --- a/test/helpers.c +++ b/test/helpers.c @@ -526,3 +526,13 @@ int t_submit_and_wait_single(struct io_uring *ring, struct io_uring_cqe **cqe) } return 0; } + +size_t t_iovec_data_length(struct iovec *iov, unsigned iov_len) +{ + size_t sz = 0; + int i; + + for (i = 0; i < iov_len; i++) + sz += iov[i].iov_len; + return sz; +} diff --git a/test/helpers.h b/test/helpers.h index b7465890..a45b8683 100644 --- a/test/helpers.h +++ b/test/helpers.h @@ -124,6 +124,8 @@ unsigned long long utime_since_now(struct timeval *tv); int t_submit_and_wait_single(struct io_uring *ring, struct io_uring_cqe **cqe); +size_t t_iovec_data_length(struct iovec *iov, unsigned iov_len); + #ifdef __cplusplus } #endif diff --git a/test/vec-regbuf.c b/test/vec-regbuf.c index 286b78c6..0cbe2c74 100644 --- a/test/vec-regbuf.c +++ b/test/vec-regbuf.c @@ -269,7 +269,7 @@ static int test_vec(struct buf_desc *bd, struct iovec *vecs, int nr_vec, struct sockaddr_storage addr; int sock_server, sock_client; struct verify_data vd; - size_t total_len = 0; + size_t total_len; int i, ret; void *verify_res; pthread_t th; @@ -284,9 +284,7 @@ static int test_vec(struct buf_desc *bd, struct iovec *vecs, int nr_vec, for (i = 0; i < bd->size; i++) bd->buf_wr[i] = i; memset(bd->buf_rd, 0, bd->size); - - for (i = 0; i < nr_vec; i++) - total_len += vecs[i].iov_len; + total_len = t_iovec_data_length(vecs, nr_vec); vd.bd = bd; vd.vecs = vecs; -- 2.49.0 Signed-off-by: Pavel Begunkov --- test/helpers.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/helpers.h b/test/helpers.h index a45b8683..7dafeb17 100644 --- a/test/helpers.h +++ b/test/helpers.h @@ -126,6 +126,13 @@ int t_submit_and_wait_single(struct io_uring *ring, struct io_uring_cqe **cqe); size_t t_iovec_data_length(struct iovec *iov, unsigned iov_len); +static inline void t_sqe_prep_cmd(struct io_uring_sqe *sqe, + int fd, unsigned cmd_op) +{ + io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, NULL, 0, 0); + sqe->cmd_op = cmd_op; +} + #ifdef __cplusplus } #endif -- 2.49.0 Signed-off-by: Pavel Begunkov --- test/helpers.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/helpers.h | 3 +++ 2 files changed, 47 insertions(+) diff --git a/test/helpers.c b/test/helpers.c index 15ceb17a..83a128e3 100644 --- a/test/helpers.c +++ b/test/helpers.c @@ -536,3 +536,47 @@ size_t t_iovec_data_length(struct iovec *iov, unsigned iov_len) sz += iov[i].iov_len; return sz; } + +#define t_min(a, b) ((a) < (b) ? (a) : (b)) + +unsigned long t_compare_data_iovec(struct iovec *iov_src, unsigned nr_src, + struct iovec *iov_dst, unsigned nr_dst) +{ + size_t src_len = t_iovec_data_length(iov_src, nr_src); + size_t dst_len = t_iovec_data_length(iov_dst, nr_dst); + size_t len_left = t_min(src_len, dst_len); + unsigned long src_off = 0, dst_off = 0; + unsigned long offset = 0; + + while (offset != len_left) { + size_t len = len_left - offset; + unsigned long i; + + len = t_min(len, iov_src->iov_len - src_off); + len = t_min(len, iov_dst->iov_len - dst_off); + + for (i = 0; i < len; i++) { + char csrc = ((char *)iov_src->iov_base)[src_off + i]; + char cdst = ((char *)iov_dst->iov_base)[dst_off + i]; + + if (csrc != cdst) { + fprintf(stderr, "data mismatch, %i vs %i\n", + csrc, cdst); + return -EINVAL; + } + } + + src_off += len; + dst_off += len; + if (src_off == iov_src->iov_len) { + src_off = 0; + iov_src++; + } + if (dst_off == iov_dst->iov_len) { + dst_off = 0; + iov_dst++; + } + offset += len; + } + return 0; +} diff --git a/test/helpers.h b/test/helpers.h index 7dafeb17..cfada945 100644 --- a/test/helpers.h +++ b/test/helpers.h @@ -126,6 +126,9 @@ int t_submit_and_wait_single(struct io_uring *ring, struct io_uring_cqe **cqe); size_t t_iovec_data_length(struct iovec *iov, unsigned iov_len); +unsigned long t_compare_data_iovec(struct iovec *iov_src, unsigned nr_src, + struct iovec *iov_dst, unsigned nr_dst); + static inline void t_sqe_prep_cmd(struct io_uring_sqe *sqe, int fd, unsigned cmd_op) { -- 2.49.0 Tests io_uring cmds with vectored registered buffers, which relies on io_uring mock files. Also test read/write. Signed-off-by: Pavel Begunkov --- test/Makefile | 1 + test/mock_file.c | 373 +++++++++++++++++++++++++++++++++++++++++++++++ test/mock_file.h | 47 ++++++ 3 files changed, 421 insertions(+) create mode 100644 test/mock_file.c create mode 100644 test/mock_file.h diff --git a/test/Makefile b/test/Makefile index 626ae674..c1afda5c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -256,6 +256,7 @@ test_srcs := \ vec-regbuf.c \ timestamp.c \ ring-query.c \ + mock_file.c \ # EOL # Please keep this list sorted alphabetically. diff --git a/test/mock_file.c b/test/mock_file.c new file mode 100644 index 00000000..e7c3c669 --- /dev/null +++ b/test/mock_file.c @@ -0,0 +1,373 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "liburing.h" +#include "test.h" +#include "helpers.h" + +#include "mock_file.h" + +static struct io_uring mgr_ring; +static __u64 mock_features; +static int mgr_fd; + +static bool has_feature(int feature) +{ + return mock_features >= feature; +} + +static int setup_mgr(void) +{ + struct io_uring_mock_probe mp; + struct io_uring_cqe *cqe; + struct io_uring_sqe *sqe; + int ret; + + ret = mgr_fd = open("/dev/io_uring_mock", O_RDWR); + if (mgr_fd < 0) { + printf("no io_uring mock files, skip\n"); + return T_EXIT_SKIP; + } + + ret = io_uring_queue_init(8, &mgr_ring, 0); + if (ret) { + fprintf(stderr, "mgr ring setup failed %i\n", ret); + return T_EXIT_FAIL; + } + + memset(&mp, 0, sizeof(mp)); + sqe = io_uring_get_sqe(&mgr_ring); + t_sqe_prep_cmd(sqe, mgr_fd, IORING_MOCK_MGR_CMD_PROBE); + sqe->addr = (__u64)(unsigned long)∓ + sqe->len = sizeof(mp); + + ret = t_submit_and_wait_single(&mgr_ring, &cqe); + if (ret || cqe->res) { + fprintf(stderr, "probe cmd failed %i %i\n", ret, cqe->res); + return T_EXIT_FAIL; + } + + io_uring_cqe_seen(&mgr_ring, cqe); + mock_features = mp.features; + return 0; +} + +static int create_mock_file(struct io_uring_mock_create *mc) +{ + struct io_uring_cqe *cqe; + struct io_uring_sqe *sqe; + int ret; + + sqe = io_uring_get_sqe(&mgr_ring); + t_sqe_prep_cmd(sqe, mgr_fd, IORING_MOCK_MGR_CMD_CREATE); + sqe->addr = (__u64)(unsigned long)mc; + sqe->len = sizeof(*mc); + + ret = t_submit_and_wait_single(&mgr_ring, &cqe); + if (ret || cqe->res) { + fprintf(stderr, "file create cmd failed %i %i\n", ret, cqe->res); + return T_EXIT_FAIL; + } + io_uring_cqe_seen(&mgr_ring, cqe); + return 0; +} + +static int t_copy_regvec(struct io_uring *ring, int mock_fd, + struct iovec *iov, unsigned iov_len, char *buf, + bool from_iov) +{ + struct io_uring_cqe *cqe; + struct io_uring_sqe *sqe; + int ret; + + sqe = io_uring_get_sqe(ring); + t_sqe_prep_cmd(sqe, mock_fd, IORING_MOCK_CMD_COPY_REGBUF); + sqe->addr3 = (__u64)(unsigned long)buf; + sqe->addr = (__u64)(unsigned long)iov; + sqe->len = iov_len; + if (from_iov) + sqe->file_index = IORING_MOCK_COPY_FROM; + sqe->buf_index = from_iov ? 0 : 1; + sqe->user_data = 43; + sqe->uring_cmd_flags |= IORING_URING_CMD_FIXED; + + ret = t_submit_and_wait_single(ring, &cqe); + if (ret) + t_error(1, ret, "submit/wait failed"); + + ret = cqe->res; + io_uring_cqe_seen(ring, cqe); + return ret; +} + +static int t_copy_verify_regvec(struct io_uring *ring, int mock_fd, + struct iovec *iov, unsigned iov_len, char *buf, + bool from_iov) +{ + struct iovec iov2; + int ret; + + ret = t_copy_regvec(ring, mock_fd, iov, iov_len, buf, from_iov); + if (ret < 0 || ret != t_iovec_data_length(iov, iov_len)) + return ret < 0 ? ret : -1; + + iov2.iov_base = buf; + iov2.iov_len = -1U; + + ret = t_compare_data_iovec(iov, iov_len, &iov2, 1); + if (ret) { + fprintf(stderr, "iovec1 data mismatch %i\n", ret); + return -1; + } + return 0; +} + +static int test_regvec_cmd(struct io_uring *ring, int mock_fd) +{ + struct iovec buf_iovec[2]; + struct iovec iov[8]; + size_t size = 4096 * 32; + char *buf_src, *buf_dst; + int i, ret; + + buf_src = aligned_alloc(4096, size); + buf_dst = aligned_alloc(4096, size); + if (!buf_src || !buf_dst) + t_error(0, -ENOMEM, "can't allocate buffers"); + + for (i = 0; i < size; i++) + buf_src[i] = 'a' + (i % 26); + + buf_iovec[0].iov_base = buf_src; + buf_iovec[0].iov_len = size; + buf_iovec[1].iov_base = buf_dst; + buf_iovec[1].iov_len = size; + ret = t_register_buffers(ring, buf_iovec, 2); + if (ret) { + free(buf_src); + free(buf_dst); + return ret == T_SETUP_SKIP ? 0 : T_EXIT_FAIL; + } + + memset(buf_dst, 0, size); + iov[0].iov_len = size; + iov[0].iov_base = buf_src; + ret = t_copy_verify_regvec(ring, mock_fd, iov, 1, buf_dst, true); + if (ret < 0) { + fprintf(stderr, "t_copy_verify_regvec iovec1 failed %i\n", ret); + return T_EXIT_FAIL; + } + + memset(buf_dst, 0, size); + iov[0].iov_len = size; + iov[0].iov_base = buf_dst; + ret = t_copy_verify_regvec(ring, mock_fd, iov, 1, buf_src, false); + if (ret < 0) { + fprintf(stderr, "t_copy_verify_regvec iovec1 reverse failed %i\n", ret); + return T_EXIT_FAIL; + } + + memset(buf_dst, 0, size); + iov[0].iov_base = buf_src; + iov[0].iov_len = 5; + iov[1].iov_base = buf_src + 5; + iov[1].iov_len = 11; + iov[2].iov_base = buf_src + (4096 - 127); + iov[2].iov_len = 127; + iov[3].iov_base = buf_src + (4096 - 127); + iov[3].iov_len = 127 + 4096 + 13; + iov[4].iov_base = buf_src + 4 * 4096; + iov[4].iov_len = 4096 + 73; + iov[5].iov_base = buf_src + 7 * 4096 + 127; + iov[5].iov_len = 4096 * 11 + 132; + assert(t_iovec_data_length(iov, 6) <= size); + ret = t_copy_verify_regvec(ring, mock_fd, iov, 6, buf_dst, true); + if (ret < 0) { + fprintf(stderr, "t_copy_verify_regvec iovec6 failed %i\n", ret); + return T_EXIT_FAIL; + } + + memset(buf_dst, 0, size); + iov[0].iov_base = buf_dst; + iov[0].iov_len = 5; + iov[1].iov_base = buf_dst + 5; + iov[1].iov_len = 11; + iov[2].iov_base = buf_dst + (4096 - 127); + iov[2].iov_len = 127; + iov[3].iov_base = buf_dst + 4 * 4096; + iov[3].iov_len = 4096 + 73; + iov[4].iov_base = buf_dst + 7 * 4096 + 127; + iov[4].iov_len = 4096 * 11 + 132; + assert(t_iovec_data_length(iov, 5) <= size); + ret = t_copy_verify_regvec(ring, mock_fd, iov, 5, buf_src, false); + if (ret < 0) { + fprintf(stderr, "t_copy_verify_regvec iovec6 reverse failed %i\n", ret); + return T_EXIT_FAIL; + } + + free(buf_src); + free(buf_dst); + return 0; +} + +static int test_cmds(void) +{ + struct io_uring_mock_create mc; + struct io_uring ring; + int ret, mock_fd; + + memset(&mc, 0, sizeof(mc)); + if (create_mock_file(&mc)) + return T_EXIT_FAIL; + mock_fd = mc.out_fd; + + ret = io_uring_queue_init(8, &ring, 0); + if (ret) { + fprintf(stderr, "ring setup failed: %d\n", ret); + return 1; + } + + if (has_feature(IORING_MOCK_FEAT_CMD_COPY)) { + ret = test_regvec_cmd(&ring, mock_fd); + if (ret) { + fprintf(stderr, "test_regvec_cmd() failed\n"); + return T_EXIT_FAIL; + } + } else { + printf("skip test_regvec_cmd()\n"); + } + + io_uring_queue_exit(&ring); + return 0; +} + +static int test_reads(struct io_uring *ring, int mock_fd, void *buffer) +{ + struct io_uring_cqe *cqe; + struct io_uring_sqe *sqe; + int io_len = 4096; + int nr_reqs = 16; + int i, ret; + + for (i = 0; i < nr_reqs; i++) { + sqe = io_uring_get_sqe(ring); + io_uring_prep_read(sqe, mock_fd, buffer, io_len, 0); + sqe->user_data = i; + } + + ret = io_uring_submit(ring); + if (ret != nr_reqs) { + fprintf(stderr, "submit got %d, wanted %d\n", ret, nr_reqs); + return T_EXIT_FAIL; + } + + for (i = 0; i < nr_reqs; i++) { + ret = io_uring_wait_cqe(ring, &cqe); + if (ret) { + fprintf(stderr, "wait_cqe=%d\n", ret); + return T_EXIT_FAIL; + } + if (cqe->res != io_len) { + fprintf(stderr, "unexpected cqe res %i, data %i\n", + cqe->res, (int)cqe->user_data); + return T_EXIT_FAIL; + } + io_uring_cqe_seen(ring, cqe); + } + return 0; +} + +static int test_rw(void) +{ + void *buffer; + struct io_uring ring; + int ret, i; + + if (!has_feature(IORING_MOCK_FEAT_RW_ZERO)) { + printf("no mock read-write support, skip\n"); + return T_EXIT_SKIP; + } + + buffer = malloc(4096); + if (!buffer) { + fprintf(stderr, "can't allocate buffers\n"); + return T_EXIT_FAIL; + } + + ret = io_uring_queue_init(32, &ring, 0); + if (ret) { + fprintf(stderr, "ring setup failed: %d\n", ret); + return 1; + } + + for (i = 0; i < 8; i++) { + struct io_uring_mock_create mc; + bool nowait = i & 1; + bool async = i & 2; + bool poll = i & 4; + int mock_fd; + + memset(&mc, 0, sizeof(mc)); + if (poll) { + if (!has_feature(IORING_MOCK_FEAT_POLL)) + continue; + mc.flags |= IORING_MOCK_CREATE_F_POLL; + } + if (nowait) { + if (!has_feature(IORING_MOCK_FEAT_RW_NOWAIT)) + continue; + mc.flags |= IORING_MOCK_CREATE_F_SUPPORT_NOWAIT; + } + if (async) { + if (!has_feature(IORING_MOCK_FEAT_RW_ASYNC)) + continue; + mc.rw_delay_ns = 1000 * 1000 * 50; + } + mc.file_size = 10 * (1UL << 20); + if (create_mock_file(&mc)) + return T_EXIT_FAIL; + mock_fd = mc.out_fd; + + ret = test_reads(&ring, mock_fd, buffer); + if (ret) { + fprintf(stderr, "rw failed %i/%i/%i\n", + nowait, async, poll); + return T_EXIT_FAIL; + } + + close(mock_fd); + } + + free(buffer); + io_uring_queue_exit(&ring); + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret; + + ret = setup_mgr(); + if (ret) + return ret; + + ret = test_cmds(); + if (ret) + return T_EXIT_FAIL; + + ret = test_rw(); + if (ret) { + fprintf(stderr, "test_rw failed %i\n", ret); + return T_EXIT_FAIL; + } + + io_uring_queue_exit(&mgr_ring); + close(mgr_fd); + return 0; +} diff --git a/test/mock_file.h b/test/mock_file.h new file mode 100644 index 00000000..debeee8e --- /dev/null +++ b/test/mock_file.h @@ -0,0 +1,47 @@ +#ifndef LINUX_IO_URING_MOCK_FILE_H +#define LINUX_IO_URING_MOCK_FILE_H + +#include + +enum { + IORING_MOCK_FEAT_CMD_COPY, + IORING_MOCK_FEAT_RW_ZERO, + IORING_MOCK_FEAT_RW_NOWAIT, + IORING_MOCK_FEAT_RW_ASYNC, + IORING_MOCK_FEAT_POLL, + + IORING_MOCK_FEAT_END, +}; + +struct io_uring_mock_probe { + __u64 features; + __u64 __resv[9]; +}; + +enum { + IORING_MOCK_CREATE_F_SUPPORT_NOWAIT = 1, + IORING_MOCK_CREATE_F_POLL = 2, +}; + +struct io_uring_mock_create { + __u32 out_fd; + __u32 flags; + __u64 file_size; + __u64 rw_delay_ns; + __u64 __resv[13]; +}; + +enum { + IORING_MOCK_MGR_CMD_PROBE, + IORING_MOCK_MGR_CMD_CREATE, +}; + +enum { + IORING_MOCK_CMD_COPY_REGBUF, +}; + +enum { + IORING_MOCK_COPY_FROM = 1, +}; + +#endif -- 2.49.0