| /* 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 <stdio.h> |
| #include <unistd.h> |
| #include <poll.h> |
| |
| #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; |
| } |