#include "../config-host.h" /* 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 T_EXIT_SKIP; 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; }