/* SPDX-License-Identifier: MIT */ /* * Description: tests linked requests failing during submission */ #include #include #include #include #include #include #include #include "liburing.h" #define DRAIN_USER_DATA 42 static int test_underprep_fail(bool hardlink, bool drain, bool link_last, int link_size, int fail_idx) { const int invalid_fd = 42; int link_flags = IOSQE_IO_LINK; int total_submit = link_size; struct io_uring ring; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; char buffer[1]; int i, ret, fds[2]; if (drain) link_flags |= IOSQE_IO_DRAIN; if (hardlink) link_flags |= IOSQE_IO_HARDLINK; assert(fail_idx < link_size); assert(link_size < 40); /* create a new ring as it leaves it dirty */ ret = io_uring_queue_init(8, &ring, 0); if (ret) { printf("ring setup failed\n"); return -1; } if (pipe(fds)) { perror("pipe"); return -1; } if (drain) { /* clog drain, so following reqs sent to draining */ sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, fds[0], buffer, sizeof(buffer), 0); sqe->user_data = DRAIN_USER_DATA; sqe->flags |= IOSQE_IO_DRAIN; total_submit++; } for (i = 0; i < link_size; i++) { sqe = io_uring_get_sqe(&ring); if (i == fail_idx) { io_uring_prep_read(sqe, invalid_fd, buffer, 1, 0); sqe->ioprio = (short) -1; } else { io_uring_prep_nop(sqe); } if (i != link_size - 1 || !link_last) sqe->flags |= link_flags; sqe->user_data = i; } ret = io_uring_submit(&ring); if (ret != total_submit) { /* Old behaviour, failed early and under-submitted */ if (ret == fail_idx + 1 + drain) goto out; fprintf(stderr, "submit failed: %d\n", ret); return -1; } if (drain) { /* unclog drain */ ret = write(fds[1], buffer, sizeof(buffer)); if (ret < 0) { perror("write"); return 1; } } for (i = 0; i < total_submit; i++) { ret = io_uring_wait_cqe(&ring, &cqe); if (ret) { fprintf(stderr, "wait_cqe=%d\n", ret); return 1; } ret = cqe->res; if (cqe->user_data == DRAIN_USER_DATA) { if (ret != 1) { fprintf(stderr, "drain failed %d\n", ret); return 1; } } else if (cqe->user_data == fail_idx) { if (ret == 0 || ret == -ECANCELED) { fprintf(stderr, "half-prep req unexpected return %d\n", ret); return 1; } } else { if (ret != -ECANCELED) { fprintf(stderr, "cancel failed %d, ud %d\n", ret, (int)cqe->user_data); return 1; } } io_uring_cqe_seen(&ring, cqe); } out: close(fds[0]); close(fds[1]); io_uring_queue_exit(&ring); return 0; } int main(int argc, char *argv[]) { int ret, link_size, fail_idx, i; if (argc > 1) return 0; /* * hardlink, size=3, fail_idx=1, drain=false -- kernel fault * link, size=3, fail_idx=0, drain=true -- kernel fault * link, size=3, fail_idx=1, drain=true -- invalid cqe->res */ for (link_size = 0; link_size < 3; link_size++) { for (fail_idx = 0; fail_idx < link_size; fail_idx++) { for (i = 0; i < 8; i++) { bool hardlink = (i & 1) != 0; bool drain = (i & 2) != 0; bool link_last = (i & 4) != 0; ret = test_underprep_fail(hardlink, drain, link_last, link_size, fail_idx); if (!ret) continue; fprintf(stderr, "failed %d, hard %d, drain %d," "link_last %d, size %d, idx %d\n", ret, hardlink, drain, link_last, link_size, fail_idx); return 1; } } } return 0; }