/*
 * Copyright (c) 2014 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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.
 *
 */
#include <common.h>
#include <fs.h>
#include <fcntl.h>
#include <malloc.h>
#include <libfile.h>
#include <progress.h>
#include <stdlib.h>
#include <linux/stat.h>

/*
 * pwrite_full - write to filedescriptor at offset
 *
 * Like pwrite, but guarantees to write the full buffer out, else it
 * returns with an error.
 */
int pwrite_full(int fd, const void *buf, size_t size, loff_t offset)
{
	size_t insize = size;
	int now;

	while (size) {
		now = pwrite(fd, buf, size, offset);
		if (now == 0) {
			errno = ENOSPC;
			return -errno;
		}
		if (now < 0)
			return now;
		size -= now;
		buf += now;
		offset += now;
	}

	return insize;
}
EXPORT_SYMBOL(pwrite_full);

/*
 * write_full - write to filedescriptor
 *
 * Like write, but guarantees to write the full buffer out, else
 * it returns with an error.
 */
int write_full(int fd, const void *buf, size_t size)
{
	size_t insize = size;
	int now;

	while (size) {
		now = write(fd, buf, size);
		if (now == 0) {
			errno = ENOSPC;
			return -errno;
		}
		if (now < 0)
			return now;
		size -= now;
		buf += now;
	}

	return insize;
}
EXPORT_SYMBOL(write_full);

/*
 * read_full - read from filedescriptor
 *
 * Like read, but this function only returns less bytes than
 * requested when the end of file is reached.
 */
int read_full(int fd, void *buf, size_t size)
{
	size_t insize = size;
	int now;

	while (size) {
		now = read(fd, buf, size);
		if (now == 0)
			break;
		if (now < 0)
			return now;
		size -= now;
		buf += now;
	}

	return insize - size;
}
EXPORT_SYMBOL(read_full);

int copy_fd(int in, int out)
{
	int bs = 4096, ret;
	void *buf = malloc(bs);

	if (!buf)
		return -ENOMEM;

	while (1) {
		ret = read(in, buf, bs);
		if (ret <= 0)
			break;

		ret = write_full(out, buf, ret);
		if (ret < 0)
			break;
	}

        free(buf);

	return ret;
}

/*
 * read_file_line - read a line from a file
 *
 * Used to compose a filename from a printf format and to read a line from this
 * file. All leading and trailing whitespaces (including line endings) are
 * removed. The returned buffer must be freed with free(). This function is
 * supposed for reading variable like content into a buffer, so files > 1024
 * bytes are ignored.
 */
char *read_file_line(const char *fmt, ...)
{
	va_list args;
	char *filename;
	char *buf, *line = NULL;
	size_t size;
	int ret;
	struct stat s;

	va_start(args, fmt);
	filename = bvasprintf(fmt, args);
	va_end(args);

	ret = stat(filename, &s);
	if (ret)
		goto out;

	if (s.st_size > 1024)
		goto out;

	buf = read_file(filename, &size);
	if (!buf)
		goto out;

	line = strim(buf);

	line = xstrdup(line);
	free(buf);
out:
	free(filename);
	return line;
}
EXPORT_SYMBOL_GPL(read_file_line);

/**
 * read_file_2 - read a file to an allocated buffer
 * @filename:  The filename to read
 * @size:      After successful return contains the size of the file
 * @outbuf:    contains a pointer to the file data after successful return
 * @max_size:  The maximum size to read. Use FILESIZE_MAX for reading files
 *             of any size.
 *
 * This function reads a file to an allocated buffer. At maximum @max_size
 * bytes are read. The actual read size is returned in @size. -EFBIG is
 * returned if the file is bigger than @max_size, but the buffer is read
 * anyway up to @max_size in this case. Free the buffer with free() after
 * usage. The allocated buffer is actually one byte bigger than the file
 * and the extra byte is initialized to '\0' so that the returned buffer
 * can safely be interpreted as a string.
 *
 * Return: 0 for success, or negative error code. -EFBIG is returned
 * when the file has been bigger than max_size.
 */
int read_file_2(const char *filename, size_t *size, void **outbuf,
		loff_t max_size)
{
	int fd;
	struct stat s;
	void *buf = NULL;
	const char *tmpfile = "/.read_file_tmp";
	int ret;
	loff_t read_size;

again:
	ret = stat(filename, &s);
	if (ret)
		return ret;

	if (max_size == FILESIZE_MAX)
		read_size = s.st_size;
	else
		read_size = max_size;

	if (read_size == FILESIZE_MAX) {
		ret = copy_file(filename, tmpfile, 0);
		if (ret)
			return ret;
		filename = tmpfile;
		goto again;
	}

	buf = calloc(read_size + 1, 1);
	if (!buf) {
		ret = -ENOMEM;
		errno = ENOMEM;
		goto err_out;
	}

	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		ret = fd;
		goto err_out;
	}

	ret = read_full(fd, buf, read_size);
	if (ret < 0)
		goto err_out1;

	close(fd);

	if (size)
		*size = ret;

	if (filename == tmpfile)
		unlink(tmpfile);

	*outbuf = buf;

	if (read_size < s.st_size)
		return -EFBIG;

	return 0;

