Add test/file-register

Tests both existing functionality, but also the new sparse maps,
update/remove/add functionality.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h
index ea57526..4f532d9 100644
--- a/src/include/liburing/io_uring.h
+++ b/src/include/liburing/io_uring.h
@@ -150,5 +150,11 @@
 #define IORING_UNREGISTER_FILES		3
 #define IORING_REGISTER_EVENTFD		4
 #define IORING_UNREGISTER_EVENTFD	5
+#define IORING_REGISTER_FILES_UPDATE	6
+
+struct io_uring_files_update {
+	__u32 offset;
+	__s32 *fds;
+};
 
 #endif
diff --git a/test/Makefile b/test/Makefile
index 4710b30..3987f89 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -6,7 +6,7 @@
 	       io_uring_enter nop sq-full cq-full 35fa71a030ca-test \
 		917257daa0fe-test b19062a56726-test eeed8b54e0df-test link \
 		send_recvmsg a4c0b3decb33-test 500f9fbadef8-test timeout \
-		sq-space_left stdout cq-ready cq-peek-batch
+		sq-space_left stdout cq-ready cq-peek-batch file-register
 
 include ../Makefile.quiet
 
@@ -20,7 +20,7 @@
 	35fa71a030ca-test.c 917257daa0fe-test.c b19062a56726-test.c \
 	eeed8b54e0df-test.c link.c send_recvmsg.c a4c0b3decb33-test.c \
 	500f9fbadef8-test.c timeout.c sq-space_left.c stdout.c cq-ready.c\
-	cq-peek-batch.c
+	cq-peek-batch.c file-register.c
 
 test_objs := $(patsubst %.c,%.ol,$(test_srcs))
 
diff --git a/test/file-register.c b/test/file-register.c
new file mode 100644
index 0000000..f50e32e
--- /dev/null
+++ b/test/file-register.c
@@ -0,0 +1,332 @@
+/*
+ * Description: run various file registration tests
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+
+static int no_update = 0;
+
+static void close_files(int *files, int nr_files, int add)
+{
+	char fname[32];
+	int i;
+
+	for (i = 0; i < nr_files; i++) {
+		close(files[i]);
+		if (!add)
+			sprintf(fname, ".reg.%d", i);
+		else
+			sprintf(fname, ".add.%d", i);
+		unlink(fname);
+	}
+	free(files);
+}
+
+static int *open_files(int nr_files, int extra, int add)
+{
+	char fname[32];
+	int *files;
+	int i;
+
+	files = calloc(nr_files + extra, sizeof(int));
+
+	for (i = 0; i < nr_files; i++) {
+		if (!add)
+			sprintf(fname, ".reg.%d", i);
+		else
+			sprintf(fname, ".add.%d", i);
+		files[i] = open(fname, O_RDWR | O_CREAT, 0644);
+		if (files[i] < 0) {
+			perror("open");
+			free(files);
+			files = NULL;
+			break;
+		}
+	}
+	if (extra) {
+		for (i = nr_files; i < nr_files + extra; i++)
+			files[i] = -1;
+	}
+
+	return files;
+}
+
+static int test_replace_all(struct io_uring *ring)
+{
+	struct io_uring_files_update up;
+	int *files;
+	int ret, i;
+
+	files = open_files(100, 0, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 100);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	up.fds = malloc(100 * sizeof(int));
+	for (i = 0; i < 100; i++)
+		up.fds[i] = -1;
+	up.offset = 0;
+
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES_UPDATE, &up, 100);
+	if (ret != 100) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	close_files(files, 100, 0);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	return 1;
+}
+
+static int test_replace(struct io_uring *ring)
+{
+	struct io_uring_files_update up;
+	int *files;
+	int ret;
+
+	files = open_files(100, 0, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 100);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	up.fds = open_files(10, 0, 1);
+	up.offset = 90;
+
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES_UPDATE, &up, 10);
+	if (ret != 10) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	close_files(files, 100, 0);
+	close_files(up.fds, 10, 1);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	close_files(up.fds, 10, 1);
+	return 1;
+}
+
+static int test_removals(struct io_uring *ring)
+{
+	struct io_uring_files_update up;
+	int *files;
+	int ret, i;
+
+	files = open_files(100, 0, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 100);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	up.fds = calloc(10, sizeof(int));
+	for (i = 0; i < 10; i++)
+		up.fds[i] = -1;
+	up.offset = 50;
+
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES_UPDATE, &up, 10);
+	if (ret != 10) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	close_files(files, 100, 0);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	return 1;
+}
+
+static int test_additions(struct io_uring *ring)
+{
+	struct io_uring_files_update up;
+	int *files;
+	int ret;
+
+	files = open_files(100, 100, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 200);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	up.fds = open_files(2, 0, 1);
+	up.offset = 100;
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES_UPDATE, &up, 2);
+	if (ret != 2) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+
+	close_files(files, 100, 0);
+	close_files(up.fds, 2, 1);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	close_files(up.fds, 2, 1);
+	return 1;
+}
+
+static int test_sparse(struct io_uring *ring)
+{
+	int *files;
+	int ret;
+
+	files = open_files(100, 100, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 200);
+	if (ret) {
+		if (errno == EBADF) {
+			printf("Sparse files not supported\n");
+			no_update = 1;
+			goto done;
+		}
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret) {
+		printf("ret=%d, errno=%d\n", ret, errno);
+		goto err;
+	}
+done:
+	close_files(files, 100, 0);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	return 1;
+}
+
+static int test_basic_many(struct io_uring *ring)
+{
+	int *files;
+	int ret;
+
+	files = open_files(768, 0, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 768);
+	if (ret)
+		goto err;
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret)
+		goto err;
+	close_files(files, 768, 0);
+	return 0;
+err:
+	close_files(files, 768, 0);
+	return 1;
+}
+
+static int test_basic(struct io_uring *ring)
+{
+	int *files;
+	int ret;
+
+	files = open_files(100, 0, 0);
+	ret = io_uring_register(ring->ring_fd, IORING_REGISTER_FILES, files, 100);
+	if (ret)
+		goto err;
+	ret = io_uring_register(ring->ring_fd, IORING_UNREGISTER_FILES, NULL, 0);
+	if (ret)
+		goto err;
+	close_files(files, 100, 0);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	return 1;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring;
+	int ret;
+
+	ret = io_uring_queue_init(8, &ring, 0);
+	if (ret) {
+		printf("ring setup failed\n");
+		return 1;
+	}
+
+	ret = test_basic(&ring);
+	if (ret) {
+		printf("test_basic failed\n");
+		return ret;
+	}
+
+	ret = test_basic_many(&ring);
+	if (ret) {
+		printf("test_basic_many failed\n");
+		return ret;
+	}
+
+	ret = test_sparse(&ring);
+	if (ret) {
+		printf("test_sparse failed\n");
+		return ret;
+	}
+
+	if (no_update)
+		return 0;
+
+	ret = test_additions(&ring);
+	if (ret) {
+		printf("test_additions failed\n");
+		return ret;
+	}
+
+	ret = test_removals(&ring);
+	if (ret) {
+		printf("test_removals failed\n");
+		return ret;
+	}
+
+	ret = test_replace(&ring);
+	if (ret) {
+		printf("test_replace failed\n");
+		return ret;
+	}
+
+	ret = test_replace_all(&ring);
+	if (ret) {
+		printf("test_replace_all failed\n");
+		return ret;
+	}
+
+	return 0;
+}