| /* SPDX-License-Identifier: MIT */ |
| /* |
| * Test reads that will punt to blocking context, with immediate overwrite |
| * of iovec->iov_base to NULL. If the kernel doesn't properly handle |
| * reuse of the iovec, we should get -EFAULT. |
| */ |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <pthread.h> |
| #include <sys/time.h> |
| |
| #include "helpers.h" |
| #include "liburing.h" |
| |
| #define STR_SIZE 32768 |
| #define FILE_SIZE 65536 |
| |
| struct thread_data { |
| int fd1, fd2; |
| volatile int do_exit; |
| }; |
| |
| static void *flusher(void *__data) |
| { |
| struct thread_data *data = __data; |
| int i = 0; |
| |
| while (!data->do_exit) { |
| posix_fadvise(data->fd1, 0, FILE_SIZE, POSIX_FADV_DONTNEED); |
| posix_fadvise(data->fd2, 0, FILE_SIZE, POSIX_FADV_DONTNEED); |
| usleep(10); |
| i++; |
| } |
| |
| return NULL; |
| } |
| |
| static char str1[STR_SIZE]; |
| static char str2[STR_SIZE]; |
| |
| static struct io_uring ring; |
| |
| static int no_stable; |
| |
| static int prep(int fd, char *str, int split, int async) |
| { |
| struct io_uring_sqe *sqe; |
| struct iovec iovs[16]; |
| int ret, i; |
| |
| if (split) { |
| int vsize = STR_SIZE / 16; |
| void *ptr = str; |
| |
| for (i = 0; i < 16; i++) { |
| iovs[i].iov_base = ptr; |
| iovs[i].iov_len = vsize; |
| ptr += vsize; |
| } |
| } else { |
| iovs[0].iov_base = str; |
| iovs[0].iov_len = STR_SIZE; |
| } |
| |
| sqe = io_uring_get_sqe(&ring); |
| io_uring_prep_readv(sqe, fd, iovs, split ? 16 : 1, 0); |
| sqe->user_data = fd; |
| if (async) |
| sqe->flags = IOSQE_ASYNC; |
| ret = io_uring_submit(&ring); |
| if (ret != 1) { |
| fprintf(stderr, "submit got %d\n", ret); |
| return 1; |
| } |
| if (split) { |
| for (i = 0; i < 16; i++) |
| iovs[i].iov_base = NULL; |
| } else { |
| iovs[0].iov_base = NULL; |
| } |
| return 0; |
| } |
| |
| static int wait_nr(int nr) |
| { |
| int i, ret; |
| |
| for (i = 0; i < nr; i++) { |
| struct io_uring_cqe *cqe; |
| |
| ret = io_uring_wait_cqe(&ring, &cqe); |
| if (ret) |
| return ret; |
| if (cqe->res < 0) { |
| fprintf(stderr, "cqe->res=%d\n", cqe->res); |
| return 1; |
| } |
| io_uring_cqe_seen(&ring, cqe); |
| } |
| |
| return 0; |
| } |
| |
| static unsigned long long mtime_since(const struct timeval *s, |
| const struct timeval *e) |
| { |
| long long sec, usec; |
| |
| sec = e->tv_sec - s->tv_sec; |
| usec = (e->tv_usec - s->tv_usec); |
| if (sec > 0 && usec < 0) { |
| sec--; |
| usec += 1000000; |
| } |
| |
| sec *= 1000; |
| usec /= 1000; |
| return sec + usec; |
| } |
| |
| static unsigned long long mtime_since_now(struct timeval *tv) |
| { |
| struct timeval end; |
| |
| gettimeofday(&end, NULL); |
| return mtime_since(tv, &end); |
| } |
| |
| static int test_reuse(int argc, char *argv[], int split, int async) |
| { |
| struct thread_data data; |
| struct io_uring_params p = { }; |
| int fd1, fd2, ret, i; |
| struct timeval tv; |
| pthread_t thread; |
| char *fname1 = ".reuse.1"; |
| int do_unlink = 1; |
| void *tret; |
| |
| if (argc > 1) { |
| fname1 = argv[1]; |
| do_unlink = 0; |
| } |
| |
| ret = io_uring_queue_init_params(32, &ring, &p); |
| if (ret) { |
| fprintf(stderr, "io_uring_queue_init: %d\n", ret); |
| return 1; |
| } |
| |
| if (!(p.features & IORING_FEAT_SUBMIT_STABLE)) { |
| fprintf(stdout, "FEAT_SUBMIT_STABLE not there, skipping\n"); |
| no_stable = 1; |
| return 0; |
| } |
| |
| if (do_unlink) |
| t_create_file(fname1, FILE_SIZE); |
| |
| t_create_file(".reuse.2", FILE_SIZE); |
| |
| fd1 = open(fname1, O_RDONLY); |
| if (fd1 < 0) { |
| perror("open fname1"); |
| goto err; |
| } |
| fd2 = open(".reuse.2", O_RDONLY); |
| if (fd2 < 0) { |
| perror("open .reuse.2"); |
| goto err; |
| } |
| |
| data.fd1 = fd1; |
| data.fd2 = fd2; |
| data.do_exit = 0; |
| pthread_create(&thread, NULL, flusher, &data); |
| usleep(10000); |
| |
| gettimeofday(&tv, NULL); |
| for (i = 0; i < 1000; i++) { |
| ret = prep(fd1, str1, split, async); |
| if (ret) { |
| fprintf(stderr, "prep1 failed: %d\n", ret); |
| goto err; |
| } |
| ret = prep(fd2, str2, split, async); |
| if (ret) { |
| fprintf(stderr, "prep1 failed: %d\n", ret); |
| goto err; |
| } |
| ret = wait_nr(2); |
| if (ret) { |
| fprintf(stderr, "wait_nr: %d\n", ret); |
| goto err; |
| } |
| if (mtime_since_now(&tv) > 5000) |
| break; |
| } |
| |
| data.do_exit = 1; |
| pthread_join(thread, &tret); |
| |
| close(fd2); |
| close(fd1); |
| io_uring_queue_exit(&ring); |
| if (do_unlink) |
| unlink(fname1); |
| unlink(".reuse.2"); |
| return 0; |
| err: |
| io_uring_queue_exit(&ring); |
| if (do_unlink) |
| unlink(fname1); |
| unlink(".reuse.2"); |
| return 1; |
| |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ret, i; |
| |
| for (i = 0; i < 4; i++) { |
| int split, async; |
| |
| split = (i & 1) != 0; |
| async = (i & 2) != 0; |
| |
| ret = test_reuse(argc, argv, split, async); |
| if (ret) { |
| fprintf(stderr, "test_reuse %d %d failed\n", split, async); |
| return ret; |
| } |
| if (no_stable) |
| break; |
| } |
| |
| return 0; |
| } |