err_out1:
	close(fd);
err_out:
	free(buf);

	if (filename == tmpfile)
		unlink(tmpfile);

	return ret;
}
EXPORT_SYMBOL(read_file_2);

/**
 * read_file - read a file to an allocated buffer
 * @filename:  The filename to read
 * @size:      After successful return contains the size of the file
 *
 * This function reads a file to an allocated buffer.
 * Some TFTP servers do not transfer the size of a file. In this case
 * the file is first read to a temporary file.
 *
 * Return: The buffer containing the file or NULL on failure
 */
void *read_file(const char *filename, size_t *size)
{
	int ret;
	void *buf;

	ret = read_file_2(filename, size, &buf, FILESIZE_MAX);
	if (!ret)
		return buf;

	return NULL;
}
EXPORT_SYMBOL(read_file);

/**
 * write_file - write a buffer to a file
 * @filename:    The filename to write
 * @size:        The size of the buffer
 *
 * Return: 0 for success or negative error value
 */
int write_file(const char *filename, const void *buf, size_t size)
{
	int fd, ret;

	fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT);
	if (fd < 0)
		return fd;

	ret = write_full(fd, buf, size);

	close(fd);

	if (ret < 0)
		return ret;

	return 0;
}
EXPORT_SYMBOL(write_file);

/**
 * write_file_flash - write a buffer to a file backed by flash
 * @filename:    The filename to write
 * @size:        The size of the buffer
 *
 * Functional this is identical to write_file but calls erase() before writing.
 *
 * Return: 0 for success or negative error value
 */
int write_file_flash(const char *filename, const void *buf, size_t size)
{
	int fd, ret;

	fd = open(filename, O_WRONLY);
	if (fd < 0)
		return fd;

	ret = erase(fd, size, 0);
	if (ret < 0)
		goto out_close;

	ret = write_full(fd, buf, size);

out_close:
	close(fd);

	if (ret < 0)
		return ret;

	return 0;
}
EXPORT_SYMBOL(write_file_flash);

/**
 * copy_file - Copy a file
 * @src:	The source filename
 * @dst:	The destination filename
 * @verbose:	if true, show a progression bar
 *
 * Return: 0 for success or negative error code
 */
int copy_file(const char *src, const char *dst, int verbose)
{
	char *rw_buf = NULL;
	int srcfd = 0, dstfd = 0;
	int r, s;
	int ret = 1, err1 = 0;
	int mode;
	loff_t total = 0;
	struct stat srcstat, dststat;

	rw_buf = xmalloc(RW_BUF_SIZE);

	srcfd = open(src, O_RDONLY);
	if (srcfd < 0) {
		printf("could not open %s: %s\n", src, errno_str());
		ret = srcfd;
		goto out;
	}

	mode = O_WRONLY | O_CREAT;

	s = stat(dst, &dststat);
	if (s && s != -ENOENT) {
		printf("could not stat %s: %s\n", dst, errno_str());
		ret = s;
		goto out;
	}

	/* Set O_TRUNC only if file exists and is a regular file */
	if (!s && S_ISREG(dststat.st_mode))
		mode |= O_TRUNC;

	dstfd = open(dst, mode);
	if (dstfd < 0) {
		printf("could not open %s: %s\n", dst, errno_str());
		ret = dstfd;
		goto out;
	}

	ret = stat(src, &srcstat);
	if (ret)
		goto out;

	if (srcstat.st_size != FILESIZE_MAX) {
		discard_range(dstfd, srcstat.st_size, 0);
		if (s || S_ISREG(dststat.st_mode)) {
			ret = ftruncate(dstfd, srcstat.st_size);
			if (ret)
				goto out;
		}
	}

	if (verbose)
		init_progression_bar(srcstat.st_size);

	while (1) {
		r = read(srcfd, rw_buf, RW_BUF_SIZE);
		if (r < 0) {
			perror("read");
			ret = r;
			goto out;
		}
		if (!r)
			break;

		ret = write_full(dstfd, rw_buf, r);
		if (ret < 0) {
			perror("write");
			goto out;
		}

		total += r;

		if (verbose) {
			if (srcstat.st_size && srcstat.st_size != FILESIZE_MAX)
				show_progress(total);
			else
				show_progress(total / 16384);
		}
	}

	ret = 0;
out:
	if (verbose)
		putchar('\n');

	free(rw_buf);
	if (srcfd > 0)
		close(srcfd);
	if (dstfd > 0)
		err1 = close(dstfd);

	return ret ?: err1;
}
EXPORT_SYMBOL(copy_file);

