/*
 * simple unit test for utils_io.c (blockwise low level functions)
 *
 * Copyright (C) 2018-2021 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "utils_io.h"

enum fn_enum {
	READ_BUFFER = 0,
	WRITE_BUFFER,
	READ_BLOCKWISE,
	WRITE_BLOCKWISE,
	READ_LSEEK_BLOCKWISE,
	WRITE_LSEEK_BLOCKWISE
} test_fn;

char		*test_file;
size_t		test_bsize;
size_t		test_alignment;
size_t		test_length;
off_t		test_offset; //FIXME: check for proper 64bit support (and test it!)
size_t		test_mem_alignment = 4096;

static int test_read_buffer(void)
{
	void *buffer = NULL;
	int fd = -1;
	ssize_t ret = -EINVAL;

	//printf("Entering test_read_buffer\n");

	if (posix_memalign(&buffer, test_mem_alignment, test_length)) {
		fprintf(stderr, "Failed to allocate aligned buffer.\n");
		goto out;
	}

	fd = open(test_file, O_RDONLY | O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "Failed to open %s.\n", test_file);
		goto out;
	}

	ret = read_buffer(fd, buffer, test_length);
	if (ret < 0)
		goto out;

	ret = (size_t) ret == test_length ? 0 : -EIO;
out:
	if (fd >= 0)
		close(fd);
	free(buffer);
	return ret;
}

static int test_write_buffer(void)
{
	void *buffer = NULL;
	int fd = -1;
	ssize_t ret = -EINVAL;

	//printf("Entering test_write_buffer\n");

	if (posix_memalign(&buffer, test_mem_alignment, test_length)) {
		fprintf(stderr, "Failed to allocate aligned buffer.\n");
		goto out;
	}

	fd = open(test_file, O_WRONLY | O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "Failed to open %s.\n", test_file);
		goto out;
	}

	ret = write_buffer(fd, buffer, test_length);
	if (ret < 0)
		goto out;

	ret = (size_t) ret == test_length ? 0 : -EIO;
out:
	if (fd >= 0)
		close(fd);
	free(buffer);
	return ret;
}

static int test_read_blockwise(void)
{
	void *buffer = NULL;
	int fd = -1;
	ssize_t ret = -EINVAL;

	//printf("Entering test_read_blockwise ");
	//printf("test_bsize: %zu, test_length: %zu\n", test_bsize, test_length);

	if (posix_memalign(&buffer, test_mem_alignment, test_length)) {
		fprintf(stderr, "Failed to allocate aligned buffer.\n");
		goto out;
	}

	fd = open(test_file, O_RDONLY | O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "Failed to open %s.\n", test_file);
		goto out;
	}


	ret = read_blockwise(fd, test_bsize, test_mem_alignment, buffer, test_length);
	if (ret < 0)
		goto out;

	ret = (size_t) ret == test_length ? 0 : -EIO;
out:
	if (fd >= 0)
		close(fd);
	free(buffer);
	return ret;
}

static int test_write_blockwise(void)
{
	void *buffer = NULL;
	int fd = -1;
	ssize_t ret = -EINVAL;

	//printf("Entering test_write_blockwise\n");

	if (posix_memalign(&buffer, test_mem_alignment, test_length)) {
		fprintf(stderr, "Failed to allocate aligned buffer.\n");
		goto out;
	}

	fd = open(test_file, O_RDWR | O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "Failed to open %s.\n", test_file);
		goto out;
	}

	ret = write_blockwise(fd, test_bsize, test_mem_alignment, buffer, test_length);
	if (ret < 0)
		goto out;

	ret = (size_t) ret == test_length ? 0 : -EIO;
out:
	if (fd >= 0)
		close(fd);
	free(buffer);
	return ret;
}

static int test_read_lseek_blockwise(void)
{
	void *buffer = NULL;
	int fd = -1;
	ssize_t ret = -EINVAL;

	//printf("Entering test_read_lseek_blockwise\n");

	if (posix_memalign(&buffer, test_mem_alignment, test_length)) {
		fprintf(stderr, "Failed to allocate aligned buffer.\n");
		goto out;
	}

	fd = open(test_file, O_RDONLY | O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "Failed to open %s.\n", test_file);
		goto out;
	}

	ret = read_lseek_blockwise(fd, test_bsize, test_mem_alignment, buffer, test_length, test_offset);
	if (ret < 0)
		goto out;

	ret = (size_t) ret == test_length ? 0 : -EIO;
out:
	if (fd >= 0)
		close(fd);
	free(buffer);
	return ret;
}

static int test_write_lseek_blockwise(void)
{
	void *buffer = NULL;
	int fd = -1;
	ssize_t ret = -EINVAL;

	//printf("Entering test_write_lseek_blockwise\n");

	if (posix_memalign(&buffer, test_mem_alignment, test_length)) {
		fprintf(stderr, "Failed to allocate aligned buffer.\n");
		goto out;
	}

	fd = open(test_file, O_RDWR | O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "Failed to open %s.\n", test_file);
		goto out;
	}

	ret = write_lseek_blockwise(fd, test_bsize, test_mem_alignment, buffer, test_length, test_offset);
	if (ret < 0)
		goto out;

	ret = (size_t) ret == test_length ? 0 : -EIO;
out:
	if (fd >= 0)
		close(fd);
	free(buffer);
	return ret;
}

static void usage(void)
{
	fprintf(stderr, "Use:\tunit-utils-io file/device blockwise_fn length  [bsize] [offset].\n");
}

static int parse_input_params(int argc, char **argv)
{
	struct stat st;
	unsigned long offset;

	if (argc < 4) {
		usage();
		return 1;
	}

	if (stat(argv[1], &st)) {
		fprintf(stderr, "File/device %s is missing?\n", argv[1]);
		return 1;
	}
	test_file = argv[1];
	if (sscanf(argv[3], "%zu", &test_length) != 1)
		return 1;
	if (argc >= 5 && sscanf(argv[4], "%zu", &test_bsize) != 1)
		return 1;
	if (argc >= 6) {
		if (sscanf(argv[5], "%ld", &offset) != 1)
			return 1;
		test_offset = offset;
	}

	if (!strcmp(argv[2], "read_buffer"))
		test_fn = READ_BUFFER;
	else if (!strcmp(argv[2], "write_buffer"))
		test_fn = WRITE_BUFFER;
	else if (!strcmp(argv[2], "read_blockwise")) {
		if (argc < 5) {
			usage();
			return 1;
		}
		test_fn = READ_BLOCKWISE;
	} else if (!strcmp(argv[2], "write_blockwise")) {
		if (argc < 5) {
			usage();
			return 1;
		}
		test_fn = WRITE_BLOCKWISE;
	} else if (!strcmp(argv[2], "read_lseek_blockwise")) {
		if (argc < 6) {
			usage();
			return 1;
		}
		test_fn = READ_LSEEK_BLOCKWISE;
	} else if (!strcmp(argv[2], "write_lseek_blockwise")) {
		if (argc < 6) {
			usage();
			return 1;
		}
		test_fn = WRITE_LSEEK_BLOCKWISE;
	} else {
		usage();
		return 1;
	}

	/* printf("function '%s': length %zu", argv[2], test_length);
	if (argc >= 5)
		printf(", bsize %zu", test_bsize);
	if (argc >= 6)
		printf(", offset %llu", test_offset);
	printf("\n"); */

	return 0;
}

int main(int argc, char **argv)
{
	long ps;
	int r = EXIT_FAILURE;

	if (parse_input_params(argc, argv))
		return r;

	ps = sysconf(_SC_PAGESIZE);
	if (ps > 0)
		test_mem_alignment = (size_t)ps;

	switch (test_fn) {
	case READ_BUFFER:
		r = test_read_buffer();
		break;
	case WRITE_BUFFER:
		r = test_write_buffer();
		break;
	case READ_BLOCKWISE:
		r = test_read_blockwise();
		break;
	case WRITE_BLOCKWISE:
		r = test_write_blockwise();
		break;
	case READ_LSEEK_BLOCKWISE:
		r = test_read_lseek_blockwise();
		break;
	case WRITE_LSEEK_BLOCKWISE:
		r = test_write_lseek_blockwise();
		break;
	default :
		fprintf(stderr, "Internal test error.\n");
		return r;
	}

	return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
