summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Yudaken <dylany@fb.com>2022-02-22 02:57:12 -0800
committerJens Axboe <axboe@kernel.dk>2022-02-23 16:39:19 -0700
commit57352019dd1faa77c2c3b6bf689217207d649560 (patch)
tree52fdad27c169f58fb97839dc31570878cd2dcf86
parent8be8af4afcb4909104caea5d180dff3bed45a19e (diff)
downloadliburing-57352019dd1faa77c2c3b6bf689217207d649560.tar.gz
liburing-57352019dd1faa77c2c3b6bf689217207d649560.tar.bz2
Test consistent file position updates
read(2)/write(2) and friends support sequential reads without giving an explicit offset. The result of these should leave the file with an incremented offset. Add tests for both read and write to check that io_uring behaves consistently in these scenarios. Expect that if you queue many reads/writes, and set the IOSQE_IO_LINK flag, that they will behave similarly to calling read(2)/write(2) in sequence. Set IOSQE_ASYNC as well in a set of tests. This exacerbates the problem by forcing work to happen in different threads to submission. Also add tests for not setting IOSQE_IO_LINK, but allow the file offset to progress past the end of the file. Signed-off-by: Dylan Yudaken <dylany@fb.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r--test/Makefile1
-rw-r--r--test/fpos.c256
2 files changed, 257 insertions, 0 deletions
diff --git a/test/Makefile b/test/Makefile
index 1e318f7..f421f53 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -78,6 +78,7 @@ test_srcs := \
file-update.c \
file-verify.c \
fixed-link.c \
+ fpos.c \
fsync.c \
hardlink.c \
io-cancel.c \
diff --git a/test/fpos.c b/test/fpos.c
new file mode 100644
index 0000000..40df613
--- /dev/null
+++ b/test/fpos.c
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: test io_uring fpos handling
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "helpers.h"
+#include "liburing.h"
+
+#define FILE_SIZE 5000
+#define QUEUE_SIZE 2048
+
+static void create_file(const char *file, size_t size)
+{
+ ssize_t ret;
+ char *buf;
+ size_t idx;
+ int fd;
+
+ buf = t_malloc(size);
+ for (idx = 0; idx < size; ++idx) {
+ /* write 0 or 1 */
+ buf[idx] = (unsigned char)(idx & 0x01);
+ }
+
+ fd = open(file, O_WRONLY | O_CREAT, 0644);
+ assert(fd >= 0);
+
+ ret = write(fd, buf, size);
+ fsync(fd);
+ close(fd);
+ free(buf);
+ assert(ret == size);
+}
+
+static int test_read(struct io_uring *ring, bool async, bool link,
+ int blocksize)
+{
+ int ret, fd, i;
+ bool done = false;
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ loff_t current, expected = 0;
+ int count_ok;
+ int count_0 = 0, count_1 = 0;
+ unsigned char buff[QUEUE_SIZE * blocksize];
+ unsigned char reordered[QUEUE_SIZE * blocksize];
+
+ create_file(".test_fpos_read", FILE_SIZE);
+ fd = open(".test_fpos_read", O_RDONLY);
+ unlink(".test_fpos_read");
+ assert(fd >= 0);
+
+ while (!done) {
+ for (i = 0; i < QUEUE_SIZE; ++i) {
+ sqe = io_uring_get_sqe(ring);
+ if (!sqe) {
+ fprintf(stderr, "no sqe\n");
+ return -1;
+ }
+ io_uring_prep_read(sqe, fd,
+ buff + i * blocksize,
+ blocksize, -1);
+ sqe->user_data = i;
+ if (async)
+ sqe->flags |= IOSQE_ASYNC;
+ if (link && i != QUEUE_SIZE - 1)
+ sqe->flags |= IOSQE_IO_LINK;
+ }
+ ret = io_uring_submit_and_wait(ring, QUEUE_SIZE);
+ if (ret != QUEUE_SIZE) {
+ fprintf(stderr, "submit failed: %d\n", ret);
+ return 1;
+ }
+ count_ok = 0;
+ for (i = 0; i < QUEUE_SIZE; ++i) {
+ int res;
+
+ ret = io_uring_peek_cqe(ring, &cqe);
+ if (ret) {
+ fprintf(stderr, "peek failed: %d\n", ret);
+ return ret;
+ }
+ assert(cqe->user_data < QUEUE_SIZE);
+ memcpy(reordered + count_ok,
+ buff + cqe->user_data * blocksize, blocksize);
+ res = cqe->res;
+ io_uring_cqe_seen(ring, cqe);
+ if (res == 0) {
+ done = true;
+ } else if (res == -ECANCELED) {
+ /* cancelled, probably ok */
+ } else if (res < 0 || res > blocksize) {
+ fprintf(stderr, "bad read: %d\n", res);
+ return -1;
+ } else {
+ expected += res;
+ count_ok += res;
+ }
+ }
+ ret = 0;
+ for (i = 0; i < count_ok; i++) {
+ if (reordered[i] == 1) {
+ count_1++;
+ } else if (reordered[i] == 0) {
+ count_0++;
+ } else {
+ fprintf(stderr, "odd read %d\n",
+ (int)reordered[i]);
+ ret = -1;
+ break;
+ }
+ }
+ if (labs(count_1 - count_0) > 1) {
+ fprintf(stderr, "inconsistent reads, got 0s:%d 1s:%d\n",
+ count_0, count_1);
+ ret = -1;
+ }
+ current = lseek(fd, 0, SEEK_CUR);
+ if (current < expected || (current != expected && link)) {
+ /* accept that with !link current may be > expected */
+ fprintf(stderr, "f_pos incorrect, expected %ld have %ld\n",
+ expected, current);
+ ret = -1;
+ }
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+
+static int test_write(struct io_uring *ring, bool async,
+ bool link, int blocksize)
+{
+ int ret, fd, i;
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ bool fail = false;
+ loff_t current;
+ char data[blocksize+1];
+ char readbuff[QUEUE_SIZE*blocksize+1];
+
+ fd = open(".test_fpos_write", O_RDWR | O_CREAT, 0644);
+ unlink(".test_fpos_write");
+ assert(fd >= 0);
+
+ for (i = 0; i < blocksize; i++)
+ data[i] = 'A' + i;
+
+ data[blocksize] = '\0';
+
+ for (i = 0; i < QUEUE_SIZE; ++i) {
+ sqe = io_uring_get_sqe(ring);
+ if (!sqe) {
+ fprintf(stderr, "no sqe\n");
+ return -1;
+ }
+ io_uring_prep_write(sqe, fd, data + (i % blocksize), 1, -1);
+ sqe->user_data = 1;
+ if (async)
+ sqe->flags |= IOSQE_ASYNC;
+ if (link && i != QUEUE_SIZE - 1)
+ sqe->flags |= IOSQE_IO_LINK;
+ }
+ ret = io_uring_submit_and_wait(ring, QUEUE_SIZE);
+ if (ret != QUEUE_SIZE) {
+ fprintf(stderr, "submit failed: %d\n", ret);
+ return 1;
+ }
+ for (i = 0; i < QUEUE_SIZE; ++i) {
+ int res;
+
+ ret = io_uring_peek_cqe(ring, &cqe);
+ res = cqe->res;
+ if (ret) {
+ fprintf(stderr, "peek failed: %d\n", ret);
+ return ret;
+ }
+ io_uring_cqe_seen(ring, cqe);
+ if (!fail && res != 1) {
+ fprintf(stderr, "bad result %d\n", res);
+ fail = true;
+ }
+ }
+ current = lseek(fd, 0, SEEK_CUR);
+ if (current != QUEUE_SIZE) {
+ fprintf(stderr, "f_pos incorrect, expected %ld have %d\n",
+ current, QUEUE_SIZE);
+ fail = true;
+ }
+ current = lseek(fd, 0, SEEK_SET);
+ if (current != 0) {
+ perror("seek to start");
+ return -1;
+ }
+ ret = read(fd, readbuff, QUEUE_SIZE);
+ if (ret != QUEUE_SIZE) {
+ fprintf(stderr, "did not write enough: %d\n", ret);
+ return -1;
+ }
+ i = 0;
+ while (i < QUEUE_SIZE - blocksize) {
+ if (strncmp(readbuff + i, data, blocksize)) {
+ char bad[QUEUE_SIZE+1];
+
+ memcpy(bad, readbuff + i, blocksize);
+ bad[blocksize] = '\0';
+ fprintf(stderr, "unexpected data %s\n", bad);
+ fail = true;
+ }
+ i += blocksize;
+ }
+
+ return fail ? -1 : 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct io_uring ring;
+ int ret;
+
+ if (argc > 1)
+ return 0;
+
+ ret = io_uring_queue_init(QUEUE_SIZE, &ring, 0);
+ if (ret) {
+ fprintf(stderr, "ring setup failed\n");
+ return 1;
+ }
+
+ for (int test = 0; test < 16; test++) {
+ int async = test & 0x01;
+ int link = test & 0x02;
+ int write = test & 0x04;
+ int blocksize = test & 0x08 ? 1 : 7;
+
+ ret = write
+ ? test_write(&ring, !!async, !!link, blocksize)
+ : test_read(&ring, !!async, !!link, blocksize);
+ if (ret) {
+ fprintf(stderr, "failed %s async=%d link=%d blocksize=%d\n",
+ write ? "write" : "read",
+ async, link, blocksize);
+ return -1;
+ }
+ }
+ return 0;
+}