blob: 4a5794c18b9f1fe5fe921980c4ebda62624f4c70 [file]
/* SPDX-License-Identifier: MIT */
/*
* Description: test NOP flags (IORING_NOP_FILE, IORING_NOP_FIXED_FILE,
* IORING_NOP_FIXED_BUFFER, IORING_NOP_TW, IORING_NOP_CQE32)
*
* These flags allow NOP to exercise file lookups, buffer lookups, and
* task_work completion paths without real I/O, useful for targeted testing.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "liburing.h"
#include "helpers.h"
#ifndef IORING_NOP_INJECT_RESULT
#define IORING_NOP_INJECT_RESULT (1U << 0)
#endif
#ifndef IORING_NOP_FILE
#define IORING_NOP_FILE (1U << 1)
#endif
#ifndef IORING_NOP_FIXED_FILE
#define IORING_NOP_FIXED_FILE (1U << 2)
#endif
#ifndef IORING_NOP_FIXED_BUFFER
#define IORING_NOP_FIXED_BUFFER (1U << 3)
#endif
#ifndef IORING_NOP_TW
#define IORING_NOP_TW (1U << 4)
#endif
#ifndef IORING_NOP_CQE32
#define IORING_NOP_CQE32 (1U << 5)
#endif
static int no_nop_flags;
static int no_nop_file;
/*
* Detect NOP flags support using INJECT_RESULT. A kernel that supports
* NOP flags will return the injected value; one that doesn't will
* return 0 (plain NOP) or -EINVAL.
*/
static int test_nop_detect(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret;
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_INJECT_RESULT;
sqe->len = 42;
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit detect: %d\n", ret);
return T_EXIT_FAIL;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait detect: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res != 42) {
/* Kernel doesn't support NOP flags */
no_nop_flags = 1;
io_uring_cqe_seen(ring, cqe);
return T_EXIT_SKIP;
}
io_uring_cqe_seen(ring, cqe);
return T_EXIT_PASS;
}
/*
* Detect IORING_NOP_FILE support by using it with a bad fd.
* If supported, returns -EBADF. If not, returns 0 (NOP succeeds,
* kernel ignores the FILE flag).
*/
static int test_nop_file_bad_fd(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret;
if (no_nop_flags)
return T_EXIT_SKIP;
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FILE;
sqe->fd = 9999; /* invalid fd */
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit: %d\n", ret);
return T_EXIT_FAIL;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res == 0) {
/* Kernel doesn't support IORING_NOP_FILE */
no_nop_file = 1;
io_uring_cqe_seen(ring, cqe);
return T_EXIT_SKIP;
}
if (cqe->res != -EBADF && cqe->res != -EINVAL) {
fprintf(stderr, "nop bad fd res: %d (expected -EBADF)\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
return T_EXIT_PASS;
}
/*
* Test IORING_NOP_FILE - NOP with a normal fd lookup
*/
static int test_nop_file(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret, fd;
if (no_nop_flags || no_nop_file)
return T_EXIT_SKIP;
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
perror("open /dev/null");
return T_EXIT_FAIL;
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FILE;
sqe->fd = fd;
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit: %d\n", ret);
close(fd);
return T_EXIT_FAIL;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait: %d\n", ret);
close(fd);
return T_EXIT_FAIL;
}
if (cqe->res != 0) {
if (cqe->res == -EINVAL) {
no_nop_file = 1;
return T_EXIT_SKIP;
}
fprintf(stderr, "nop file res: %d\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
close(fd);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
close(fd);
return T_EXIT_PASS;
}
/*
* Test IORING_NOP_FIXED_FILE - NOP with a fixed file lookup
*/
static int test_nop_fixed_file(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret, fd;
if (no_nop_flags || no_nop_file)
return T_EXIT_SKIP;
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
perror("open /dev/null");
return T_EXIT_FAIL;
}
ret = io_uring_register_files(ring, &fd, 1);
if (ret) {
fprintf(stderr, "register files: %d\n", ret);
close(fd);
return T_EXIT_FAIL;
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FILE | IORING_NOP_FIXED_FILE;
sqe->fd = 0; /* fixed file index */
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit: %d\n", ret);
goto err;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait: %d\n", ret);
goto err;
}
if (cqe->res != 0) {
fprintf(stderr, "nop fixed file res: %d\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
goto err;
}
io_uring_cqe_seen(ring, cqe);
/* Test with invalid fixed file index */
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FILE | IORING_NOP_FIXED_FILE;
sqe->fd = 999; /* invalid index */
sqe->user_data = 2;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit bad index: %d\n", ret);
goto err;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait bad index: %d\n", ret);
goto err;
}
if (cqe->res != -EBADF) {
fprintf(stderr, "nop bad fixed file res: %d (expected -EBADF)\n",
cqe->res);
io_uring_cqe_seen(ring, cqe);
goto err;
}
io_uring_cqe_seen(ring, cqe);
io_uring_unregister_files(ring);
close(fd);
return T_EXIT_PASS;
err:
io_uring_unregister_files(ring);
close(fd);
return T_EXIT_FAIL;
}
/*
* Test IORING_NOP_FIXED_BUFFER - NOP with a registered buffer lookup
*/
static int test_nop_fixed_buffer(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
char buf[4096];
struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
int ret;
if (no_nop_flags)
return T_EXIT_SKIP;
ret = io_uring_register_buffers(ring, &iov, 1);
if (ret) {
fprintf(stderr, "register buffers: %d\n", ret);
return T_EXIT_FAIL;
}
/* Valid buffer index */
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FIXED_BUFFER;
sqe->buf_index = 0;
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit: %d\n", ret);
goto err;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait: %d\n", ret);
goto err;
}
if (cqe->res != 0) {
if (cqe->res == -EINVAL) {
io_uring_cqe_seen(ring, cqe);
return T_EXIT_SKIP;
}
fprintf(stderr, "nop fixed buffer res: %d\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
goto err;
}
io_uring_cqe_seen(ring, cqe);
/* Invalid buffer index */
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FIXED_BUFFER;
sqe->buf_index = 999;
sqe->user_data = 2;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit bad buf: %d\n", ret);
goto err;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait bad buf: %d\n", ret);
goto err;
}
if (cqe->res == 0) {
/* Kernel doesn't support IORING_NOP_FIXED_BUFFER */
io_uring_cqe_seen(ring, cqe);
io_uring_unregister_buffers(ring);
return T_EXIT_SKIP;
}
if (cqe->res != -EFAULT) {
fprintf(stderr, "nop bad buffer res: %d (expected -EFAULT)\n",
cqe->res);
io_uring_cqe_seen(ring, cqe);
goto err;
}
io_uring_cqe_seen(ring, cqe);
io_uring_unregister_buffers(ring);
return T_EXIT_PASS;
err:
io_uring_unregister_buffers(ring);
return T_EXIT_FAIL;
}
/*
* Test IORING_NOP_TW - NOP completing via task_work
*/
static int test_nop_tw(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret, i;
if (no_nop_flags)
return T_EXIT_SKIP;
/* Submit several NOPs via task_work path */
for (i = 0; i < 8; i++) {
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_TW;
sqe->user_data = i + 1;
}
ret = io_uring_submit(ring);
if (ret != 8) {
fprintf(stderr, "submit tw: %d\n", ret);
return T_EXIT_FAIL;
}
for (i = 0; i < 8; i++) {
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait tw: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res != 0) {
if (cqe->res == -EINVAL) {
io_uring_cqe_seen(ring, cqe);
return T_EXIT_SKIP;
}
fprintf(stderr, "nop tw res: %d\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
}
return T_EXIT_PASS;
}
/*
* Test IORING_NOP_TW combined with IORING_NOP_INJECT_RESULT
*/
static int test_nop_tw_inject(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret;
if (no_nop_flags)
return T_EXIT_SKIP;
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_TW | IORING_NOP_INJECT_RESULT;
sqe->len = 42;
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit: %d\n", ret);
return T_EXIT_FAIL;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res != 42) {
if (cqe->res == -EINVAL) {
io_uring_cqe_seen(ring, cqe);
return T_EXIT_SKIP;
}
fprintf(stderr, "nop tw inject res: %d (expected 42)\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
return T_EXIT_PASS;
}
/*
* Test IORING_NOP_CQE32 - NOP with extra1/extra2 in big CQE
*/
static int test_nop_cqe32(void)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct io_uring ring;
int ret;
if (no_nop_flags)
return T_EXIT_SKIP;
ret = io_uring_queue_init(8, &ring, IORING_SETUP_CQE32);
if (ret) {
if (ret == -EINVAL)
return T_EXIT_SKIP;
fprintf(stderr, "ring setup cqe32: %d\n", ret);
return T_EXIT_FAIL;
}
sqe = io_uring_get_sqe(&ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_CQE32;
sqe->off = 0xdeadbeef12345678ULL; /* extra1 */
sqe->addr = 0xabcdef0011223344ULL; /* extra2 */
sqe->user_data = 1;
ret = io_uring_submit(&ring);
if (ret != 1) {
fprintf(stderr, "submit cqe32: %d\n", ret);
goto err;
}
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret) {
fprintf(stderr, "wait cqe32: %d\n", ret);
goto err;
}
if (cqe->res != 0) {
if (cqe->res == -EINVAL)
goto skip;
fprintf(stderr, "nop cqe32 res: %d\n", cqe->res);
io_uring_cqe_seen(&ring, cqe);
goto err;
}
if (cqe->big_cqe[0] != 0xdeadbeef12345678ULL) {
fprintf(stderr, "cqe32 extra1: 0x%llx (expected 0xdeadbeef12345678)\n",
(unsigned long long) cqe->big_cqe[0]);
io_uring_cqe_seen(&ring, cqe);
goto err;
}
if (cqe->big_cqe[1] != 0xabcdef0011223344ULL) {
fprintf(stderr, "cqe32 extra2: 0x%llx (expected 0xabcdef0011223344)\n",
(unsigned long long) cqe->big_cqe[1]);
io_uring_cqe_seen(&ring, cqe);
goto err;
}
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
return T_EXIT_PASS;
err:
io_uring_queue_exit(&ring);
return T_EXIT_FAIL;
skip:
io_uring_queue_exit(&ring);
return T_EXIT_SKIP;
}
/*
* Test IORING_NOP_CQE32 requires CQE32 or CQE_MIXED ring
*/
static int test_nop_cqe32_no_ring_support(void)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct io_uring ring;
int ret;
if (no_nop_flags)
return T_EXIT_SKIP;
/* Ring without CQE32 */
ret = io_uring_queue_init(8, &ring, 0);
if (ret) {
fprintf(stderr, "ring setup: %d\n", ret);
return T_EXIT_FAIL;
}
sqe = io_uring_get_sqe(&ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_CQE32;
sqe->off = 1;
sqe->addr = 2;
sqe->user_data = 1;
ret = io_uring_submit(&ring);
if (ret != 1) {
fprintf(stderr, "submit: %d\n", ret);
io_uring_queue_exit(&ring);
return T_EXIT_FAIL;
}
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret) {
fprintf(stderr, "wait: %d\n", ret);
io_uring_queue_exit(&ring);
return T_EXIT_FAIL;
}
if (cqe->res != -EINVAL) {
fprintf(stderr, "nop cqe32 no support res: %d (expected -EINVAL)\n",
cqe->res);
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
return T_EXIT_PASS;
}
/*
* Test combined flags: FILE + FIXED_FILE + FIXED_BUFFER + TW
*/
static int test_nop_combined(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
char buf[4096];
struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
int ret, fd;
if (no_nop_flags || no_nop_file)
return T_EXIT_SKIP;
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
perror("open /dev/null");
return T_EXIT_FAIL;
}
ret = io_uring_register_files(ring, &fd, 1);
if (ret) {
fprintf(stderr, "register files: %d\n", ret);
close(fd);
return T_EXIT_FAIL;
}
ret = io_uring_register_buffers(ring, &iov, 1);
if (ret) {
fprintf(stderr, "register buffers: %d\n", ret);
io_uring_unregister_files(ring);
close(fd);
return T_EXIT_FAIL;
}
/* All flags combined */
sqe = io_uring_get_sqe(ring);
io_uring_prep_nop(sqe);
sqe->nop_flags = IORING_NOP_FILE | IORING_NOP_FIXED_FILE |
IORING_NOP_FIXED_BUFFER | IORING_NOP_TW;
sqe->fd = 0;
sqe->buf_index = 0;
sqe->user_data = 1;
ret = io_uring_submit(ring);
if (ret != 1) {
fprintf(stderr, "submit combined: %d\n", ret);
goto err;
}
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "wait combined: %d\n", ret);
goto err;
}
if (cqe->res != 0) {
fprintf(stderr, "nop combined res: %d\n", cqe->res);
io_uring_cqe_seen(ring, cqe);
goto err;
}
io_uring_cqe_seen(ring, cqe);
io_uring_unregister_buffers(ring);
io_uring_unregister_files(ring);
close(fd);
return T_EXIT_PASS;
err:
io_uring_unregister_buffers(ring);
io_uring_unregister_files(ring);
close(fd);
return T_EXIT_FAIL;
}
int main(int argc, char *argv[])
{
struct io_uring ring;
int ret;
if (argc > 1)
return T_EXIT_SKIP;
ret = io_uring_queue_init(8, &ring, IORING_SETUP_SUBMIT_ALL);
if (ret) {
if (ret == -EINVAL)
return T_EXIT_SKIP;
fprintf(stderr, "ring setup failed: %d\n", ret);
return T_EXIT_FAIL;
}
ret = test_nop_detect(&ring);
if (ret == T_EXIT_SKIP) {
printf("NOP flags not supported, skipping\n");
io_uring_queue_exit(&ring);
return T_EXIT_SKIP;
} else if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_detect failed\n");
return T_EXIT_FAIL;
}
/* Use bad fd test to detect NOP_FILE support */
ret = test_nop_file_bad_fd(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_file_bad_fd failed\n");
return T_EXIT_FAIL;
}
ret = test_nop_file(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_file failed\n");
return T_EXIT_FAIL;
}
ret = test_nop_fixed_file(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_fixed_file failed\n");
return T_EXIT_FAIL;
}
ret = test_nop_fixed_buffer(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_fixed_buffer failed\n");
return T_EXIT_FAIL;
}
ret = test_nop_tw(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_tw failed\n");
return T_EXIT_FAIL;
}
ret = test_nop_tw_inject(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_tw_inject failed\n");
return T_EXIT_FAIL;
}
io_uring_queue_exit(&ring);
ret = test_nop_cqe32();
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_cqe32 failed\n");
return T_EXIT_FAIL;
}
ret = test_nop_cqe32_no_ring_support();
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_cqe32_no_ring_support failed\n");
return T_EXIT_FAIL;
}
/* New ring for combined test since we need fresh file/buffer tables */
ret = io_uring_queue_init(8, &ring, 0);
if (ret) {
fprintf(stderr, "ring setup failed: %d\n", ret);
return T_EXIT_FAIL;
}
ret = test_nop_combined(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_nop_combined failed\n");
return T_EXIT_FAIL;
}
io_uring_queue_exit(&ring);
return T_EXIT_PASS;
}