aboutsummaryrefslogtreecommitdiff
path: root/test/file-verify.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/file-verify.c')
-rw-r--r--test/file-verify.c629
1 files changed, 629 insertions, 0 deletions
diff --git a/test/file-verify.c b/test/file-verify.c
new file mode 100644
index 0000000..327cb1d
--- /dev/null
+++ b/test/file-verify.c
@@ -0,0 +1,629 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: run various reads tests, verifying data
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <linux/fs.h>
+
+#include "helpers.h"
+#include "liburing.h"
+
+#define FSIZE 128*1024*1024
+#define CHUNK_SIZE 131072
+#define PUNCH_SIZE 32768
+
+/*
+ * 8 because it fits within the on-stack iov, 16 because it's larger than 8
+ */
+#define MIN_VECS 8
+#define MAX_VECS 16
+
+/*
+ * Can be anything, let's just do something for a bit of parallellism
+ */
+#define READ_BATCH 16
+
+/*
+ * Each offset in the file has the offset / sizeof(int) stored for every
+ * sizeof(int) address.
+ */
+static int verify_buf(void *buf, size_t size, off_t off)
+{
+ int i, u_in_buf = size / sizeof(unsigned int);
+ unsigned int *ptr;
+
+ off /= sizeof(unsigned int);
+ ptr = buf;
+ for (i = 0; i < u_in_buf; i++) {
+ if (off != *ptr) {
+ fprintf(stderr, "Found %u, wanted %lu\n", *ptr, off);
+ return 1;
+ }
+ ptr++;
+ off++;
+ }
+
+ return 0;
+}
+
+static int test_truncate(struct io_uring *ring, const char *fname, int buffered,
+ int vectored, int provide_buf)
+{
+ struct io_uring_cqe *cqe;
+ struct io_uring_sqe *sqe;
+ struct iovec vec;
+ struct stat sb;
+ off_t punch_off, off, file_size;
+ void *buf = NULL;
+ int u_in_buf, i, ret, fd, first_pass = 1;
+ unsigned int *ptr;
+
+ if (buffered)
+ fd = open(fname, O_RDWR);
+ else
+ fd = open(fname, O_DIRECT | O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ return 1;
+ }
+
+ if (fstat(fd, &sb) < 0) {
+ perror("stat");
+ close(fd);
+ return 1;
+ }
+
+ if (S_ISREG(sb.st_mode)) {
+ file_size = sb.st_size;
+ } else if (S_ISBLK(sb.st_mode)) {
+ unsigned long long bytes;
+
+ if (ioctl(fd, BLKGETSIZE64, &bytes) < 0) {
+ perror("ioctl");
+ close(fd);
+ return 1;
+ }
+ file_size = bytes;
+ } else {
+ goto out;
+ }
+
+ if (file_size < CHUNK_SIZE)
+ goto out;
+
+ t_posix_memalign(&buf, 4096, CHUNK_SIZE);
+
+ off = file_size - (CHUNK_SIZE / 2);
+ punch_off = off + CHUNK_SIZE / 4;
+
+ u_in_buf = CHUNK_SIZE / sizeof(unsigned int);
+ ptr = buf;
+ for (i = 0; i < u_in_buf; i++) {
+ *ptr = i;
+ ptr++;
+ }
+ ret = pwrite(fd, buf, CHUNK_SIZE / 2, off);
+ if (ret < 0) {
+ perror("pwrite");
+ goto err;
+ } else if (ret != CHUNK_SIZE / 2)
+ goto out;
+
+again:
+ /*
+ * Read in last bit of file so it's known cached, then remove half of that
+ * last bit so we get a short read that needs retry
+ */
+ ret = pread(fd, buf, CHUNK_SIZE / 2, off);
+ if (ret < 0) {
+ perror("pread");
+ goto err;
+ } else if (ret != CHUNK_SIZE / 2)
+ goto out;
+
+ if (posix_fadvise(fd, punch_off, CHUNK_SIZE / 4, POSIX_FADV_DONTNEED) < 0) {
+ perror("posix_fadivse");
+ goto err;
+ }
+
+ if (provide_buf) {
+ sqe = io_uring_get_sqe(ring);
+ io_uring_prep_provide_buffers(sqe, buf, CHUNK_SIZE, 1, 0, 0);
+ ret = io_uring_submit(ring);
+ if (ret != 1) {
+ fprintf(stderr, "submit failed %d\n", ret);
+ goto err;
+ }
+ ret = io_uring_wait_cqe(ring, &cqe);
+ if (ret < 0) {
+ fprintf(stderr, "wait completion %d\n", ret);
+ goto err;
+ }
+ ret = cqe->res;
+ io_uring_cqe_seen(ring, cqe);
+ if (ret) {
+ fprintf(stderr, "Provide buffer failed %d\n", ret);
+ goto err;
+ }
+ }
+
+ sqe = io_uring_get_sqe(ring);
+ if (!sqe) {
+ fprintf(stderr, "get sqe failed\n");
+ goto err;
+ }
+
+ if (vectored) {
+ assert(!provide_buf);
+ vec.iov_base = buf;
+ vec.iov_len = CHUNK_SIZE;
+ io_uring_prep_readv(sqe, fd, &vec, 1, off);
+ } else {
+ if (provide_buf) {
+ io_uring_prep_read(sqe, fd, NULL, CHUNK_SIZE, off);
+ sqe->flags |= IOSQE_BUFFER_SELECT;
+ } else {
+ io_uring_prep_read(sqe, fd, buf, CHUNK_SIZE, off);
+ }
+ }
+ memset(buf, 0, CHUNK_SIZE);
+
+ ret = io_uring_submit(ring);
+ if (ret != 1) {
+ fprintf(stderr, "Submit failed %d\n", ret);
+ goto err;
+ }
+
+ ret = io_uring_wait_cqe(ring, &cqe);
+ if (ret < 0) {
+ fprintf(stderr, "wait completion %d\n", ret);
+ goto err;
+ }
+
+ ret = cqe->res;
+ io_uring_cqe_seen(ring, cqe);
+ if (ret != CHUNK_SIZE / 2) {
+ fprintf(stderr, "Unexpected truncated read %d\n", ret);
+ goto err;
+ }
+
+ if (verify_buf(buf, CHUNK_SIZE / 2, 0))
+ goto err;
+
+ /*
+ * Repeat, but punch first part instead of last
+ */
+ if (first_pass) {
+ punch_off = file_size - CHUNK_SIZE / 4;
+ first_pass = 0;
+ goto again;
+ }
+
+out:
+ free(buf);
+ close(fd);
+ return 0;
+err:
+ free(buf);
+ close(fd);
+ return 1;
+}
+
+enum {
+ PUNCH_NONE,
+ PUNCH_FRONT,
+ PUNCH_MIDDLE,
+ PUNCH_END,
+};
+
+/*
+ * For each chunk in file, DONTNEED a start, end, or middle segment of it.
+ * We enter here with the file fully cached every time, either freshly
+ * written or after other reads. This forces (at least) the buffered reads
+ * to be handled incrementally, exercising that path.
+ */
+static int do_punch(int fd)
+{
+ off_t offset = 0;
+ int punch_type;
+
+ while (offset + CHUNK_SIZE <= FSIZE) {
+ off_t punch_off;
+
+ punch_type = rand() % (PUNCH_END + 1);
+ switch (punch_type) {
+ default:
+ case PUNCH_NONE:
+ punch_off = -1; /* gcc... */
+ break;
+ case PUNCH_FRONT:
+ punch_off = offset;
+ break;
+ case PUNCH_MIDDLE:
+ punch_off = offset + PUNCH_SIZE;
+ break;
+ case PUNCH_END:
+ punch_off = offset + CHUNK_SIZE - PUNCH_SIZE;
+ break;
+ }
+
+ offset += CHUNK_SIZE;
+ if (punch_type == PUNCH_NONE)
+ continue;
+ if (posix_fadvise(fd, punch_off, PUNCH_SIZE, POSIX_FADV_DONTNEED) < 0) {
+ perror("posix_fadivse");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int provide_buffers(struct io_uring *ring, void **buf)
+{
+ struct io_uring_cqe *cqe;
+ struct io_uring_sqe *sqe;
+ int i, ret;
+
+ /* real use case would have one buffer chopped up, but... */
+ for (i = 0; i < READ_BATCH; i++) {
+ sqe = io_uring_get_sqe(ring);
+ io_uring_prep_provide_buffers(sqe, buf[i], CHUNK_SIZE, 1, 0, i);
+ }
+
+ ret = io_uring_submit(ring);
+ if (ret != READ_BATCH) {
+ fprintf(stderr, "Submit failed %d\n", ret);
+ return 1;
+ }
+
+ for (i = 0; i < READ_BATCH; i++) {
+ ret = io_uring_wait_cqe(ring, &cqe);
+ if (ret) {
+ fprintf(stderr, "wait cqe %d\n", ret);
+ return 1;
+ }
+ if (cqe->res < 0) {
+ fprintf(stderr, "cqe res provide %d\n", cqe->res);
+ return 1;
+ }
+ io_uring_cqe_seen(ring, cqe);
+ }
+
+ return 0;
+}
+
+static int test(struct io_uring *ring, const char *fname, int buffered,
+ int vectored, int small_vecs, int registered, int provide)
+{
+ struct iovec vecs[READ_BATCH][MAX_VECS];
+ struct io_uring_cqe *cqe;
+ struct io_uring_sqe *sqe;
+ void *buf[READ_BATCH];
+ int ret, fd, flags;
+ int i, j, nr_vecs;
+ off_t off, voff;
+ size_t left;
+
+ if (registered) {
+ assert(!provide);
+ assert(!vectored && !small_vecs);
+ }
+ if (provide) {
+ assert(!registered);
+ assert(!vectored && !small_vecs);
+ }
+
+ flags = O_RDONLY;
+ if (!buffered)
+ flags |= O_DIRECT;
+ fd = open(fname, flags);
+ if (fd < 0) {
+ perror("open");
+ return 1;
+ }
+
+ if (do_punch(fd))
+ return 1;
+
+ if (vectored) {
+ if (small_vecs)
+ nr_vecs = MIN_VECS;
+ else
+ nr_vecs = MAX_VECS;
+
+ for (j = 0; j < READ_BATCH; j++) {
+ for (i = 0; i < nr_vecs; i++) {
+ void *ptr;
+
+ t_posix_memalign(&ptr, 4096, CHUNK_SIZE / nr_vecs);
+ vecs[j][i].iov_base = ptr;
+ vecs[j][i].iov_len = CHUNK_SIZE / nr_vecs;
+ }
+ }
+ } else {
+ for (j = 0; j < READ_BATCH; j++)
+ t_posix_memalign(&buf[j], 4096, CHUNK_SIZE);
+ nr_vecs = 0;
+ }
+
+ if (registered) {
+ struct iovec v[READ_BATCH];
+
+ for (i = 0; i < READ_BATCH; i++) {
+ v[i].iov_base = buf[i];
+ v[i].iov_len = CHUNK_SIZE;
+ }
+ ret = io_uring_register_buffers(ring, v, READ_BATCH);
+ if (ret) {
+ fprintf(stderr, "Error buffer reg %d\n", ret);
+ goto err;
+ }
+ }
+
+ i = 0;
+ left = FSIZE;
+ off = 0;
+ while (left) {
+ int pending = 0;
+
+ if (provide && provide_buffers(ring, buf))
+ goto err;
+
+ for (i = 0; i < READ_BATCH; i++) {
+ size_t this = left;
+
+ if (this > CHUNK_SIZE)
+ this = CHUNK_SIZE;
+
+ sqe = io_uring_get_sqe(ring);
+ if (!sqe) {
+ fprintf(stderr, "get sqe failed\n");
+ goto err;
+ }
+
+ if (vectored) {
+ io_uring_prep_readv(sqe, fd, vecs[i], nr_vecs, off);
+ } else {
+ if (registered) {
+ io_uring_prep_read_fixed(sqe, fd, buf[i], this, off, i);
+ } else if (provide) {
+ io_uring_prep_read(sqe, fd, NULL, this, off);
+ sqe->flags |= IOSQE_BUFFER_SELECT;
+ } else {
+ io_uring_prep_read(sqe, fd, buf[i], this, off);
+ }
+ }
+ sqe->user_data = ((uint64_t)off << 32) | i;
+ off += this;
+ left -= this;
+ pending++;
+ if (!left)
+ break;
+ }
+
+ ret = io_uring_submit(ring);
+ if (ret != pending) {
+ fprintf(stderr, "sqe submit failed: %d\n", ret);
+ goto err;
+ }
+
+ for (i = 0; i < pending; i++) {
+ int index;
+
+ ret = io_uring_wait_cqe(ring, &cqe);
+ if (ret < 0) {
+ fprintf(stderr, "wait completion %d\n", ret);
+ goto err;
+ }
+ if (cqe->res < 0) {
+ fprintf(stderr, "bad read %d, read %d\n", cqe->res, i);
+ goto err;
+ }
+ if (cqe->flags & IORING_CQE_F_BUFFER)
+ index = cqe->flags >> 16;
+ else
+ index = cqe->user_data & 0xffffffff;
+ voff = cqe->user_data >> 32;
+ io_uring_cqe_seen(ring, cqe);
+ if (vectored) {
+ for (j = 0; j < nr_vecs; j++) {
+ void *buf = vecs[index][j].iov_base;
+ size_t len = vecs[index][j].iov_len;
+
+ if (verify_buf(buf, len, voff))
+ goto err;
+ voff += len;
+ }
+ } else {
+ if (verify_buf(buf[index], CHUNK_SIZE, voff))
+ goto err;
+ }
+ }
+ }
+
+ ret = 0;
+done:
+ if (registered)
+ io_uring_unregister_buffers(ring);
+ if (vectored) {
+ for (j = 0; j < READ_BATCH; j++)
+ for (i = 0; i < nr_vecs; i++)
+ free(vecs[j][i].iov_base);
+ } else {
+ for (j = 0; j < READ_BATCH; j++)
+ free(buf[j]);
+ }
+ close(fd);
+ return ret;
+err:
+ ret = 1;
+ goto done;
+}
+
+static int fill_pattern(const char *fname)
+{
+ size_t left = FSIZE;
+ unsigned int val, *ptr;
+ void *buf;
+ int fd, i;
+
+ fd = open(fname, O_WRONLY);
+ if (fd < 0) {
+ perror("open");
+ return 1;
+ }
+
+ val = 0;
+ buf = t_malloc(4096);
+ while (left) {
+ int u_in_buf = 4096 / sizeof(val);
+ size_t this = left;
+
+ if (this > 4096)
+ this = 4096;
+ ptr = buf;
+ for (i = 0; i < u_in_buf; i++) {
+ *ptr = val;
+ val++;
+ ptr++;
+ }
+ if (write(fd, buf, 4096) != 4096)
+ return 1;
+ left -= 4096;
+ }
+
+ fsync(fd);
+ close(fd);
+ free(buf);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct io_uring ring;
+ const char *fname;
+ char buf[32];
+ int ret;
+
+ srand(getpid());
+
+ if (argc > 1) {
+ fname = argv[1];
+ } else {
+ sprintf(buf, ".file-verify.%d", getpid());
+ fname = buf;
+ t_create_file(fname, FSIZE);
+ }
+
+ ret = io_uring_queue_init(READ_BATCH, &ring, 0);
+ if (ret) {
+ fprintf(stderr, "ring setup failed: %d\n", ret);
+ goto err;
+ }
+
+ if (fill_pattern(fname))
+ goto err;
+
+ ret = test(&ring, fname, 1, 0, 0, 0, 0);
+ if (ret) {
+ fprintf(stderr, "Buffered novec test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 1, 0, 0, 1, 0);
+ if (ret) {
+ fprintf(stderr, "Buffered novec reg test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 1, 0, 0, 0, 1);
+ if (ret) {
+ fprintf(stderr, "Buffered novec provide test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 1, 1, 0, 0, 0);
+ if (ret) {
+ fprintf(stderr, "Buffered vec test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 1, 1, 1, 0, 0);
+ if (ret) {
+ fprintf(stderr, "Buffered small vec test failed\n");
+ goto err;
+ }
+
+ ret = test(&ring, fname, 0, 0, 0, 0, 0);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT novec test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 0, 0, 0, 1, 0);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT novec reg test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 0, 0, 0, 0, 1);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT novec provide test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 0, 1, 0, 0, 0);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT vec test failed\n");
+ goto err;
+ }
+ ret = test(&ring, fname, 0, 1, 1, 0, 0);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT small vec test failed\n");
+ goto err;
+ }
+
+ ret = test_truncate(&ring, fname, 1, 0, 0);
+ if (ret) {
+ fprintf(stderr, "Buffered end truncate read failed\n");
+ goto err;
+ }
+ ret = test_truncate(&ring, fname, 1, 1, 0);
+ if (ret) {
+ fprintf(stderr, "Buffered end truncate vec read failed\n");
+ goto err;
+ }
+ ret = test_truncate(&ring, fname, 1, 0, 1);
+ if (ret) {
+ fprintf(stderr, "Buffered end truncate pbuf read failed\n");
+ goto err;
+ }
+
+ ret = test_truncate(&ring, fname, 0, 0, 0);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT end truncate read failed\n");
+ goto err;
+ }
+ ret = test_truncate(&ring, fname, 0, 1, 0);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT end truncate vec read failed\n");
+ goto err;
+ }
+ ret = test_truncate(&ring, fname, 0, 0, 1);
+ if (ret) {
+ fprintf(stderr, "O_DIRECT end truncate pbuf read failed\n");
+ goto err;
+ }
+
+ if (buf == fname)
+ unlink(fname);
+ return 0;
+err:
+ if (buf == fname)
+ unlink(fname);
+ return 1;
+}