#include "../config-host.h" /* SPDX-License-Identifier: MIT */ /* * Description: Test IORING_ASYNC_CANCEL_FD | IORING_ASYNC_CANCEL_USERDATA * * Tests that combining CANCEL_FD and CANCEL_USERDATA correctly matches * requests by both file descriptor and user_data. These share a union * in the kernel (io_cancel_data.data / io_cancel_data.file), so this * exercises that the union is handled correctly. */ #include #include #include #include "liburing.h" #include "helpers.h" static int cancel_all(struct io_uring *ring, int fd, int fixed, int nr) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int i, ret; sqe = io_uring_get_sqe(ring); io_uring_prep_cancel(sqe, 0, IORING_ASYNC_CANCEL_ALL); sqe->cancel_flags |= IORING_ASYNC_CANCEL_FD; if (fixed) sqe->cancel_flags |= IORING_ASYNC_CANCEL_FD_FIXED; sqe->fd = fd; sqe->user_data = 200; ret = io_uring_submit(ring); if (ret != 1) return 1; for (i = 0; i < nr + 1; i++) { ret = io_uring_wait_cqe(ring, &cqe); if (ret) return 1; io_uring_cqe_seen(ring, cqe); } return 0; } /* * Submit multiple polls on the same fd with different user_data values. * Cancel with CANCEL_FD | CANCEL_USERDATA targeting a specific user_data. * Only the request matching both fd AND user_data should be canceled. */ static int test_cancel_fd_userdata(struct io_uring *ring, int *fd, int fixed) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret, i, __fd = fd[0]; int target_ud = 2; int canceled = 0; if (fixed) { ret = io_uring_register_files(ring, fd, 2); if (ret) { fprintf(stderr, "file register: %d\n", ret); return T_EXIT_FAIL; } __fd = 0; } /* Submit 4 polls on the same fd, with user_data 1..4 */ for (i = 0; i < 4; i++) { sqe = io_uring_get_sqe(ring); if (!sqe) { fprintf(stderr, "get sqe failed\n"); return T_EXIT_FAIL; } io_uring_prep_poll_add(sqe, __fd, POLLIN); sqe->user_data = i + 1; if (fixed) sqe->flags |= IOSQE_FIXED_FILE; } ret = io_uring_submit(ring); if (ret != 4) { fprintf(stderr, "submit: %d\n", ret); return T_EXIT_FAIL; } /* Cancel only the request with user_data == target_ud on this fd */ sqe = io_uring_get_sqe(ring); if (!sqe) { fprintf(stderr, "get sqe failed\n"); return T_EXIT_FAIL; } io_uring_prep_cancel(sqe, 0, 0); sqe->cancel_flags = IORING_ASYNC_CANCEL_FD | IORING_ASYNC_CANCEL_USERDATA; if (fixed) sqe->cancel_flags |= IORING_ASYNC_CANCEL_FD_FIXED; sqe->fd = __fd; sqe->addr = target_ud; sqe->user_data = 100; ret = io_uring_submit(ring); if (ret != 1) { fprintf(stderr, "cancel submit: %d\n", ret); return T_EXIT_FAIL; } /* * Get the cancel CQE first. If the kernel doesn't support these * cancel flags, it will return -EINVAL and we skip the test. */ ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait: %d\n", ret); return T_EXIT_FAIL; } /* * The cancel CQE may arrive before or after the canceled poll CQE. * If we got the cancel result first, check it. If we got a poll * completion first, consume it and get the cancel result next. */ if (cqe->user_data == 100) { if (cqe->res == -EINVAL) { io_uring_cqe_seen(ring, cqe); cancel_all(ring, __fd, fixed, 4); if (fixed) io_uring_unregister_files(ring); return T_EXIT_SKIP; } if (cqe->res < 0) { fprintf(stderr, "cancel failed: %d\n", cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); /* Now get the canceled poll CQE */ ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait: %d\n", ret); return T_EXIT_FAIL; } if (cqe->user_data != target_ud || cqe->res != -ECANCELED) { fprintf(stderr, "unexpected ud=%lu res=%d\n", (unsigned long)cqe->user_data, cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); canceled++; } else if (cqe->user_data == target_ud) { if (cqe->res != -ECANCELED) { fprintf(stderr, "poll ud=%d res=%d\n", target_ud, cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } canceled++; io_uring_cqe_seen(ring, cqe); /* Now get the cancel result CQE */ ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait: %d\n", ret); return T_EXIT_FAIL; } if (cqe->user_data != 100 || cqe->res < 0) { fprintf(stderr, "cancel ud=%lu res=%d\n", (unsigned long)cqe->user_data, cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); } else { fprintf(stderr, "unexpected user_data %lu res %d\n", (unsigned long)cqe->user_data, cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } if (canceled != 1) { fprintf(stderr, "expected 1 canceled, got %d\n", canceled); return T_EXIT_FAIL; } /* The other 3 polls should still be pending - cancel them */ if (cancel_all(ring, __fd, fixed, 3)) { fprintf(stderr, "cleanup failed\n"); return T_EXIT_FAIL; } if (fixed) io_uring_unregister_files(ring); return T_EXIT_PASS; } /* * Submit polls on two different fds with the same user_data. * Cancel with CANCEL_FD | CANCEL_USERDATA should only match the * request on the right fd with the right user_data. */ static int test_cancel_fd_userdata_two_fds(struct io_uring *ring, int *fd1, int *fd2) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret, i; int canceled = 0; /* Poll on fd1 with user_data=1 */ sqe = io_uring_get_sqe(ring); io_uring_prep_poll_add(sqe, fd1[0], POLLIN); sqe->user_data = 1; /* Poll on fd2 with user_data=1 (same user_data, different fd) */ sqe = io_uring_get_sqe(ring); io_uring_prep_poll_add(sqe, fd2[0], POLLIN); sqe->user_data = 1; /* Poll on fd1 with user_data=2 (same fd, different user_data) */ sqe = io_uring_get_sqe(ring); io_uring_prep_poll_add(sqe, fd1[0], POLLIN); sqe->user_data = 2; ret = io_uring_submit(ring); if (ret != 3) { fprintf(stderr, "submit: %d\n", ret); return T_EXIT_FAIL; } /* * Cancel: fd=fd1[0], user_data=1. Should only cancel the first * poll (fd1, ud=1), not the second (fd2, ud=1) or third (fd1, ud=2). */ sqe = io_uring_get_sqe(ring); io_uring_prep_cancel(sqe, 0, 0); sqe->cancel_flags = IORING_ASYNC_CANCEL_FD | IORING_ASYNC_CANCEL_USERDATA; sqe->fd = fd1[0]; sqe->addr = 1; sqe->user_data = 100; ret = io_uring_submit(ring); if (ret != 1) { fprintf(stderr, "cancel submit: %d\n", ret); return T_EXIT_FAIL; } for (i = 0; i < 2; i++) { ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait: %d\n", ret); return T_EXIT_FAIL; } if (cqe->user_data == 100) { if (cqe->res < 0) { fprintf(stderr, "cancel res: %d\n", cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } } else if (cqe->user_data == 1) { if (cqe->res != -ECANCELED) { fprintf(stderr, "poll res: %d\n", cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } canceled++; } else { fprintf(stderr, "unexpected ud=%lu res=%d\n", (unsigned long)cqe->user_data, cqe->res); io_uring_cqe_seen(ring, cqe); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); } if (canceled != 1) { fprintf(stderr, "expected 1 cancel, got %d\n", canceled); return T_EXIT_FAIL; } /* Clean up: cancel all remaining */ sqe = io_uring_get_sqe(ring); io_uring_prep_cancel(sqe, 0, IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_ALL); sqe->user_data = 200; ret = io_uring_submit(ring); if (ret != 1) { fprintf(stderr, "cleanup submit: %d\n", ret); return T_EXIT_FAIL; } for (i = 0; i < 3; i++) { ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "cleanup wait: %d\n", ret); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); } return T_EXIT_PASS; } int main(int argc, char *argv[]) { struct io_uring ring; int ret, fd[2], fd2[2]; if (argc > 1) return T_EXIT_SKIP; if (pipe(fd) < 0) { perror("pipe"); return T_EXIT_FAIL; } ret = io_uring_queue_init(8, &ring, 0); if (ret) { fprintf(stderr, "ring setup: %d\n", ret); return T_EXIT_FAIL; } ret = test_cancel_fd_userdata(&ring, fd, 0); if (ret == T_EXIT_SKIP) return T_EXIT_SKIP; if (ret) { fprintf(stderr, "test normal fd failed\n"); return T_EXIT_FAIL; } ret = test_cancel_fd_userdata(&ring, fd, 1); if (ret == T_EXIT_SKIP) return T_EXIT_SKIP; if (ret) { fprintf(stderr, "test fixed fd failed\n"); return T_EXIT_FAIL; } if (pipe(fd2) < 0) { perror("pipe"); return T_EXIT_FAIL; } ret = test_cancel_fd_userdata_two_fds(&ring, fd, fd2); if (ret) { fprintf(stderr, "test two fds failed\n"); return ret; } close(fd2[0]); close(fd2[1]); close(fd[0]); close(fd[1]); io_uring_queue_exit(&ring); return T_EXIT_PASS; }