/* SPDX-License-Identifier: GPL-3.0-only */

#include "tf.h"

DEFINE_TF_ERRLIST;

int atoi64(const char *str, int64_t *value)
{
	char *endptr;
	long long tmp;

	errno = 0; /* To distinguish success/failure after call */
	tmp = strtoll(str, &endptr, 10);
	if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN))
		return -E_ATOI_OVERFLOW;
	/*
	 * If there were no digits at all, strtoll() stores the original value
	 * of str in *endptr.
	 */
	if (endptr == str)
		return -E_ATOI_NO_DIGITS;
	/*
	 * The implementation may also set errno and return 0 in case no
	 * conversion was performed.
	 */
	if (errno != 0 && tmp == 0)
		return -E_ATOI_NO_DIGITS;
	if (*endptr != '\0') /* Further characters after number */
		return -E_ATOI_JUNK_AT_END;
	*value = tmp;
	return 1;
}

__attribute__ ((warn_unused_result))
void *xrealloc(void *p, size_t size)
{
	/*
	 * No need to check for NULL pointers: If p is NULL, the call
	 * to realloc is equivalent to malloc(size)
	 */
	assert(size);
	if (!(p = realloc(p, size))) {
		EMERG_LOG("realloc failed (size = %zu), aborting\n", size);
		exit(EXIT_FAILURE);
	}
	return p;
}

__attribute__ ((warn_unused_result))
void *xmalloc(size_t size)
{
	return xrealloc(NULL, size);
}

__attribute__ ((warn_unused_result))
void *xcalloc(size_t size)
{
	void *p = xmalloc(size);
	memset(p, 0, size);
	return p;
}

__attribute__ ((warn_unused_result))
char *xstrdup(const char *str)
{
	char *p = strdup(str);

	if (p)
		return p;
	EMERG_LOG("strdup failed, aborting\n");
	exit(EXIT_FAILURE);
}

/*
 * Get the home directory of the current user. Returns a dynamically allocated
 * string that must be freed by the caller.
 */
__attribute__ ((warn_unused_result))
char *get_homedir(void)
{
	struct passwd *pw = getpwuid(getuid());

	if (!pw) {
		EMERG_LOG("could not get home directory: %s\n",
			strerror(errno));
		exit(EXIT_FAILURE);
	}
	return xstrdup(pw->pw_dir);
}

/*
 * Print a formated message to a dynamically allocated string.
 *
 * This function is similar to vasprintf(), a GNU extension which is not in C
 * or POSIX. It allocates a string large enough to hold the output including
 * the terminating null byte. The allocated string is returned via the first
 * argument and must be freed by the caller. However, unlike vasprintf(), this
 * function calls exit() if insufficient memory is available, while vasprintf()
 * returns -1 in this case.
 *
 * It returns the number of bytes written, not including the terminating \p
 * NULL character.
 */
__attribute__ ((format (printf, 2, 0)))
unsigned xvasprintf(char **result, const char *fmt, va_list ap)
{
	int ret;
	size_t size = 150;
	va_list aq;

	*result = xmalloc(size + 1);
	va_copy(aq, ap);
	ret = vsnprintf(*result, size, fmt, aq);
	va_end(aq);
	assert(ret >= 0);
	if ((size_t)ret < size)
		return ret;
	size = ret + 1;
	*result = xrealloc(*result, size);
	va_copy(aq, ap);
	ret = vsnprintf(*result, size, fmt, aq);
	va_end(aq);
	assert(ret >= 0 && (size_t)ret < size);
	return ret;
}

__attribute__ ((format (printf, 2, 3)))
/* Print to a dynamically allocated string, variable number of arguments.  */
unsigned xasprintf(char **result, const char *fmt, ...)
{
	va_list ap;
	unsigned ret;

	va_start(ap, fmt);
	ret = xvasprintf(result, fmt, ap);
	va_end(ap);
	return ret;
}

/*
 * Compile a regular expression.
 *
 * This simple wrapper calls regcomp(3) and logs a message on errors.
 */
int xregcomp(regex_t *preg, const char *regex, int cflags)
{
	char *buf;
	size_t size;
	int ret = regcomp(preg, regex, cflags);

	if (ret == 0)
		return 1;
	size = regerror(ret, preg, NULL, 0);
	buf = xmalloc(size);
	regerror(ret, preg, buf, size);
	ERROR_LOG("%s\n", buf);
	free(buf);
	return -E_REGEX;
}

