/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "str.h"
#include "ostream.h"
#include "fs-api.h"
#include "safe-mkdir.h"
#include "safe-mkstemp.h"
#include "test-common.h"
#include "test-dir.h"
#include "unlink-directory.h"
#include "settings.h"
#include <sys/stat.h>
#include <unistd.h>

static void test_fs_posix(void)
{
	int ret;
	const char *error;
	struct fs *fs;
	struct fs_parameters fs_params;

	test_begin("test-fs-posix filesystem");
	i_zero(&fs_params);

	const char *const settings[] = {
		"fs", "posix",
		"fs/posix/fs_driver", "posix",
		"fs_posix_prefix", t_strconcat(test_dir_get(), "/", NULL),
		NULL
	};
	struct settings_simple test_set;
	settings_simple_init(&test_set, settings);
	ret = fs_init_auto(test_set.event, &fs_params, &fs, &error);
	test_out_quiet("fs_init() failed", ret > 0);
	if (ret <= 0) {
		settings_simple_deinit(&test_set);
		test_end();
		goto error_no_fs;
	}

	struct fs *ref = fs;
	fs_ref(ref);
	fs_unref(&ref);
	test_assert(ref == NULL);
	test_assert(fs != NULL);

	test_assert(fs_get_parent(fs) == NULL);
	test_assert_strcmp(fs_get_driver(fs), "posix");
	test_end();

	struct fs_file *file;
	char buf[10];
	ssize_t count;
	test_begin("test-fs-posix bad file read");
	file = fs_file_init(fs, "fail_1", FS_OPEN_MODE_READONLY);
	test_assert(fs_exists(file) == 0);
	count = fs_read(file, buf, 1);
	test_assert(count == -1 && errno == ENOENT &&
		    strstr(fs_file_last_error(file), "No such file or directory") != NULL);
	fs_file_deinit(&file);
	test_end();

	test_begin("test-fs-posix good file write");
	file = fs_file_init(fs, "good1", FS_OPEN_MODE_REPLACE);
	test_assert(file != NULL);
	test_assert(fs_exists(file) == 0); /* file not created until data is written */
	test_assert(fs_write(file, "X", 1) == 0);
	test_assert(fs_exists(file) == 1);
	fs_file_deinit(&file);
	test_end();

	test_begin("test-fs-posix good file read");
	file = fs_file_init(fs, "good1", FS_OPEN_MODE_READONLY);
	test_assert(fs_exists(file) == 1);
	errno = 0;
	count = fs_read(file, buf, 2);
	test_assert(count == 1 && errno == 0);
	fs_file_deinit(&file);
	test_end();

	struct fs_iter *iter = fs_iter_init(fs, "/", 0);
	const char *filename;
	test_begin("test-fs-posix iterator");
	filename = fs_iter_next(iter);
	test_assert_strcmp(filename, "good1");
	test_assert(fs_iter_next(iter) == NULL);
	fs_iter_deinit(&iter, &error);
	test_end();

	struct stat st;
	test_begin("test-fs-posix file stat and delete");
	file = fs_file_init(fs, "good1", FS_OPEN_MODE_READONLY);
	test_assert(fs_stat(file, &st) == 0);
	test_assert(st.st_size == 1);
	test_assert(fs_delete(file) == 0);
	fs_file_deinit(&file);
	test_end();

	test_begin("test-fs-posix file write fname rename");
	file = fs_file_init(fs, "subdir/badfname", FS_OPEN_MODE_REPLACE);
	struct ostream *output = fs_write_stream(file);
	o_stream_nsend_str(output, "hello");
	fs_set_metadata(file, FS_METADATA_WRITE_FNAME, "subdir/rename1");
	test_assert(fs_write_stream_finish(file, &output) == 1);
	test_assert(strcmp(fs_file_path(file), "subdir/rename1") == 0);
	fs_file_deinit(&file);
	file = fs_file_init(fs, "subdir/rename1", FS_OPEN_MODE_READONLY);
	test_assert(fs_stat(file, &st) == 0);
	test_assert(st.st_size == 5);
	fs_file_deinit(&file);
	test_end();

	test_begin("test-fs-posix file copy fname rename");
	struct fs_file *src = fs_file_init(fs, "subdir/rename1", FS_OPEN_MODE_READONLY);
	struct fs_file *dest = fs_file_init(fs, "subdir/badfname", FS_OPEN_MODE_REPLACE);
	fs_set_metadata(dest, FS_METADATA_WRITE_FNAME, "subdir/rename2");
	test_assert(fs_copy(src, dest) == 0);
	test_assert(strcmp(fs_file_path(dest), "subdir/rename2") == 0);
	fs_file_deinit(&src);
	fs_file_deinit(&dest);
	file = fs_file_init(fs, "subdir/rename2", FS_OPEN_MODE_READONLY);
	test_assert(fs_stat(file, &st) == 0);
	test_assert(st.st_size == 5);
	fs_file_deinit(&file);
	test_end();

	fs_deinit(&fs);
	settings_simple_deinit(&test_set);

error_no_fs:
	return;
}

int main(void)
{
	static void (*const test_functions[])(void) = {
		test_fs_posix,
		NULL
	};

	test_dir_init("fs-posix");

	return test_run(test_functions);
}
