/* SPDX-License-Identifier: MIT */ /* * Description: test io_uring poll handling * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" struct thread_data { struct io_uring *ring; int fd; int events; const char *test; int out[2]; }; static void *epoll_wait_fn(void *data) { struct thread_data *td = data; struct epoll_event ev; if (epoll_wait(td->fd, &ev, 1, -1) < 0) { perror("epoll_wait"); goto err; } return NULL; err: return (void *) 1; } static void *iou_poll(void *data) { struct thread_data *td = data; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret; sqe = io_uring_get_sqe(td->ring); io_uring_prep_poll_add(sqe, td->fd, td->events); ret = io_uring_submit(td->ring); if (ret != 1) { fprintf(stderr, "submit got %d\n", ret); goto err; } ret = io_uring_wait_cqe(td->ring, &cqe); if (ret) { fprintf(stderr, "wait_cqe: %d\n", ret); goto err; } td->out[0] = cqe->res & 0x3f; io_uring_cqe_seen(td->ring, cqe); return NULL; err: return (void *) 1; } static void *poll_pipe(void *data) { struct thread_data *td = data; struct pollfd pfd; int ret; pfd.fd = td->fd; pfd.events = td->events; ret = poll(&pfd, 1, -1); if (ret < 0) perror("poll"); td->out[1] = pfd.revents; return NULL; } static int do_pipe_pollin_test(struct io_uring *ring) { struct thread_data td; pthread_t threads[2]; int ret, pipe1[2]; char buf; if (pipe(pipe1) < 0) { perror("pipe"); return 1; } td.ring = ring; td.fd = pipe1[0]; td.events = POLLIN; td.test = __FUNCTION__; pthread_create(&threads[1], NULL, iou_poll, &td); pthread_create(&threads[0], NULL, poll_pipe, &td); usleep(100000); buf = 0x89; ret = write(pipe1[1], &buf, sizeof(buf)); if (ret != sizeof(buf)) { fprintf(stderr, "write failed: %d\n", ret); return 1; } pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); if (td.out[0] != td.out[1]) { fprintf(stderr, "%s: res %x/%x differ\n", __FUNCTION__, td.out[0], td.out[1]); return 1; } return 0; } static int do_pipe_pollout_test(struct io_uring *ring) { struct thread_data td; pthread_t threads[2]; int ret, pipe1[2]; char buf; if (pipe(pipe1) < 0) { perror("pipe"); return 1; } td.ring = ring; td.fd = pipe1[1]; td.events = POLLOUT; td.test = __FUNCTION__; pthread_create(&threads[0], NULL, poll_pipe, &td); pthread_create(&threads[1], NULL, iou_poll, &td); usleep(100000); buf = 0x89; ret = write(pipe1[1], &buf, sizeof(buf)); if (ret != sizeof(buf)) { fprintf(stderr, "write failed: %d\n", ret); return 1; } pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); if (td.out[0] != td.out[1]) { fprintf(stderr, "%s: res %x/%x differ\n", __FUNCTION__, td.out[0], td.out[1]); return 1; } return 0; } static int do_fd_test(struct io_uring *ring, const char *fname, int events) { struct thread_data td; pthread_t threads[2]; int fd; fd = open(fname, O_RDONLY); if (fd < 0) { if (errno == EPERM || errno == EACCES) return T_EXIT_SKIP; perror("open"); return 1; } td.ring = ring; td.fd = fd; td.events = events; td.test = __FUNCTION__; pthread_create(&threads[0], NULL, poll_pipe, &td); pthread_create(&threads[1], NULL, iou_poll, &td); pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); if (td.out[0] != td.out[1]) { fprintf(stderr, "%s: res %x/%x differ\n", __FUNCTION__, td.out[0], td.out[1]); return 1; } return 0; } static int iou_epoll_ctl(struct io_uring *ring, int epfd, int fd, struct epoll_event *ev) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret; sqe = io_uring_get_sqe(ring); if (!sqe) { fprintf(stderr, "Failed to get sqe\n"); return 1; } io_uring_prep_epoll_ctl(sqe, epfd, fd, EPOLL_CTL_ADD, ev); ret = io_uring_submit(ring); if (ret != 1) { fprintf(stderr, "submit: %d\n", ret); return 1; } ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait_cqe: %d\n", ret); return 1; } ret = cqe->res; io_uring_cqe_seen(ring, cqe); return ret; } static int do_test_epoll(struct io_uring *ring, int iou_epoll_add) { struct epoll_event ev; struct thread_data td; pthread_t threads[2]; int ret, pipe1[2]; char buf; int fd; fd = epoll_create1(0); if (fd < 0) { perror("epoll_create"); return 1; } if (pipe(pipe1) < 0) { perror("pipe"); return 1; } ev.events = EPOLLIN; ev.data.fd = pipe1[0]; if (!iou_epoll_add) { if (epoll_ctl(fd, EPOLL_CTL_ADD, pipe1[0], &ev) < 0) { perror("epoll_ctrl"); return 1; } } else { ret = iou_epoll_ctl(ring, fd, pipe1[0], &ev); if (ret == -EINVAL) { fprintf(stdout, "epoll not supported, skipping\n"); return 0; } else if (ret < 0) { return 1; } } td.ring = ring; td.fd = fd; td.events = POLLIN; td.test = __FUNCTION__; pthread_create(&threads[0], NULL, iou_poll, &td); pthread_create(&threads[1], NULL, epoll_wait_fn, &td); usleep(100000); buf = 0x89; ret = write(pipe1[1], &buf, sizeof(buf)); if (ret != sizeof(buf)) { fprintf(stderr, "write failed: %d\n", ret); return 1; } pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); return 0; } int main(int argc, char *argv[]) { struct io_uring ring; const char *fname; int ret; ret = io_uring_queue_init(1, &ring, 0); if (ret) { fprintf(stderr, "ring setup failed\n"); return 1; } ret = do_pipe_pollin_test(&ring); if (ret) { fprintf(stderr, "pipe pollin test failed\n"); return ret; } ret = do_pipe_pollout_test(&ring); if (ret) { fprintf(stderr, "pipe pollout test failed\n"); return ret; } ret = do_test_epoll(&ring, 0); if (ret) { fprintf(stderr, "epoll test 0 failed\n"); return ret; } ret = do_test_epoll(&ring, 1); if (ret) { fprintf(stderr, "epoll test 1 failed\n"); return ret; } if (argc > 1) fname = argv[1]; else fname = argv[0]; ret = do_fd_test(&ring, fname, POLLIN); if (ret == T_EXIT_FAIL) { fprintf(stderr, "fd test IN failed\n"); return ret; } ret = do_fd_test(&ring, fname, POLLOUT); if (ret == T_EXIT_FAIL) { fprintf(stderr, "fd test OUT failed\n"); return ret; } ret = do_fd_test(&ring, fname, POLLOUT | POLLIN); if (ret == T_EXIT_FAIL) { fprintf(stderr, "fd test IN|OUT failed\n"); return ret; } return 0; }