[PATCH 6/6] incfs: Integration tests for incremental-fs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Eugene Zemtsov <ezemtsov@xxxxxxxxxx>

Testing main use cases for Incremental FS.
Things like:
	- interaction between consuments and producers
	- basic dir operations
	- mounting a backing file with existing data

Signed-off-by: Eugene Zemtsov <ezemtsov@xxxxxxxxxx>
---
 tools/testing/selftests/Makefile              |    1 +
 .../selftests/filesystems/incfs/.gitignore    |    1 +
 .../selftests/filesystems/incfs/Makefile      |   12 +
 .../selftests/filesystems/incfs/config        |    1 +
 .../selftests/filesystems/incfs/incfs_test.c  | 1603 +++++++++++++++++
 .../selftests/filesystems/incfs/utils.c       |  159 ++
 .../selftests/filesystems/incfs/utils.h       |   39 +
 7 files changed, 1816 insertions(+)
 create mode 100644 tools/testing/selftests/filesystems/incfs/.gitignore
 create mode 100644 tools/testing/selftests/filesystems/incfs/Makefile
 create mode 100644 tools/testing/selftests/filesystems/incfs/config
 create mode 100644 tools/testing/selftests/filesystems/incfs/incfs_test.c
 create mode 100644 tools/testing/selftests/filesystems/incfs/utils.c
 create mode 100644 tools/testing/selftests/filesystems/incfs/utils.h

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 971fc8428117..78fd8590cede 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -11,6 +11,7 @@ TARGETS += efivarfs
 TARGETS += exec
 TARGETS += filesystems
 TARGETS += filesystems/binderfs
+TARGETS += filesystems/incfs
 TARGETS += firmware
 TARGETS += ftrace
 TARGETS += futex
