Add test case for SQPOLL -EBUSY bug

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/test/Makefile b/test/Makefile
index 3b5e19f..61825d1 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -21,7 +21,7 @@
 		file-update accept-reuse poll-v-poll fadvise madvise \
 		short-read openat2 probe shared-wq personality eventfd \
 		send_recv eventfd-ring across-fork sq-poll-kthread splice \
-		lfs-openat lfs-openat-write iopoll
+		lfs-openat lfs-openat-write iopoll d4ae271dfaae-test
 
 include ../Makefile.quiet
 
@@ -53,7 +53,7 @@
 	file-update.c accept-reuse.c poll-v-poll.c fadvise.c \
 	madvise.c short-read.c openat2.c probe.c shared-wq.c \
 	personality.c eventfd.c eventfd-ring.c across-fork.c sq-poll-kthread.c \
-	splice.c lfs-openat.c lfs-openat-write.c iopoll.c
+	splice.c lfs-openat.c lfs-openat-write.c iopoll.c d4ae271dfaae-test.c
 
 ifdef CONFIG_HAVE_STATX
 test_srcs += statx.c
diff --git a/test/d4ae271dfaae-test.c b/test/d4ae271dfaae-test.c
new file mode 100644
index 0000000..6f263c6
--- /dev/null
+++ b/test/d4ae271dfaae-test.c
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Test case for SQPOLL missing a 'ret' clear in case of busy.
+ *
+ * Heavily based on a test case from
+ * Xiaoguang Wang <xiaoguang.wang@linux.alibaba.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "liburing.h"
+
+#define FILE_SIZE	(128 * 1024)
+
+static int create_file(const char *file)
+{
+	ssize_t ret;
+	char *buf;
+	int fd;
+
+	buf = malloc(FILE_SIZE);
+	memset(buf, 0xaa, FILE_SIZE);
+
+	fd = open(file, O_WRONLY | O_CREAT, 0644);
+	if (fd < 0) {
+		perror("open file");
+		return 1;
+	}
+	ret = write(fd, buf, FILE_SIZE);
+	fsync(fd);
+	close(fd);
+	return ret != FILE_SIZE;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring;
+	int i, fd, ret;
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct iovec *iovecs;
+	struct io_uring_params p;
+	char *fname;
+	void *buf;
+
+	if (geteuid()) {
+		fprintf(stdout, "Test requires root, skipping\n");
+		return 0;
+	}
+
+	memset(&p, 0, sizeof(p));
+	p.flags = IORING_SETUP_SQPOLL;
+	ret = io_uring_queue_init_params(4, &ring, &p);
+	if (ret < 0) {
+		fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+		return 1;
+	}
+
+	if (argc > 1) {
+		fname = argv[1];
+	} else {
+		fname = ".sqpoll.tmp";
+		if (create_file(fname)) {
+			fprintf(stderr, "file creation failed\n");
+			goto out;
+		}
+	}
+
+	fd = open(fname, O_RDONLY | O_DIRECT);
+	if (fd < 0) {
+		perror("open");
+		goto out;
+	}
+
+	iovecs = calloc(10, sizeof(struct iovec));
+	for (i = 0; i < 10; i++) {
+		if (posix_memalign(&buf, 4096, 4096))
+			goto out;
+		iovecs[i].iov_base = buf;
+		iovecs[i].iov_len = 4096;
+	}
+
+	ret = io_uring_register_files(&ring, &fd, 1);
+	if (ret < 0) {
+		fprintf(stderr, "register files %d\n", ret);
+		goto out;
+	}
+
+	for (i = 0; i < 10; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		if (!sqe)
+			break;
+
+		io_uring_prep_readv(sqe, 0, &iovecs[i], 1, 0);
+		sqe->flags |= IOSQE_FIXED_FILE;
+
+		ret = io_uring_submit(&ring);
+		usleep(1000);
+	}
+
+	for (i = 0; i < 10; i++) {
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe=%d\n", ret);
+			break;
+		}
+		if (cqe->res != 4096) {
+			fprintf(stderr, "ret=%d, wanted 4096\n", cqe->res);
+			ret = 1;
+			break;
+		}
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	close(fd);
+out:
+	if (fname != argv[1])
+		unlink(fname);
+	io_uring_queue_exit(&ring);
+	return ret;
+}