/* Write input from fd to dynamically allocated buffer. */
int fd2buf(int fd, struct iovec *result)
{
	const size_t max_size = 32 * 1024;
	size_t loaded = 0;
	int ret;
	struct iovec iov;

	iov.iov_len = 100; /* guess that's sufficient */
	iov.iov_base = xmalloc(iov.iov_len);
	for (;;) {
		ret = read(fd, iov.iov_base + loaded,  iov.iov_len - loaded);
		if (ret < 0)
			ret = -ERRNO_TO_TF_ERROR(errno);
		if (ret <= 0)
			break;
		loaded += ret;
		if (loaded >= iov.iov_len) {
			iov.iov_len *= 2;
			ret = -ERRNO_TO_TF_ERROR(EOVERFLOW);
			if (iov.iov_len >= max_size)
				break;
			iov.iov_base = xrealloc(iov.iov_base, iov.iov_len);
		}
	};
	if (ret < 0) {
		free(iov.iov_base);
		result->iov_base = NULL;
		result->iov_len = 0;
		return ret;
	}
	iov.iov_len = loaded;
	iov.iov_base = xrealloc(iov.iov_base, loaded + 1);
	*((char *)iov.iov_base + loaded) = '\0';
	*result = iov;
	return 1;
}

__attribute__ ((format (printf, 2, 3)))
void tf_log(int ll, const char* fmt,...)
{
	va_list argp;
	if (ll < loglevel_arg_val)
		return;
	va_start(argp, fmt);
	vfprintf(stderr, fmt, argp);
	va_end(argp);
}

struct regfile_iter {
	DIR *dir;
	int dfd;
	struct dirent *entry;
	struct stat stat;
};

void regfile_iter_next(struct regfile_iter *iter)
{
	for (;;) {
		iter->entry = readdir(iter->dir);
		if (!iter->entry)
			return;
		if (!strcmp(iter->entry->d_name, "."))
			continue;
		if (!strcmp(iter->entry->d_name, ".."))
			continue;
		if (fstatat(iter->dfd, iter->entry->d_name, &iter->stat, 0) == -1)
			continue;
		if (!S_ISREG(iter->stat.st_mode))
			continue;
		if (iter->stat.st_size == 0) /* skip over empty files */
			continue;
		return;
	}
}

void regfile_iter_new(const char *dirname, struct regfile_iter **result)
{
	struct regfile_iter *iter;
	DIR *dir = opendir(dirname);

	if (!dir) {
		NOTICE_LOG("opendir %s: %s\n", dirname, strerror(errno));
		*result = NULL;
		return;
	}
	iter = xmalloc(sizeof(*iter));
	iter->dir = dir;
	iter->dfd = dirfd(iter->dir);
	assert(iter->dfd >= 0);
	regfile_iter_next(iter);
	*result = iter;
}

static void *xmmap(size_t sz, int fd, const char *path)
{
	void *result = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);

	if (result == MAP_FAILED) {
		EMERG_LOG("mmap %s: %s\n", path, strerror(errno));
		exit(EXIT_FAILURE);
	}
	return result;
}

void mmap_file(const char *path, struct iovec *iov)
{
	int fd, ret;

	ret = open(path, 0);
	if (ret < 0) {
		EMERG_LOG("could not open %s: %s\n", path, strerror(errno));
		exit(EXIT_FAILURE);
	}
	fd = ret;
	iov->iov_len = lseek(fd, 0, SEEK_END);
	if (iov->iov_len == (size_t)(off_t)-1) {
		EMERG_LOG("lseek %s: %s\n", path, strerror(errno));
		exit(EXIT_FAILURE);
	}
	iov->iov_base = xmmap(iov->iov_len, fd, path);
	close(fd);
}

bool regfile_iter_map(const struct regfile_iter *iter, struct iovec *result)
{
	int ret, fd;
	void *map;
	const char *path;

	if (!iter || !iter->entry)
		return false;
	path = iter->entry->d_name;
	ret = openat(iter->dfd, path, O_RDONLY, 0);
	if (ret < 0) {
		EMERG_LOG("could not open %s: %s\n", path, strerror(errno));
		exit(EXIT_FAILURE);
	}
	fd = ret;
	map = xmmap(iter->stat.st_size, fd, path);
	close(fd);
	result->iov_len = iter->stat.st_size;
	result->iov_base = map;
	return true;
}

const char *regfile_iter_basename(const struct regfile_iter *iter)
{
	if (!iter || !iter->entry)
		return NULL;
	return iter->entry->d_name;
}

const struct stat *regfile_iter_stat(const struct regfile_iter *iter)
{
	return iter? &iter->stat : NULL;
}

void regfile_iter_free(struct regfile_iter *iter)
{
	if (!iter)
		return;
	closedir(iter->dir);
	free(iter);
}