diff --git a/tools/testing/selftests/filesystems/incfs/.gitignore b/tools/testing/selftests/filesystems/incfs/.gitignore
new file mode 100644
index 000000000000..4cba9c219a92
--- /dev/null
+++ b/tools/testing/selftests/filesystems/incfs/.gitignore
@@ -0,0 +1 @@
+incfs_test
\ No newline at end of file
diff --git a/tools/testing/selftests/filesystems/incfs/Makefile b/tools/testing/selftests/filesystems/incfs/Makefile
new file mode 100644
index 000000000000..493efc23fa33
--- /dev/null
+++ b/tools/testing/selftests/filesystems/incfs/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -Wall
+CFLAGS += -I../../../../../usr/include/
+CFLAGS += -I../../../../include/uapi/
+CFLAGS += -I../../../../lib
+
+EXTRA_SOURCES := utils.c ../../../../lib/lz4.c
+TEST_GEN_PROGS := incfs_test
+
+include ../../lib.mk
+
+$(OUTPUT)/incfs_test: incfs_test.c $(EXTRA_SOURCES)
diff --git a/tools/testing/selftests/filesystems/incfs/config b/tools/testing/selftests/filesystems/incfs/config
new file mode 100644
index 000000000000..b6749837a318
--- /dev/null
+++ b/tools/testing/selftests/filesystems/incfs/config
@@ -0,0 +1 @@
+CONFIG_INCREMENTAL_FS=y
\ No newline at end of file
diff --git a/tools/testing/selftests/filesystems/incfs/incfs_test.c b/tools/testing/selftests/filesystems/incfs/incfs_test.c
new file mode 100644
index 000000000000..6c2797970f77
--- /dev/null
+++ b/tools/testing/selftests/filesystems/incfs/incfs_test.c
@@ -0,0 +1,1603 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018 Google LLC
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <alloca.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "../../kselftest.h"
+
+#include "lz4.h"
+#include "utils.h"
+#define TEST_FAILURE 1
+#define TEST_SUCCESS 0
+
+struct test_file {
+	int ino;
+	char *name;
+	off_t size;
+};
+
+struct test_files_set {
+	struct test_file *files;
+	int files_count;
+};
+
+struct test_files_set get_test_files_set(void)
+{
+	static struct test_file files[] = {
+			{ .name = "file_one_byte", .size = 1 },
+			{ .name = "file_one_block",
+			.size = INCFS_DATA_FILE_BLOCK_SIZE },
+			{ .name = "file_one_and_a_half_blocks",
+			.size = INCFS_DATA_FILE_BLOCK_SIZE +
+				INCFS_DATA_FILE_BLOCK_SIZE / 2 },
+			{ .name = "file_three",
+			.size = 300 * INCFS_DATA_FILE_BLOCK_SIZE + 3 },
+			{ .name = "file_four",
+			.size = 400 * INCFS_DATA_FILE_BLOCK_SIZE + 7 },
+			{ .name = "file_five",
+			.size = 500 * INCFS_DATA_FILE_BLOCK_SIZE + 7 },
+			{ .name = "file_six",
+			.size = 600 * INCFS_DATA_FILE_BLOCK_SIZE + 7 },
+			{ .name = "file_seven",
+			.size = 700 * INCFS_DATA_FILE_BLOCK_SIZE + 7 },
+			{ .name = "file_eight",
+			.size = 800 * INCFS_DATA_FILE_BLOCK_SIZE + 7 },
+			{ .name = "file_nine",
+			.size = 900 * INCFS_DATA_FILE_BLOCK_SIZE + 7 },
+			{ .name = "file_big",
+			.size = 500 * 1024 * 1024 }
+		};
+	return (struct test_files_set){
+		.files = files,
+		.files_count = ARRAY_SIZE(files)
+	};
+}
+
+struct test_files_set get_small_test_files_set(void)
+{
+	static struct test_file files[] = {
+			{ .name = "file_one_byte", .size = 1 },
+			{ .name = "file_one_block",
+			.size = INCFS_DATA_FILE_BLOCK_SIZE },
+			{ .name = "file_one_and_a_half_blocks",
+			.size = INCFS_DATA_FILE_BLOCK_SIZE +
+				INCFS_DATA_FILE_BLOCK_SIZE / 2 },
+			{ .name = "file_three",
+			.size = 300 * INCFS_DATA_FILE_BLOCK_SIZE + 3 },
+			{ .name = "file_four",
+			.size = 400 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }
+		};
+	return (struct test_files_set){
+		.files = files,
+		.files_count = ARRAY_SIZE(files)
+	};
+}
+
+static int get_file_block_seed(int file, int block)
+{
+	return 7919 * file + block;
+}
+
+static loff_t min(loff_t a, loff_t b)
+{
+	return a < b ? a : b;
+}
+
+static pid_t flush_and_fork(void)
+{
+	fflush(stdout);
+	return fork();
+}
+
+static void print_error(char *msg)
+{
+	ksft_print_msg("%s: %s\n", msg, strerror(errno));
+}
+
+static int wait_for_process(pid_t pid)
+{
+	int status;
+	int wait_res;
+
+	wait_res = waitpid(pid, &status, 0);
+	if (wait_res <= 0) {
+		print_error("Can't wait for the child");
+		return -EINVAL;
+	}
+	if (!WIFEXITED(status)) {
+		ksft_print_msg("Unexpected child status pid=%d\n", pid);
+		return -EINVAL;
+	}
+	status = WEXITSTATUS(status);
+	if (status != 0)
+		return status;
+	return 0;
+}
+
+static void rnd_buf(uint8_t *data, size_t len, unsigned int seed)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		seed = 1103515245 * seed + 12345;
+		data[i] = (uint8_t)(seed >> (i % 13));
+	}
+}
+
+struct file_and_block {
+	struct test_file *file;
+	int block_index;
+};
+
+static int emit_test_blocks(int fd, struct file_and_block *blocks, int count)
+{
+	uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE];
+	uint8_t comp_data[2 * INCFS_DATA_FILE_BLOCK_SIZE];
+	int block_count = (count > 32) ? 32 : count;
+	int data_buf_size = 2 * INCFS_DATA_FILE_BLOCK_SIZE
+					* block_count;
+	uint8_t *data_buf = malloc(data_buf_size);
+	uint8_t *current_data = data_buf;
+	uint8_t *data_end = data_buf + data_buf_size;
+	struct incfs_new_data_block *block_buf =
+			calloc(block_count, sizeof(*block_buf));
+	ssize_t write_res = 0;
+	int error = 0;
+	int i = 0;
+	int blocks_written = 0;
+
+	for (i = 0; i < block_count; i++) {
+		int block_index = blocks[i].block_index;
+		struct test_file *file = blocks[i].file;
+		bool compress = (file->ino + block_index) % 2 == 0;
+		int seed = get_file_block_seed(file->ino, block_index);
+		off_t block_offset =
+			((off_t)block_index) * INCFS_DATA_FILE_BLOCK_SIZE;
+		size_t block_size = 0;
+
+		if (block_offset > file->size) {
+			error = -EINVAL;
+			break;
+		} else {
+			if (file->size - block_offset
+					> INCFS_DATA_FILE_BLOCK_SIZE)
+				block_size = INCFS_DATA_FILE_BLOCK_SIZE;
+			else
+				block_size = file->size - block_offset;
+		}
+
+		rnd_buf(data, block_size, seed);
+		if (compress) {
+			size_t comp_size = LZ4_compress_default((char *)data,
+				(char *)comp_data, block_size,
+				ARRAY_SIZE(comp_data));
+
+			if (comp_size <= 0) {
+				error = -EBADMSG;
+				break;
+			}
+			if (current_data + comp_size > data_end) {
+				error = -ENOMEM;
+				break;
+			}
+			memcpy(current_data, comp_data, comp_size);
+			block_size = comp_size;
+			block_buf[i].compression = COMPRESSION_LZ4;
+		} else {
+			if (current_data + block_size > data_end) {
+				error = -ENOMEM;
+				break;
+			}
+			memcpy(current_data, data, block_size);
+			block_buf[i].compression = COMPRESSION_NONE;
+		}
+
+		block_buf[i].file_ino = file->ino;
+		block_buf[i].block_index = block_index;
+		block_buf[i].data_len = block_size;
+		block_buf[i].data = ptr_to_u64(current_data);
+		block_buf[i].compression =
+			compress ? COMPRESSION_LZ4 : COMPRESSION_NONE;
+		current_data += block_size;
+	}
+
+	if (!error) {
+		write_res = write(fd, block_buf, sizeof(*block_buf) * i);
+		if (write_res < 0)
+			error = -errno;
+		else
+			blocks_written = write_res / sizeof(*block_buf);
+	}
+	if (error) {
+		ksft_print_msg("Writing data block error. Write returned: %d. Error:%s\n",
+				write_res, strerror(-error));
+	}
+	free(block_buf);
+	free(data_buf);
+	return (error < 0) ? error : blocks_written;
+}
+
+static int emit_test_block(int fd, struct test_file *file, int block_index)
+{
+	struct file_and_block blk = {
+		.file = file,
+		.block_index = block_index
+	};
+	int res = 0;
+
+	res = emit_test_blocks(fd, &blk, 1);
+	if (res == 0)
+		return -EINVAL;
+	if (res == 1)
+		return 0;
+	return res;
+}
+
+static void shuffle(int array[], int count, unsigned int seed)
+{
+	int i;
+
+	for (i = 0; i < count - 1; i++) {
+		int items_left = count - i;
+		int shuffle_index;
+		int v;
+
+		seed = 1103515245 * seed + 12345;
+		shuffle_index = i + seed % items_left;
+
+		v = array[shuffle_index];
+		array[shuffle_index] = array[i];
+		array[i] = v;
+	}
+}
+
+static int emit_test_file_data(int fd, struct test_file *file)
+{
+	int i;
+	int block_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
+	int *block_indexes = NULL;
+	struct file_and_block *blocks = NULL;
+	int result = 0;
+	int blocks_written = 0;
+
+	if (file->size == 0)
+		return 0;
+
+	blocks = calloc(block_cnt, sizeof(*blocks));
+	block_indexes = calloc(block_cnt, sizeof(*block_indexes));
+	for (i = 0; i < block_cnt; i++)
+		block_indexes[i] = i;
+
+	shuffle(block_indexes, block_cnt, file->ino);
+	for (i = 0; i < block_cnt; i++) {
+		blocks[i].block_index = block_indexes[i];
+		blocks[i].file = file;
+	}
+
+	for (i = 0; i < block_cnt; i += blocks_written) {
+		blocks_written = emit_test_blocks(fd,
+				blocks + i,
+				block_cnt - i);
+		if (blocks_written < 0) {
+			result = blocks_written;
+			goto out;
+		}
+		if (blocks_written == 0) {
+			result = -EIO;
+			goto out;
+		}
+	}
+out:
+	free(blocks);
+	free(block_indexes);
+	return result;
+}
+
+static loff_t read_whole_file(char *filename)
+{
+	int fd = -1;
+	loff_t result;
+	loff_t bytes_read = 0;
+	uint8_t buff[16 * 1024];
+
+	fd = open(filename, O_RDONLY);
+	if (fd <= 0)
+		return fd;
+
+	while (1) {
+		int read_result = read(fd, buff, ARRAY_SIZE(buff));
+
+		if (read_result < 0) {
+			print_error("Error during reading from a file.");
+			result = -errno;
+			goto cleanup;
+		} else if (read_result == 0)
+			break;
+
+		bytes_read += read_result;
+	}
+	result = bytes_read;
+
+cleanup:
+	close(fd);
+	return result;
+}
+
+
+static int read_test_file(uint8_t *buf, size_t len,
+			char *filename, int block_idx)
+{
+	int fd = -1;
+	int result;
+	int bytes_read = 0;
+	size_t bytes_to_read = len;
+	off_t offset = ((off_t)block_idx) * INCFS_DATA_FILE_BLOCK_SIZE;
+
+	fd = open(filename, O_RDONLY);
+	if (fd <= 0)
+		return fd;
+
+	if (lseek(fd, offset, SEEK_SET) != offset) {
+		print_error("Seek error");
+		return -errno;
+	}
+
+	while (bytes_read < bytes_to_read) {
+		int read_result =
+			read(fd, buf + bytes_read, bytes_to_read - bytes_read);
+		if (read_result < 0) {
+			result = -errno;
+			goto cleanup;
+		} else if (read_result == 0)
+			break;
+
+		bytes_read += read_result;
+	}
+	result = bytes_read;
+
+cleanup:
+	close(fd);
+	return result;
+}
+
+static int open_test_backing_file(char *mount_dir, bool delete)
+{
+	char backing_file_name[255];
+	int backing_fd;
+
+	snprintf(backing_file_name, ARRAY_SIZE(backing_file_name), "%s.img",
+		 mount_dir);
+	backing_fd = open(backing_file_name, O_CREAT | O_RDWR | O_TRUNC, 0666);
+	if (backing_fd < 0)
+		print_error("Can't open backing file");
+	else if (delete) {
+		/* Once backing file was opened, it's safe to delete it ;) */
+		remove(backing_file_name);
+	}
+	return backing_fd;
+}
+
+static int open_existing_test_backing_file(char *mount_dir, bool delete)
+{
+	char backing_file_name[255];
+	int backing_fd;
+
+	snprintf(backing_file_name, ARRAY_SIZE(backing_file_name), "%s.img",
+		 mount_dir);
+	backing_fd = open(backing_file_name, O_RDWR);
+	if (backing_fd < 0)
+		print_error("Can't open backing file");
+	else if (delete) {
+		/* Once backing file was opened, it's safe to delete it ;) */
+		remove(backing_file_name);
+	}
+	return backing_fd;
+}
+
+static int validate_test_file_content_with_seed(char *mount_dir,
+					 struct test_file *file,
+					 unsigned int shuffle_seed)
+{
+	int error = -1;
+	char *filename = concat_file_name(mount_dir, file->name);
+	off_t size = file->size;
+	loff_t actual_size = get_file_size(filename);
+	int block_cnt = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
+	int *block_indexes = NULL;
+	int i;
+
+	block_indexes = alloca(sizeof(int) * block_cnt);
+	for (i = 0; i < block_cnt; i++)
+		block_indexes[i] = i;
+
+	if (shuffle_seed != 0)
+		shuffle(block_indexes, block_cnt, shuffle_seed);
+
+	if (actual_size != size) {
+		ksft_print_msg("File size doesn't match. name: %s expected size:%ld actual size:%ld\n",
+		       filename, size, actual_size);
+		error = -1;
+		goto failure;
+	}
+
+	for (i = 0; i < block_cnt; i++) {
+		int block_idx = block_indexes[i];
+		uint8_t expected_block[INCFS_DATA_FILE_BLOCK_SIZE];
+		uint8_t actual_block[INCFS_DATA_FILE_BLOCK_SIZE];
+		int seed = get_file_block_seed(file->ino, block_idx);
+		size_t bytes_to_compare =
+			min((off_t)INCFS_DATA_FILE_BLOCK_SIZE,
+			size - ((off_t)block_idx) * INCFS_DATA_FILE_BLOCK_SIZE);
+		int read_result =
+			read_test_file(actual_block, INCFS_DATA_FILE_BLOCK_SIZE,
+				       filename, block_idx);
+		if (read_result < 0) {
+			ksft_print_msg("Error reading block %d from file %s. Error: %s\n",
+			       block_idx, filename, strerror(-read_result));
+			error = read_result;
+			goto failure;
+		}
+		rnd_buf(expected_block, INCFS_DATA_FILE_BLOCK_SIZE, seed);
+		if (memcmp(expected_block, actual_block, bytes_to_compare)) {
+			ksft_print_msg("File contents don't match. name: %s block:%d\n",
+			       file->name, block_idx);
+			error = -2;
+			goto failure;
+		}
+	}
+	free(filename);
+	return 0;
+
+failure:
+	free(filename);
+	return error;
+}
+
+static int validate_test_file_content(char *mount_dir, struct test_file *file)
+{
+	return validate_test_file_content_with_seed(mount_dir, file, 0);
+}
+
+static int dynamic_files_and_data_test(char *mount_dir)
+{
+	struct test_files_set test = get_test_files_set();
+	const int file_num = test.files_count;
+	const int missing_file_idx = 5;
+	int backing_fd = -1, cmd_fd = -1;
+	int i;
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Check that test files don't exist in the filesystem. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		char *filename = concat_file_name(mount_dir, file->name);
+
+		if (access(filename, F_OK) != -1) {
+			ksft_print_msg("File %s somehow already exists in a clean FS.\n",
+			       filename);
+			goto failure;
+		}
+		free(filename);
+	}
+
+	/* Write test data into the command file. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		int res;
+
+		res = emit_file(cmd_fd, file->name,
+			&file->ino, INCFS_ROOT_INODE, file->size);
+		if (res < 0) {
+			ksft_print_msg("Error %s emiting file %s.\n",
+					strerror(-res), file->name);
+			goto failure;
+		}
+
+		/* Skip writing data to one file so we can check */
+		/* that it's missing later. */
+		if (i == missing_file_idx)
+			continue;
+
+		res = emit_test_file_data(cmd_fd, file);
+		if (res) {
+			ksft_print_msg("Error %s emiting data for %s.\n",
+					strerror(-res), file->name);
+			goto failure;
+		}
+	}
+
+	/* Validate contents of the FS */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		if (i == missing_file_idx) {
+			/* No data has been written to this file. */
+			/* Check for read error; */
+			uint8_t buf;
+			char *filename =
+				concat_file_name(mount_dir, file->name);
+			int res = read_test_file(&buf, 1, filename, 0);
+
+			free(filename);
+			if (res > 0) {
+				ksft_print_msg("Data present, even though never writtern.\n");
+				goto failure;
+			}
+			if (res != -ETIME) {
+				ksft_print_msg("Wrong error code: %d.\n", res);
+				goto failure;
+			}
+		} else {
+			if (validate_test_file_content(mount_dir, file) < 0)
+				goto failure;
+		}
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int errors_on_overwrite_test(char *mount_dir)
+{
+	struct test_files_set test = get_small_test_files_set();
+	const int file_num = test.files_count;
+	int backing_fd = -1, cmd_fd = -1;
+	int i, bidx;
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Write test data into the command file. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		int emit_res;
+
+		emit_res = emit_file(cmd_fd, file->name, &file->ino,
+				     INCFS_ROOT_INODE, file->size);
+		if (emit_res < 0)
+			goto failure;
+
+		emit_res = emit_test_file_data(cmd_fd, file);
+		if (emit_res)
+			goto failure;
+	}
+
+	/* Write again, this time all writes should fail. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		int emit_res;
+
+		emit_res = emit_file(cmd_fd, file->name, &file->ino,
+				     INCFS_ROOT_INODE, file->size);
+		if (emit_res != -EEXIST) {
+			ksft_print_msg("Repeated file %s wasn't reported.\n",
+			       file->name);
+			goto failure;
+		}
+
+		for (bidx = 0; bidx * INCFS_DATA_FILE_BLOCK_SIZE < file->size;
+		     bidx++) {
+			emit_res = emit_test_block(cmd_fd, file, bidx);
+
+			/* Repeated blocks are ignored without an error */
+			if (emit_res < 0) {
+				ksft_print_msg("Repeated block was reported. err:%s\n",
+				       strerror(-emit_res));
+				goto failure;
+			}
+		}
+	}
+
+	/* Validate contents of the FS */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		if (validate_test_file_content(mount_dir, file) < 0)
+			goto failure;
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int work_after_remount_test(char *mount_dir)
+{
+	struct test_files_set test = get_test_files_set();
+	const int file_num = test.files_count;
+	const int file_num_stage1 = file_num / 2;
+	const int file_num_stage2 = file_num;
+	int i = 0;
+	int backing_fd = -1, cmd_fd = -1;
+
+	backing_fd = open_test_backing_file(mount_dir, false);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+	backing_fd = -1;
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Write first half of the data into the command file. (stage 1) */
+	for (i = 0; i < file_num_stage1; i++) {
+		struct test_file *file = &test.files[i];
+
+		emit_file(cmd_fd, file->name, &file->ino, INCFS_ROOT_INODE,
+			  file->size);
+		if (emit_test_file_data(cmd_fd, file))
+			goto failure;
+	}
+
+	/* Unmount and mount again, to see that data is persistent. */
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+	backing_fd = open_existing_test_backing_file(mount_dir, false);
+	if (backing_fd < 0)
+		goto failure;
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+	backing_fd = -1;
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Write the second half of the data into the command file. (stage 2) */
+	for (; i < file_num_stage2; i++) {
+		struct test_file *file = &test.files[i];
+
+		emit_file(cmd_fd, file->name, &file->ino, INCFS_ROOT_INODE,
+			  file->size);
+		if (emit_test_file_data(cmd_fd, file))
+			goto failure;
+	}
+
+	/* Validate contents of the FS */
+	for (i = 0; i < file_num_stage2; i++) {
+		struct test_file *file = &test.files[i];
+
+		if (validate_test_file_content(mount_dir, file) < 0)
+			goto failure;
+	}
+
+	/* Hide all files */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		char *filename = concat_file_name(mount_dir, file->name);
+
+		if (access(filename, F_OK) != 0) {
+			ksft_print_msg("File %s is not visible.\n", filename);
+			goto failure;
+		}
+
+		unlink_node(cmd_fd, INCFS_ROOT_INODE, file->name);
+
+		if (access(filename, F_OK) != -1) {
+			ksft_print_msg("File %s is still visible.\n", filename);
+			goto failure;
+		}
+		free(filename);
+	}
+
+	/* Unmount and mount again, to see that unlinked files stay unlinked. */
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+	backing_fd = open_existing_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+	backing_fd = -1;
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Validate all hidden files are still hidden. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		char *filename = concat_file_name(mount_dir, file->name);
+
+		if (access(filename, F_OK) != -1) {
+			ksft_print_msg("File %s is still visible.\n", filename);
+			goto failure;
+		}
+		free(filename);
+	}
+
+	/* Final unmount */
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	close(backing_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int validate_dir(char *dir_path, struct dirent *entries, int count)
+{
+	DIR *dir;
+	struct dirent *dp;
+	int result = 0;
+	int matching_entries = 0;
+
+	dir = opendir(dir_path);
+	if (!dir) {
+		result = -errno;
+		goto out;
+	}
+
+	while ((dp = readdir(dir))) {
+		int i;
+
+		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+			continue;
+
+		for (i = 0; i < count; i++) {
+			struct dirent *entry = entries + i;
+
+			if ((dp->d_ino == entry->d_ino) &&
+			    (strcmp(dp->d_name, entry->d_name) == 0) &&
+			    (dp->d_type == entry->d_type)) {
+				matching_entries++;
+				break;
+			}
+		}
+	}
+	result = count - matching_entries;
+
+out:
+	if (dir)
+		closedir(dir);
+	return result;
+}
+
+/* Test for:
+ *  1. No more than one hardlink can be created for a dir.
+ *  2. Only an empty dir can be unlinked.
+ */
+static int dirs_corner_cases(char *mount_dir)
+{
+	int dir1_ino = 0;
+	int dir2_ino = 0;
+	int backing_fd = -1, cmd_fd = -1;
+	char dirname1[] = "dir1";
+	char *dir_path1 = concat_file_name(mount_dir, dirname1);
+	char dirname2[] = "dir2";
+	char *dir_path2 = concat_file_name(dir_path1, dirname2);
+	struct stat st = {};
+	int ret;
+	struct incfs_instruction inst = {};
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+	backing_fd = -1;
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Create dir1 node. */
+	inst = (struct incfs_instruction) {
+			.type = INCFS_INSTRUCTION_NEW_FILE,
+			.file = {
+				.size = 0,
+				.mode = S_IFDIR | 0555,
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	dir1_ino = inst.file.ino_out;
+	if (ret)
+		goto failure;
+
+	/* Create dir2 node. */
+	inst = (struct incfs_instruction) {
+			.type = INCFS_INSTRUCTION_NEW_FILE,
+			.file = {
+				.size = 0,
+				.mode = S_IFDIR | 0555,
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	dir2_ino = inst.file.ino_out;
+	if (ret)
+		goto failure;
+
+	/* Try to put dir1 into itself. */
+	inst = (struct incfs_instruction){
+			.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = dir1_ino,
+				.child_ino = dir1_ino,
+				.name = ptr_to_u64(dirname1),
+				.name_len = strlen(dirname1)
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	if (ret != -EINVAL)
+		goto failure;
+
+	/* Try to put root into dir1. */
+	inst = (struct incfs_instruction){
+			.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = dir1_ino,
+				.child_ino = INCFS_ROOT_INODE,
+				.name = ptr_to_u64(dirname1),
+				.name_len = strlen(dirname1)
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	if (ret != -EINVAL)
+		goto failure;
+
+	/* Put dir1 into root. */
+	inst = (struct incfs_instruction){
+			.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = INCFS_ROOT_INODE,
+				.child_ino = dir1_ino,
+				.name = ptr_to_u64(dirname1),
+				.name_len = strlen(dirname1)
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	if (ret)
+		goto failure;
+
+	/* Check dir1 is visible. */
+	if (stat(dir_path1, &st) != 0 || st.st_ino != dir1_ino) {
+		print_error("stat failed for dir1");
+		goto failure;
+	}
+
+	/* Put dir2 into dir1. */
+	inst = (struct incfs_instruction){
+			.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = dir1_ino,
+				.child_ino = dir2_ino,
+				.name = ptr_to_u64(dirname2),
+				.name_len = strlen(dirname2)
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	if (ret)
+		goto failure;
+
+	/* Check dir2 is visible. */
+	if (stat(dir_path2, &st) != 0 || st.st_ino != dir2_ino) {
+		print_error("stat failed for dir2");
+		goto failure;
+	}
+
+	/* Try to create a loop. Put dir2 into dir1. */
+	inst = (struct incfs_instruction){
+			.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = dir2_ino,
+				.child_ino = dir1_ino,
+				.name = ptr_to_u64(dirname1),
+				.name_len = strlen(dirname1)
+			}
+	};
+	ret = send_md_instruction(cmd_fd, &inst);
+	if (ret != -EMLINK) {
+		ksft_print_msg("Loop creation test filed. %s\n",
+					strerror(-ret));
+		goto failure;
+	}
+
+	/* Try to unlink dir1 without removing dir2 first. */
+	ret = unlink_node(cmd_fd, INCFS_ROOT_INODE, dirname1);
+	if (ret != -ENOTEMPTY) {
+		ksft_print_msg("Unlinked non empty dir: %s\n", strerror(-ret));
+		goto failure;
+	}
+
+	ret = unlink_node(cmd_fd, dir1_ino, dirname2);
+	if (ret)
+		goto failure;
+
+	ret = unlink_node(cmd_fd, INCFS_ROOT_INODE, dirname1);
+	if (ret)
+		goto failure;
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+	free(dir_path1);
+	free(dir_path2);
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	close(backing_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int directory_structure_test(char *mount_dir)
+{
+	int dir_ino = 0;
+	int file_ino = 0;
+	int backing_fd = -1, cmd_fd = -1;
+	int mismatch_count = 0;
+	char dirname[] = "dir";
+	char filename[] = "file";
+	char *dir_path = concat_file_name(mount_dir, dirname);
+	char *file_path = concat_file_name(dir_path, filename);
+	struct stat st;
+	int ret;
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+	backing_fd = -1;
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Write test data into the command file. */
+	ret = emit_dir(cmd_fd, dirname, &dir_ino, INCFS_ROOT_INODE);
+	if (ret < 0) {
+		ksft_print_msg("Error creating a dir: %s\n", strerror(-ret));
+		goto failure;
+	}
+	ret = emit_file(cmd_fd, filename, &file_ino, dir_ino, 0);
+	if (ret < 0) {
+		ksft_print_msg("Error creating a file: %s\n", strerror(-ret));
+		goto failure;
+	}
+
+	/* Validate directory structure */
+	{
+		struct dirent dir_dentry = { .d_ino = dir_ino,
+					     .d_type = DT_DIR,
+					     .d_name = "dir" };
+		struct dirent cmd_dentry = { .d_ino = INCFS_COMMAND_INODE,
+					     .d_type = DT_REG,
+					     .d_name = ".cmd" };
+		struct dirent file_dentry = { .d_ino = file_ino,
+					      .d_type = DT_REG,
+					      .d_name = "file" };
+		struct dirent root_entries[] = { cmd_dentry, dir_dentry };
+		struct dirent dir_entries[] = { file_dentry };
+
+		mismatch_count = validate_dir(mount_dir, root_entries,
+					      ARRAY_SIZE(root_entries));
+		if (mismatch_count) {
+			ksft_print_msg("Root validatoin failed. Mismatch %d",
+			       mismatch_count);
+			goto failure;
+		}
+
+		mismatch_count = validate_dir(dir_path, dir_entries,
+					      ARRAY_SIZE(dir_entries));
+		if (mismatch_count) {
+			ksft_print_msg("Subdir validatoin failed. Mismatch %d",
+			       mismatch_count);
+			goto failure;
+		}
+	}
+
+	/* Validate file inode */
+	if (stat(file_path, &st) != 0) {
+		print_error("stat failed");
+		goto failure;
+	}
+
+	if (st.st_ino != file_ino) {
+		ksft_print_msg("Unexpected file inode.");
+		goto failure;
+	}
+
+	if (st.st_size != 0) {
+		ksft_print_msg("Unexpected file size.");
+		goto failure;
+	}
+
+	ret = unlink_node(cmd_fd, dir_ino, filename);
+	if (ret < 0) {
+		ksft_print_msg("Error unlinking a file: %s\n", strerror(-ret));
+		goto failure;
+	}
+
+	/* Validate directory structure */
+	{
+		struct dirent dir_entries[0] = {};
+
+		mismatch_count = validate_dir(dir_path, dir_entries, 0);
+		if (mismatch_count) {
+			ksft_print_msg("Second subdir validatoin failed. Mismatch %d",
+			       mismatch_count);
+			goto failure;
+		}
+
+		if (access(file_path, F_OK) != -1) {
+			ksft_print_msg("Unlinked file is still visible");
+			goto failure;
+		}
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+	free(file_path);
+	free(dir_path);
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	close(backing_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int data_producer(int fd, struct test_files_set *test_set)
+{
+	int ret = 0;
+	int timeout_ms = 1000;
+	struct incfs_pending_read_info prs[100] = {};
+	int prs_size = ARRAY_SIZE(prs);
+
+	while ((ret = wait_for_pending_reads(fd, timeout_ms,
+						prs, prs_size)) > 0) {
+		struct file_and_block blocks[ARRAY_SIZE(prs)] = {};
+		int read_count = ret;
+		int i;
+
+		for (i = 0; i < read_count; i++) {
+			int j = 0;
+
+			for (j = 0; j < test_set->files_count; j++) {
+				if (test_set->files[j].ino == prs[i].file_ino)
+					blocks[i].file = &test_set->files[j];
+			}
+			blocks[i].block_index = prs[i].block_index;
+		}
+
+		ret = emit_test_blocks(fd, blocks, read_count);
+		if (ret < 0) {
+			ksft_print_msg("Emitting test data error: %s\n",
+				strerror(-ret));
+			return ret;
+		}
+	}
+	return ret;
+}
+
+
+static int multiple_providers_test(char *mount_dir)
+{
+	struct test_files_set test = get_test_files_set();
+	const int file_num = test.files_count;
+	const int producer_count = 5;
+	int backing_fd = -1, cmd_fd = -1;
+	int status;
+	int i;
+	pid_t *producer_pids = alloca(producer_count * sizeof(pid_t));
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 10000) != 0)
+		goto failure;
+	close(backing_fd);
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Tell FS about the files, without actually providing the data. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		if (emit_file(cmd_fd, file->name, &file->ino, INCFS_ROOT_INODE,
+			      file->size) < 0)
+			goto failure;
+	}
+
+	/* Start producer processes */
+	for (i = 0; i < producer_count; i++) {
+		pid_t producer_pid = flush_and_fork();
+
+		if (producer_pid == 0) {
+			int ret;
+			/*
+			 * This is a child that should provide data to
+			 * pending reads.
+			 */
+
+			ret = data_producer(cmd_fd, &test);
+			exit(-ret);
+		} else if (producer_pid > 0) {
+			producer_pids[i] = producer_pid;
+		} else {
+			print_error("Fork error");
+			goto failure;
+		}
+	}
+
+	/* Validate FS content */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		char *filename = concat_file_name(mount_dir, file->name);
+		loff_t read_result = read_whole_file(filename);
+
+		free(filename);
+		if (read_result != file->size) {
+			ksft_print_msg("Error validating file %s. Result: %ld\n",
+				file->name, read_result);
+			goto failure;
+		}
+	}
+
+	/* Check that all producers has finished with 0 exit status */
+	for (i = 0; i < producer_count; i++) {
+		status = wait_for_process(producer_pids[i]);
+		if (status != 0) {
+			ksft_print_msg("Producer %d failed with code (%s)\n",
+			       i, strerror(status));
+			goto failure;
+		}
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int concurrent_reads_and_writes_test(char *mount_dir)
+{
+	struct test_files_set test = get_test_files_set();
+	const int file_num = test.files_count;
+	/* Validate each file from that many child processes. */
+	const int child_multiplier = 3;
+	int backing_fd = -1, cmd_fd = -1;
+	int status;
+	int i;
+	pid_t producer_pid;
+	pid_t *child_pids = alloca(child_multiplier * file_num * sizeof(pid_t));
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. */
+	if (mount_fs(mount_dir, backing_fd, 10000) != 0)
+		goto failure;
+	close(backing_fd);
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Tell FS about the files, without actually providing the data. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		if (emit_file(cmd_fd, file->name, &file->ino, INCFS_ROOT_INODE,
+			      file->size) < 0)
+			goto failure;
+	}
+
+	/* Start child processes acessing data in the files */
+	for (i = 0; i < file_num * child_multiplier; i++) {
+		struct test_file *file = &test.files[i / child_multiplier];
+		pid_t child_pid = flush_and_fork();
+
+		if (child_pid == 0) {
+			/* This is a child process, do the data validation. */
+			int ret = validate_test_file_content_with_seed(
+				mount_dir, file, i);
+			if (ret >= 0) {
+				/* Zero exit status if data is valid. */
+				exit(0);
+			}
+
+			/* Positive status if validation error found. */
+			exit(-ret);
+		} else if (child_pid > 0) {
+			child_pids[i] = child_pid;
+		} else {
+			print_error("Fork error");
+			goto failure;
+		}
+	}
+
+	producer_pid = flush_and_fork();
+	if (producer_pid == 0) {
+		int ret;
+		/*
+		 * This is a child that should provide data to
+		 * pending reads.
+		 */
+
+		ret = data_producer(cmd_fd, &test);
+		exit(-ret);
+	} else {
+		status = wait_for_process(producer_pid);
+		if (status != 0) {
+			ksft_print_msg("Data produces failed. %d(%s) ", status,
+			       strerror(status));
+			goto failure;
+		}
+	}
+
+	/* Check that all children has finished with 0 exit status */
+	for (i = 0; i < file_num * child_multiplier; i++) {
+		struct test_file *file = &test.files[i / child_multiplier];
+
+		status = wait_for_process(child_pids[i]);
+		if (status != 0) {
+			ksft_print_msg("Validation for the file %s failed with code %d (%s)\n",
+			       file->name, status, strerror(status));
+			goto failure;
+		}
+	}
+
+	/* Check that there are no pending reads left */
+	{
+		struct incfs_pending_read_info prs[1] = {};
+		int timeout = 0;
+		int read_count = wait_for_pending_reads(cmd_fd, timeout, prs,
+							ARRAY_SIZE(prs));
+
+		if (read_count) {
+			ksft_print_msg("Pending reads pending when all data written\n");
+			goto failure;
+		}
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int child_procs_waiting_for_data_test(char *mount_dir)
+{
+	struct test_files_set test = get_test_files_set();
+	const int file_num = test.files_count;
+	int backing_fd = -1, cmd_fd = -1;
+	int i;
+	pid_t *child_pids = alloca(file_num * sizeof(pid_t));
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	/* Mount FS and release the backing file. (10s wait time) */
+	if (mount_fs(mount_dir, backing_fd, 10000) != 0)
+		goto failure;
+	close(backing_fd);
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/* Tell FS about the files, without actually providing the data. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		emit_file(cmd_fd, file->name, &file->ino, INCFS_ROOT_INODE,
+			  file->size);
+	}
+
+	/* Start child processes acessing data in the files */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		pid_t child_pid = flush_and_fork();
+
+		if (child_pid == 0) {
+			/* This is a child process, do the data validation. */
+			int ret = validate_test_file_content(mount_dir, file);
+
+			if (ret >= 0) {
+				/* Zero exit status if data is valid. */
+				exit(0);
+			}
+
+			/* Positive status if validation error found. */
+			exit(-ret);
+		} else if (child_pid > 0) {
+			child_pids[i] = child_pid;
+		} else {
+			print_error("Fork error");
+			goto failure;
+		}
+	}
+
+	/* Write test data into the command file. */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		if (emit_test_file_data(cmd_fd, file))
+			goto failure;
+	}
+
+	/* Check that all children has finished with 0 exit status */
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+		int status = wait_for_process(child_pids[i]);
+
+		if (status != 0) {
+			ksft_print_msg("Validation for the file %s failed with code %d (%s)\n",
+			       file->name, status, strerror(status));
+			goto failure;
+		}
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static int file_count_limit(char *mount_dir)
+{
+	int file_ino = 0;
+	int i;
+	int backing_fd = -1, cmd_fd = -1;
+	char filename[100];
+	char file_path[100];
+	int ret;
+
+	backing_fd = open_test_backing_file(mount_dir, true);
+	if (backing_fd < 0)
+		goto failure;
+
+	if (mount_fs(mount_dir, backing_fd, 50) != 0)
+		goto failure;
+	close(backing_fd);
+	backing_fd = -1;
+
+	cmd_fd = open_commands_file(mount_dir);
+	if (cmd_fd < 0)
+		goto failure;
+
+	/*
+	 * Create INCFS_MAX_FILES - 1 files as see that everything works.
+	 * One inode is already taken by the root dir.
+	 */
+	for (i = 0; i < INCFS_MAX_FILES - 1; i++) {
+		struct stat st;
+
+		sprintf(filename, "file_%d", i);
+		sprintf(file_path, "%s/%s", mount_dir, filename);
+		ret = emit_file(cmd_fd, filename, &file_ino,
+				INCFS_ROOT_INODE, 0);
+		if (ret < 0) {
+			ksft_print_msg("Error creating a file: %s (%s)\n",
+				filename, strerror(-ret));
+			goto failure;
+		}
+
+		if (stat(file_path, &st) != 0) {
+			print_error("stat failed");
+			goto failure;
+		}
+	}
+
+	ret = emit_file(cmd_fd, "over_limit_file", &file_ino,
+			INCFS_ROOT_INODE, 0);
+	if (ret != -ENFILE) {
+		ksft_print_msg("Too many files were allowed to be cerated.\n");
+		goto failure;
+	}
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	if (umount(mount_dir) != 0) {
+		print_error("Can't unmout FS");
+		goto failure;
+	}
+	return TEST_SUCCESS;
+
+failure:
+	close(cmd_fd);
+	close(backing_fd);
+	umount(mount_dir);
+	return TEST_FAILURE;
+}
+
+static char *setup_mount_dir()
+{
+	struct stat st;
+	char *current_dir = get_current_dir_name();
+	char *mount_dir = concat_file_name(current_dir,
+						"incfs_test_mount_dir");
+
+	free(current_dir);
+	if (stat(mount_dir, &st) == 0) {
+		if (S_ISDIR(st.st_mode))
+			return mount_dir;
+
+		ksft_print_msg("%s is a file, not a dir.\n", mount_dir);
+		return NULL;
+	}
+
+	if (mkdir(mount_dir, 0777)) {
+		print_error("Can't create mount dir.");
+		return NULL;
+	}
+
+	return mount_dir;
+}
+
+int main(int argc, char *argv[])
+{
+	char *mount_dir = NULL;
+	int fails = 0;
+
+	ksft_print_header();
+
+	if (geteuid() != 0)
+		ksft_print_msg("Not a root, might fail to mount.\n");
+
+	mount_dir = setup_mount_dir();
+	if (mount_dir == NULL)
+		ksft_exit_fail_msg("Can't create a mount dir\n");
+
+#define RUN_TEST(test)                                                         \
+	do {                                                                   \
+		ksft_print_msg("Running " #test "\n");                         \
+		if (test(mount_dir) == TEST_SUCCESS)                           \
+			ksft_test_result_pass(#test "\n");                     \
+		else {                                                         \
+			ksft_test_result_fail(#test "\n");                     \
+			fails++;                                               \
+		}                                                              \
+	} while (0)
+
+	RUN_TEST(directory_structure_test);
+	RUN_TEST(dirs_corner_cases);
+	RUN_TEST(file_count_limit);
+	RUN_TEST(work_after_remount_test);
+	RUN_TEST(child_procs_waiting_for_data_test);
+	RUN_TEST(errors_on_overwrite_test);
+	RUN_TEST(concurrent_reads_and_writes_test);
+	RUN_TEST(multiple_providers_test);
+	RUN_TEST(dynamic_files_and_data_test);
+
+#undef RUN_TEST
+	umount2(mount_dir, MNT_FORCE);
+	rmdir(mount_dir);
+
+	if (fails > 0)
+		ksft_exit_pass();
+	else
+		ksft_exit_pass();
+	return 0;
+}
diff --git a/tools/testing/selftests/filesystems/incfs/utils.c b/tools/testing/selftests/filesystems/incfs/utils.c
new file mode 100644
index 000000000000..1f83b97225de
--- /dev/null
+++ b/tools/testing/selftests/filesystems/incfs/utils.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018 Google LLC
+ */
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <string.h>
+#include <poll.h>
+
+#include "utils.h"
+
+int mount_fs(char *mount_dir, int backing_fd, int read_timeout_ms)
+{
+	static const char fs_name[] = INCFS_NAME;
+	char mount_options[512];
+	int result;
+
+	snprintf(mount_options, ARRAY_SIZE(mount_options),
+		 "backing_fd=%u,read_timeout_ms=%u",
+		 backing_fd, read_timeout_ms);
+
+	result = mount(fs_name, mount_dir, fs_name, 0, mount_options);
+	if (result != 0)
+		perror("Error mounting fs.");
+	return result;
+}
+
+int unlink_node(int fd, int parent_ino, char *filename)
+{
+	struct incfs_instruction inst = {
+			.type = INCFS_INSTRUCTION_REMOVE_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = parent_ino,
+				.name = ptr_to_u64(filename),
+				.name_len = strlen(filename)
+			}
+	};
+
+	return send_md_instruction(fd, &inst);
+}
+
+int emit_node(int fd, char *filename, int *ino_out, int parent_ino,
+		size_t size, mode_t mode)
+{
+	int ret = 0;
+	__u64 ino = 0;
+	struct incfs_instruction inst = {
+			.type = INCFS_INSTRUCTION_NEW_FILE,
+			.file = {
+				.size = size,
+				.mode = mode,
+			}
+	};
+
+	ret = send_md_instruction(fd, &inst);
+	if (ret)
+		return ret;
+
+	ino = inst.file.ino_out;
+	inst = (struct incfs_instruction){
+			.type = INCFS_INSTRUCTION_ADD_DIR_ENTRY,
+			.dir_entry = {
+				.dir_ino = parent_ino,
+				.child_ino = ino,
+				.name = ptr_to_u64(filename),
+				.name_len = strlen(filename)
+			}
+		};
+	ret = send_md_instruction(fd, &inst);
+	if (ret)
+		return ret;
+	*ino_out = ino;
+	return 0;
+}
+
+
+int emit_dir(int fd, char *filename, int *ino_out, int parent_ino)
+{
+	return emit_node(fd, filename, ino_out, parent_ino, 0, S_IFDIR | 0555);
+}
+
+int emit_file(int fd, char *filename, int *ino_out, int parent_ino, size_t size)
+{
+	return emit_node(fd, filename, ino_out, parent_ino, size,
+				S_IFREG | 0555);
+}
+
+int send_md_instruction(int cmd_fd, struct incfs_instruction *inst)
+{
+	inst->version = INCFS_HEADER_VER;
+	if (ioctl(cmd_fd, INCFS_IOC_PROCESS_INSTRUCTION, inst) == 0)
+		return 0;
+	return -errno;
+}
+
+loff_t get_file_size(char *name)
+{
+	struct stat st;
+
+	if (stat(name, &st) == 0)
+		return st.st_size;
+	return -ENOENT;
+}
+
+int open_commands_file(char *mount_dir)
+{
+	char cmd_file[255];
+	int cmd_fd;
+
+	snprintf(cmd_file, ARRAY_SIZE(cmd_file), "%s/.cmd", mount_dir);
+	cmd_fd = open(cmd_file, O_RDWR);
+	if (cmd_fd < 0)
+		perror("Can't open commands file");
+	return cmd_fd;
+}
+
+int wait_for_pending_reads(int fd, int timeout_ms,
+	struct incfs_pending_read_info *prs, int prs_count)
+{
+	ssize_t read_res = 0;
+
+	if (timeout_ms > 0) {
+		int poll_res = 0;
+		struct pollfd pollfd = {
+			.fd = fd,
+			.events = POLLIN
+		};
+
+		poll_res = poll(&pollfd, 1, timeout_ms);
+		if (poll_res < 0)
+			return -errno;
+		if (poll_res == 0)
+			return 0;
+		if (!(pollfd.revents | POLLIN))
+			return 0;
+	}
+
+	read_res = read(fd, prs, prs_count * sizeof(*prs));
+	if (read_res < 0)
+		return -errno;
+
+	return read_res / sizeof(*prs);
+}
+
+char *concat_file_name(char *dir, char *file)
+{
+	char full_name[FILENAME_MAX] = "";
+
+	if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0)
+		return NULL;
+	return strdup(full_name);
+}
diff --git a/tools/testing/selftests/filesystems/incfs/utils.h b/tools/testing/selftests/filesystems/incfs/utils.h
new file mode 100644
index 000000000000..c3423fe01857
--- /dev/null
+++ b/tools/testing/selftests/filesystems/incfs/utils.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019 Google LLC
+ */
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include "../../include/uapi/linux/incrementalfs.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+
+#ifdef __LP64__
+#define ptr_to_u64(p) ((__u64)p)
+#else
+#define ptr_to_u64(p) ((__u64)(__u32)p)
+#endif
+
+int mount_fs(char *mount_dir, int backing_fd, int read_timeout_ms);
+
+int send_md_instruction(int cmd_fd, struct incfs_instruction *inst);
+
+int emit_node(int fd, char *filename, int *ino_out, int parent_ino,
+		size_t size, mode_t mode);
+
+int emit_dir(int fd, char *filename, int *ino_out, int parent_ino);
+
+int emit_file(int fd, char *filename, int *ino_out, int parent_ino,
+		size_t size);
+
+int unlink_node(int fd, int parent_ino, char *filename);
+
+loff_t get_file_size(char *name);
+
+int open_commands_file(char *mount_dir);
+
+int wait_for_pending_reads(int fd, int timeout_ms,
+	struct incfs_pending_read_info *prs, int prs_count);
+
+char *concat_file_name(char *dir, char *file);
--
2.21.0.593.g511ec345e18-goog




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux