// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (c) 2017, Intel Corporation.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * library for creating and managing File Level Redundancy (FLR) mirrors
 *
 * Author: Jinshan Xiong <jinshan.xiong@intel.com>
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <assert.h>
#include <sys/param.h>

#include <libcfs/util/ioctl.h>
#include <lustre/lustreapi.h>
#include <linux/lustre/lustre_ioctl.h>
#include "lustreapi_internal.h"

/**
 * llapi_mirror_set() - Set the mirror id for the opening file pointed by @fd
 * @fd: file descriptor, must be opened with O_DIRECT
 * @id: mirror id (If @id is zero, it will clear the mirror id setting)
 *
 * Set the mirror id for the opening file pointed by @fd, once the mirror
 * is set successfully, the policy to choose mirrors will be disabed and the
 * following I/O from this file descriptor will be led to this dedicated
 * mirror @id.
 *
 * Return:
 * * %0 on success.
 * * %-errno on failure.
 */
int llapi_mirror_set(int fd, unsigned int id)
{
	struct stat stbuf;
	int rc;

	rc = ioctl(fd, LL_IOC_FLR_SET_MIRROR, id);
	if (rc < 0) {
		rc = -errno;
		return rc;
	}

	if (!id)
		return 0;

	/* in the current implementation, llite doesn't verify if the mirror
	 * id is valid, it has to be verified in an I/O context so the fstat()
	 * call is to verify that the mirror id is correct. */
	rc = fstat(fd, &stbuf);
	if (rc < 0) {
		rc = -errno;

		(void) ioctl(fd, LL_IOC_FLR_SET_MIRROR, 0);
	}

	return rc;
}

/*
 * Clear mirror id setting.
 *
 * See llapi_mirror_set() for details.
 */
int llapi_mirror_clear(int fd)
{
	return llapi_mirror_set(fd, 0);
}

/**
 * llapi_mirror_read() - Read data from a specified mirror with @id.
 * @fd: file descriptor, should be opened with O_DIRECT
 * @id: mirror id to be read from
 * @buf: read buffer
 * @count: number of bytes to be read
 * @pos: file postion where the read starts
 *
 * Read data from a specified mirror with @id. This function won't read
 * partial read result; either file end is reached, or number of @count bytes
 * is read, or an error will be returned.
 *
 * Return:
 * * %>=0 Number of bytes has been read
 * * %negative on failure(last seen error)
 */
ssize_t llapi_mirror_read(int fd, unsigned int id, void *buf, size_t count,
			  off_t pos)
{
	ssize_t result = 0;
	ssize_t page_size;
	int rc;

	page_size = sysconf(_SC_PAGESIZE);
	if (page_size < 0) {
		rc = -errno;
		return rc;
	}

	rc = llapi_mirror_set(fd, id);
	if (rc < 0)
		return rc;

	while (count > 0) {
		ssize_t bytes_read;

		bytes_read = pread(fd, buf, count, pos);
		if (!bytes_read) /* end of file */
			break;

		if (bytes_read < 0) {
			result = -errno;
			llapi_error(LLAPI_MSG_WARN, result,
				    "fail to pread %ld-%ld of mirror %u",
				    pos, count, id);
			break;
		}

		result += bytes_read;
		pos += bytes_read;
		buf += bytes_read;
		count -= bytes_read;

		if (bytes_read & (page_size - 1)) /* end of file */
			break;
	}

	(void) llapi_mirror_clear(fd);

	return result;
}

ssize_t llapi_mirror_write(int fd, unsigned int id, const void *buf,
			   size_t count, off_t pos)
{
	ssize_t result = 0;
	ssize_t page_size;
	int rc;

	page_size = sysconf(_SC_PAGESIZE);
	if (page_size < 0)
		return -EINVAL;

	if (((unsigned long)buf & (page_size - 1)) || pos & (page_size - 1))
		return -EINVAL;

	rc = llapi_mirror_set(fd, id);
	if (rc < 0)
		return rc;

	while (count > 0) {
		ssize_t bytes_written;

		if (pos & (page_size - 1)) {
			result = -EINVAL;
			break;
		}

		bytes_written = pwrite(fd, buf, count, pos);
		if (bytes_written < 0) {
			result = -errno;
			llapi_error(LLAPI_MSG_WARN, result,
				    "fail to pwrite %ld-%ld of mirror %u",
				    pos, count, id);
			break;
		}

		result += bytes_written;
		pos += bytes_written;
		buf += bytes_written;
		count -= bytes_written;
	}

	(void) llapi_mirror_clear(fd);

	return result;
}

int llapi_mirror_truncate(int fd, unsigned int id, off_t length)
{
	int rc;

	rc = llapi_mirror_set(fd, id);
	if (rc < 0)
		return rc;

	rc = ftruncate(fd, length);
	if (rc < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_WARN, rc,
			    "fail to ftruncate mirror %u to %ld", id, length);
	}

	(void) llapi_mirror_clear(fd);

	return rc;
}

int llapi_mirror_punch(int fd, unsigned int id, off_t start, size_t length)
{
	int rc;

	rc = llapi_mirror_set(fd, id);
	if (rc < 0)
		return rc;

	rc = llapi_hole_punch(fd, start, length);
	(void) llapi_mirror_clear(fd);

	return rc;
}

bool llapi_mirror_is_sparse(int fd, unsigned int id)
{
	bool sparse;
	int rc;

	rc = llapi_mirror_set(fd, id);
	if (rc < 0)
		return false;

	sparse = llapi_file_is_sparse(fd);
	(void) llapi_mirror_clear(fd);

	return sparse;
}

/**
 * llapi_mirror_data_seek() - Seek data in a specified mirror with @id.
 * @fd: file descriptor, should be opened with O_DIRECT
 * @id: mirror id to be read from
 * @pos: position for start data seek from
 * @size: size of data segment found
 *
 * Seek data in a specified mirror with @id. This function looks for the
 * first data segment from given offset and returns its offset and length
 *
 * Return:
 * * %>=0 Number of bytes has been read
 * * %negative on failure(last seen error)
 */
off_t llapi_mirror_data_seek(int fd, unsigned int id, off_t pos, size_t *size)
{
	off_t data_off;
	int rc;

	rc = llapi_mirror_set(fd, id);
	if (rc < 0)
		return rc;

	data_off = llapi_data_seek(fd, pos, size);
	(void) llapi_mirror_clear(fd);

	return data_off;
}

/**
 * llapi_mirror_copy_many() - Copy contents from @src to multiple @dst
 * @fd: file descriptor, should be opened with O_DIRECT
 * @src: source mirror id, usually a valid mirror
 * @dst: an array of destination mirror ids
 * @count: number of elements in array @dst
 *
 * Copy data contents from source mirror @src to multiple destinations
 * pointed by @dst. The destination array @dst will be altered to store
 * successfully copied mirrors.
 *
 * Return:
 * * %>0 Number of bytes has been read
 * * %negative on failure(last seen error)
 */
ssize_t llapi_mirror_copy_many(int fd, __u16 src, __u16 *dst, size_t count)
{
	const size_t buflen = DEFAULT_IO_BUFLEN;
	void *buf;
	off_t pos = 0;
	off_t data_end = 0;
	ssize_t page_size;
	ssize_t result = 0;
	bool eof = false;
	bool sparse;
	int nr;
	int i;
	int rc;

	if (!count)
		return 0;

	page_size = sysconf(_SC_PAGESIZE);
	if (page_size < 0) {
		rc = -errno;
		return rc;
	}

	rc = posix_memalign(&buf, page_size, buflen);
	if (rc) /* error code is returned directly */
		return -rc;
	(void)mlock(buf, buflen);

	sparse = llapi_mirror_is_sparse(fd, src);

	nr = count;
	if (sparse) {
		/* for sparse src we have to be sure that dst has no
		 * data in src holes, so truncate it first
		 */
		for (i = 0; i < nr; i++) {
			rc = llapi_mirror_truncate(fd, dst[i], pos);
			if (rc < 0) {
				result = rc;
				/* exclude the failed one */
				dst[i] = dst[--nr];
				i--;
				continue;
			}
		}
		if (!nr) 
			goto out_free;
	}

	while (!eof) {
		off_t data_off;
		ssize_t bytes_read;
		size_t to_write, to_read;

		if (sparse && pos >= data_end) {
			size_t data_size;

			data_off = llapi_mirror_data_seek(fd, src, pos,
							  &data_size);
			if (data_off < 0) {
				/* Non-fatal, switch to full copy */
				sparse = false;
				continue;
			}
			if (!data_size) {
				/* hole at the end of file, set pos to the
				 * data_off, so truncate block at the end
				 * will set final dst size.
				 */
				pos = data_off;
				break;
			}

			data_end = data_off + data_size;
			/* align by page */
			pos = data_off & ~(page_size - 1);
			data_end = ((data_end - 1) | (page_size - 1)) + 1;
			to_read = MIN(data_end - pos, buflen);
		} else {
			to_read = buflen;
		}

		bytes_read = llapi_mirror_read(fd, src, buf, to_read, pos);
		if (!bytes_read) { /* end of file */
			break;
		} else if (bytes_read < 0) {
			result = bytes_read;
			nr = 0;
			llapi_error(LLAPI_MSG_ERROR, result,
				    "error reading bytes %ld-%ld of mirror %u",
				    pos, to_read, src);
			break;
		}

		/* round up to page align to make direct IO happy.
		 * this implies the last segment to write. */
		to_write = ((bytes_read - 1) | (page_size - 1)) + 1;

		for (i = 0; i < nr; i++) {
			ssize_t written;

			written = llapi_mirror_write(fd, dst[i], buf,
						      to_write, pos);
			if (written < 0) {
				result = written;

				/* this mirror is not written succesfully,
				 * get rid of it from the array */
				dst[i] = dst[--nr];
				i--;
				continue;
			}
			assert(written == to_write);
		}
		pos += bytes_read;
		eof = bytes_read < to_read;
	}

	if (nr > 0) {
		for (i = 0; i < nr; i++) {
			rc = llapi_mirror_truncate(fd, dst[i], pos);
			if (rc < 0) {
				result = rc;

				/* exclude the failed one */
				dst[i] = dst[--nr];
				--i;
				continue;
			}
		}
	}

out_free:
	(void)munlock(buf, buflen);
	free(buf);

	return nr > 0 ? nr : result;
}

