/* SPDX-License-Identifier: MIT */ /* * Test that the final close of a file does indeed get it closed, if the * ring is setup with DEFER_TASKRUN and the task is waiting in cqring_wait() * during. Also see: * * https://github.com/axboe/liburing/issues/1235 * * for a bug report, and the zig code on which this test program is based. */ #include #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" enum { IS_ACCEPT = 0, IS_SEND = 0x100, IS_SEND2 = 0x101, IS_SEND3 = 0x102, IS_CLOSE = 0x200, }; struct thread_data { int parent_pid; }; static void *thread_fn(void *__data) { struct thread_data *data = __data; struct sockaddr_in saddr; int sockfd, ret; char msg[64]; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); goto done; } ret = connect(sockfd, (struct sockaddr *) &saddr, sizeof(saddr)); if (ret < 0) { perror("connect"); close(sockfd); goto done; } do { memset(msg, 0, sizeof(msg)); ret = recv(sockfd, msg, sizeof(msg), 0); } while (ret > 0); close(sockfd); done: kill(data->parent_pid, SIGUSR1); return NULL; } /* we got SIGUSR1, exit normally */ static void sig_usr1(int sig) { exit(T_EXIT_PASS); } /* timed out, failure */ static void sig_timeout(int sig) { exit(T_EXIT_FAIL); } int main(int argc, char *argv[]) { struct io_uring ring; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct sockaddr_in saddr; char *msg1 = "message number 1\n"; char *msg2 = "message number 2\n"; char *msg3 = "message number 3\n"; int val, send_fd, ret, sockfd; struct sigaction act[2] = { }; struct thread_data td; pthread_t thread; if (argc > 1) return T_EXIT_SKIP; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = htons(9999); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); return T_EXIT_FAIL; } val = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)); if (ret < 0) { perror("bind"); close(sockfd); return T_EXIT_FAIL; } ret = listen(sockfd, 1); if (ret < 0) { perror("listen"); close(sockfd); return T_EXIT_FAIL; } ret = io_uring_queue_init(8, &ring, IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN); if (ret == -EINVAL) { close(sockfd); return T_EXIT_SKIP; } sqe = io_uring_get_sqe(&ring); io_uring_prep_multishot_accept(sqe, sockfd, NULL, NULL, 0); sqe->user_data = IS_ACCEPT; io_uring_submit(&ring); /* check for no multishot accept */ ret = io_uring_peek_cqe(&ring, &cqe); if (!ret && cqe->res == -EINVAL) { close(sockfd); return T_EXIT_SKIP; } /* expected exit */ act[0].sa_handler = sig_usr1; sigaction(SIGUSR1, &act[0], NULL); /* if this hits, we have failed */ act[1].sa_handler = sig_timeout; sigaction(SIGALRM, &act[1], NULL); alarm(5); /* start receiver */ td.parent_pid = getpid(); pthread_create(&thread, NULL, thread_fn, &td); do { ret = io_uring_submit_and_wait(&ring, 1); if (ret < 0) { fprintf(stderr, "submit: %d\n", ret); return T_EXIT_FAIL; } ret = io_uring_peek_cqe(&ring, &cqe); if (ret) { fprintf(stderr, "peek: %d\n", ret); return T_EXIT_FAIL; } switch (cqe->user_data) { case IS_ACCEPT: send_fd = cqe->res; io_uring_cqe_seen(&ring, cqe); /* * prep two sends, with the 2nd linked to a close * operation. Once the close has been completed, that * will terminate the receiving thread and that will * in turn send this task a SIGUSR1 signal. If the * kernel is buggy, then we never get SIGUSR1 and we * will sit forever waiting and be timed out. */ sqe = io_uring_get_sqe(&ring); io_uring_prep_send(sqe, send_fd, msg1, strlen(msg1), 0); sqe->user_data = IS_SEND; sqe->flags = IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_LINK; sqe = io_uring_get_sqe(&ring); io_uring_prep_send(sqe, send_fd, msg2, strlen(msg2), 0); sqe->user_data = IS_SEND2; sqe->flags = IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_LINK; sqe = io_uring_get_sqe(&ring); io_uring_prep_send(sqe, send_fd, msg3, strlen(msg3), 0); sqe->user_data = IS_SEND3; sqe->flags = IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_LINK; sqe = io_uring_get_sqe(&ring); io_uring_prep_close(sqe, send_fd); sqe->user_data = IS_CLOSE; sqe->flags = IOSQE_CQE_SKIP_SUCCESS; break; case IS_SEND: case IS_SEND2: fprintf(stderr, "Should not see send response\n"); io_uring_cqe_seen(&ring, cqe); return T_EXIT_FAIL; case IS_CLOSE: fprintf(stderr, "Should not see close response\n"); io_uring_cqe_seen(&ring, cqe); return T_EXIT_FAIL; default: fprintf(stderr, "got unknown cqe\n"); return T_EXIT_FAIL; } } while (1); /* will never get here */ return T_EXIT_FAIL; }