int copy_recursive(const char *src, const char *dst)
{
	struct stat s;
	DIR *dir;
	struct dirent *d;
	int ret;

	ret = stat(src, &s);
	if (ret)
		return ret;

	if (!S_ISDIR(s.st_mode))
		return copy_file(src, dst, 0);

	ret = make_directory(dst);
	if (ret)
		return ret;

	dir = opendir(src);
	if (!dir)
		return -EIO;

	while ((d = readdir(dir))) {
		char *from, *to;
		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
			continue;

		from = basprintf("%s/%s", src, d->d_name);
		to = basprintf("%s/%s", dst, d->d_name);
		ret = copy_recursive(from, to);
		if (ret)
			break;
		free(from);
		free(to);
	}
	closedir(dir);

	return ret;
}

/**
 * compare_file - Compare two files
 * @f1:		The first file
 * @f2:		The second file
 *
 * Return: 0 if both files are identical, 1 if they differ,
 *         a negative error code if some error occured
 */
int compare_file(const char *f1, const char *f2)
{
	int fd1, fd2, ret;
	struct stat s1, s2;
	void *buf1, *buf2;
	loff_t left;

	fd1 = open(f1, O_RDONLY);
	if (fd1 < 0)
		return -errno;

	fd2 = open(f2, O_RDONLY);
	if (fd2 < 0) {
		ret = -errno;
		goto err_out1;
	}

	ret = fstat(fd1, &s1);
	if (ret)
		goto err_out2;

	ret = fstat(fd2, &s2);
	if (ret)
		goto err_out2;

	if (s1.st_size != s2.st_size)
		return 1;

	buf1 = xmalloc(RW_BUF_SIZE);
	buf2 = xmalloc(RW_BUF_SIZE);

	left = s1.st_size;
	while (left) {
		loff_t now = min(left, (loff_t)RW_BUF_SIZE);

		ret = read_full(fd1, buf1, now);
		if (ret < 0)
			goto err_out3;

		ret = read_full(fd2, buf2, now);
		if (ret < 0)
			goto err_out3;

		if (memcmp(buf1, buf2, now)) {
			ret = 1;
			goto err_out3;
		}

		left -= now;
	}

	ret = 0;

err_out3:
	free(buf1);
	free(buf2);
err_out2:
	close(fd2);
err_out1:
	close(fd1);
	return ret;
}

/**
 * open_and_lseek - open file and lseek to position
 * @filename:	The file to open
 * @mode:	The file open mode
 * @pos:	The position to lseek to
 *
 * Return: If successful this function returns a positive
 *         filedescriptor number, otherwise -1 is returned
 */
int open_and_lseek(const char *filename, int mode, loff_t pos)
{
	int fd;

	fd = open(filename, mode);
	if (fd < 0) {
		perror("open");
		return fd;
	}

	if (!pos)
		return fd;

	if (mode & (O_WRONLY | O_RDWR)) {
		struct stat s;

		if (fstat(fd, &s)) {
			perror("fstat");
			goto out;
		}

		if (s.st_size < pos && ftruncate(fd, pos)) {
			perror("ftruncate");
			goto out;
		}
	}

	if (lseek(fd, pos, SEEK_SET) != pos) {
		perror("lseek");
		goto out;
	}

	return fd;
out:
	close(fd);
	return -1;
}

/**
 * make_temp - create a name for a temporary file
 * @template:	The filename prefix
 *
 * This function creates a name for a temporary file. @template is used as a
 * template for the name which gets appended a 8-digit hexadecimal number to
 * create a unique filename.
 *
 * Return: This function returns a filename which can be used as a temporary
 *         file later on. The returned filename must be freed by the caller.
 */
char *make_temp(const char *template)
{
	char *name = NULL;
	struct stat s;
	int ret;

	do {
		free(name);
		name = basprintf("/tmp/%s-%08x", template, random32());
		ret = stat(name, &s);
	} while (!ret);

	return name;
}

/**
 * cache_file - Cache a file in /tmp
 * @path:	The file to cache
 * @newpath:	The return path where the file is copied to
 *
 * This function copies a given file to /tmp and returns its name in @newpath.
 * @newpath is dynamically allocated and must be freed by the caller.
 *
 * Return: 0 for success, negative error code otherwise.
 */
int cache_file(const char *path, char **newpath)
{
	char *npath;
	int ret;

	npath = make_temp("filecache");

	ret = copy_file(path, npath, 0);
	if (ret) {
		free(npath);
		return ret;
	}

	*newpath = npath;

	return 0;
}