/**
 * llapi_mirror_copy() - Copy contents from @src to target mirror @dst.
 * @fd: file descriptor, should be opened with O_DIRECT
 * @src: source mirror id, usually a valid mirror
 * @dst: mirror id of copy destination
 * @pos: start file pos
 * @count: number of bytes to be copied
 *
 * Copy data contents from source mirror @src to target mirror @dst.
 *
 * Return:
 * * %>0 Number of bytes has been read
 * * %negative on failure(last seen error)
 */
int llapi_mirror_copy(int fd, unsigned int src, unsigned int dst, off_t pos,
		      size_t count)
{
	const size_t buflen = DEFAULT_IO_BUFLEN;
	size_t page_size;
	ssize_t result = 0;
	void *buf;
	int rc;

	if (!count)
		return 0;

	page_size = sysconf(_SC_PAGESIZE);
	if (page_size < 0)
		return -EINVAL;

	if (pos & (page_size - 1) || !dst)
		return -EINVAL;

	if (count != OBD_OBJECT_EOF && count & (page_size - 1))
		return -EINVAL;

	rc = posix_memalign(&buf, page_size, buflen);
	if (rc) /* error code is returned directly */
		return -rc;
	(void)mlock(buf, buflen);

	while (result < count) {
		ssize_t bytes_read, bytes_written;
		size_t to_read, to_write;

		to_read = MIN(buflen, count - result);
		if (src == 0)
			bytes_read = pread(fd, buf, to_read, pos);
		else
			bytes_read = llapi_mirror_read(fd, src, buf, to_read,
							pos);
		if (!bytes_read) { /* end of file */
			break;
		} else if (bytes_read < 0) {
			result = bytes_read;
			llapi_error(LLAPI_MSG_ERROR, result,
				    "error reading bytes %ld-%ld of mirror %u",
				    pos, to_read, src);
			break;
		}

		/* round up to page align to make direct IO happy.
		 * this implies the last segment to write. */
		to_write = (bytes_read + page_size - 1) & ~(page_size - 1);

		bytes_written = llapi_mirror_write(fd, dst, buf, to_write,
						    pos);
		if (bytes_written < 0) {
			result = bytes_written;
			llapi_error(LLAPI_MSG_ERROR, result,
				    "error writing bytes %ld-%ld of mirror %u",
				    pos, to_write, dst);
			break;
		}

		assert(bytes_written == to_write);

		pos += bytes_read;
		result += bytes_read;

		if (bytes_read < to_read) /* short read occurred */
			break;
	}

	(void)munlock(buf, buflen);
	free(buf);

	if (result > 0) {
		rc = llapi_mirror_truncate(fd, dst, pos);
		if (rc < 0) {
			llapi_error(LLAPI_MSG_ERROR, result,
				    "error truncating mirror %u to %ld",
				    dst, pos);
			result = rc;
		}
	}

	return result;
}
