blob: 971fd9ba3b23037f7d7a93446683b3b418166868 [file] [log] [blame]
/* SPDX-License-Identifier: MIT */
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#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;
}