/****************************************************************************
 * bfs                                                                      *
 * Copyright (C) 2016-2022 Tavian Barnes <tavianator@tavianator.com>        *
 *                                                                          *
 * Permission to use, copy, modify, and/or distribute this software for any *
 * purpose with or without fee is hereby granted.                           *
 *                                                                          *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF         *
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR  *
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES   *
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN    *
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF  *
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.           *
 ****************************************************************************/

#include "util.h"
#include "dstring.h"
#include "xregex.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <langinfo.h>
#include <nl_types.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <wchar.h>

#if BFS_HAS_SYS_PARAM
#	include <sys/param.h>
#endif

#if BFS_HAS_SYS_SYSMACROS
#	include <sys/sysmacros.h>
#elif BFS_HAS_SYS_MKDEV
#	include <sys/mkdev.h>
#endif

#if BFS_HAS_UTIL
#	include <util.h>
#endif

char *xreadlinkat(int fd, const char *path, size_t size) {
	ssize_t len;
	char *name = NULL;

	if (size == 0) {
		size = 64;
	} else {
		++size; // NUL terminator
	}

	while (true) {
		char *new_name = realloc(name, size);
		if (!new_name) {
			goto error;
		}
		name = new_name;

		len = readlinkat(fd, path, name, size);
		if (len < 0) {
			goto error;
		} else if ((size_t)len >= size) {
			size *= 2;
		} else {
			break;
		}
	}

	name[len] = '\0';
	return name;

error:
	free(name);
	return NULL;
}

int dup_cloexec(int fd) {
#ifdef F_DUPFD_CLOEXEC
	return fcntl(fd, F_DUPFD_CLOEXEC, 0);
#else
	int ret = dup(fd);
	if (ret < 0) {
		return -1;
	}

	if (fcntl(ret, F_SETFD, FD_CLOEXEC) == -1) {
		close_quietly(ret);
		return -1;
	}

	return ret;
#endif
}

int pipe_cloexec(int pipefd[2]) {
#if __linux__ || (BSD && !__APPLE__)
	return pipe2(pipefd, O_CLOEXEC);
#else
	if (pipe(pipefd) != 0) {
		return -1;
	}

	if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1) {
		close_quietly(pipefd[1]);
		close_quietly(pipefd[0]);
		return -1;
	}

	return 0;
#endif
}

/** Get the single character describing the given file type. */
static char type_char(mode_t mode) {
	switch (mode & S_IFMT) {
	case S_IFREG:
		return '-';
	case S_IFBLK:
		return 'b';
	case S_IFCHR:
		return 'c';
	case S_IFDIR:
		return 'd';
	case S_IFLNK:
		return 'l';
	case S_IFIFO:
		return 'p';
	case S_IFSOCK:
		return 's';
#ifdef S_IFDOOR
	case S_IFDOOR:
		return 'D';
#endif
#ifdef S_IFPORT
	case S_IFPORT:
		return 'P';
#endif
#ifdef S_IFWHT
	case S_IFWHT:
		return 'w';
#endif
	}

	return '?';
}

void xstrmode(mode_t mode, char str[11]) {
	strcpy(str, "----------");

	str[0] = type_char(mode);

	if (mode & 00400) {
		str[1] = 'r';
	}
	if (mode & 00200) {
		str[2] = 'w';
	}
	if ((mode & 04100) == 04000) {
		str[3] = 'S';
	} else if (mode & 04000) {
		str[3] = 's';
	} else if (mode & 00100) {
		str[3] = 'x';
	}

	if (mode & 00040) {
		str[4] = 'r';
	}
	if (mode & 00020) {
		str[5] = 'w';
	}
	if ((mode & 02010) == 02000) {
		str[6] = 'S';
	} else if (mode & 02000) {
		str[6] = 's';
	} else if (mode & 00010) {
		str[6] = 'x';
	}

	if (mode & 00004) {
		str[7] = 'r';
	}
	if (mode & 00002) {
		str[8] = 'w';
	}
	if ((mode & 01001) == 01000) {
		str[9] = 'T';
	} else if (mode & 01000) {
		str[9] = 't';
	} else if (mode & 00001) {
		str[9] = 'x';
	}
}

const char *xbasename(const char *path) {
	const char *i;

	// Skip trailing slashes
	for (i = path + strlen(path); i > path && i[-1] == '/'; --i);

	// Find the beginning of the name
	for (; i > path && i[-1] != '/'; --i);

	// Skip leading slashes
	for (; i[0] == '/' && i[1]; ++i);

	return i;
}

int xfaccessat(int fd, const char *path, int amode) {
	int ret = faccessat(fd, path, amode, 0);

#ifdef AT_EACCESS
	// Some platforms, like Hurd, only support AT_EACCESS.  Other platforms,
	// like Android, don't support AT_EACCESS at all.
	if (ret != 0 && (errno == EINVAL || errno == ENOTSUP)) {
		ret = faccessat(fd, path, amode, AT_EACCESS);
	}
#endif

	return ret;
}

int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear) {
#if BSD && !__GNU__
	char *str_arg = (char *)*str;
	unsigned long set_arg = 0;
	unsigned long clear_arg = 0;

#if __NetBSD__
	int ret = string_to_flags(&str_arg, &set_arg, &clear_arg);
#else
	int ret = strtofflags(&str_arg, &set_arg, &clear_arg);
#endif

	*str = str_arg;
	*set = set_arg;
	*clear = clear_arg;

	if (ret != 0) {
		errno = EINVAL;
	}
	return ret;
#else // !BSD
	errno = ENOTSUP;
	return -1;
#endif
}

size_t xstrwidth(const char *str) {
	size_t len = strlen(str);
	size_t ret = 0;

	mbstate_t mb;
	memset(&mb, 0, sizeof(mb));

	while (len > 0) {
		wchar_t wc;
		size_t mblen = mbrtowc(&wc, str, len, &mb);
		int cwidth;
		if (mblen == (size_t)-1) {
			// Invalid byte sequence, assume a single-width '?'
			mblen = 1;
			cwidth = 1;
			memset(&mb, 0, sizeof(mb));
		} else if (mblen == (size_t)-2) {
			// Incomplete byte sequence, assume a single-width '?'
			mblen = len;
			cwidth = 1;
		} else {
			cwidth = wcwidth(wc);
			if (cwidth < 0) {
				cwidth = 0;
			}
		}

		str += mblen;
		len -= mblen;
		ret += cwidth;
	}

	return ret;
}

bool is_nonexistence_error(int error) {
	return error == ENOENT || errno == ENOTDIR;
}

/** Compile and execute a regular expression for xrpmatch(). */
static int xrpregex(nl_item item, const char *response) {
	const char *pattern = nl_langinfo(item);
	if (!pattern) {
		return -1;
	}

	struct bfs_regex *regex;
	int ret = bfs_regcomp(&regex, pattern, BFS_REGEX_POSIX_EXTENDED, 0);
	if (ret == 0) {
		ret = bfs_regexec(regex, response, 0);
	}

	bfs_regfree(regex);
	return ret;
}

/** Check if a response is affirmative or negative. */
static int xrpmatch(const char *response) {
	int ret = xrpregex(NOEXPR, response);
	if (ret > 0) {
		return 0;
	} else if (ret < 0) {
		return -1;
	}

	ret = xrpregex(YESEXPR, response);
	if (ret > 0) {
		return 1;
	} else if (ret < 0) {
		return -1;
	}

	// Failsafe: always handle y/n
	char c = response[0];
	if (c == 'n' || c == 'N') {
		return 0;
	} else if (c == 'y' || c == 'Y') {
		return 1;
	} else {
		return -1;
	}
}

int ynprompt(void) {
	fflush(stderr);

	char *line = xgetdelim(stdin, '\n');
	int ret = line ? xrpmatch(line) : -1;
	free(line);
	return ret;
}

dev_t bfs_makedev(int ma, int mi) {
#ifdef makedev
	return makedev(ma, mi);
#else
	return (ma << 8) | mi;
#endif
}

int bfs_major(dev_t dev) {
#ifdef major
	return major(dev);
#else
	return dev >> 8;
#endif
}

int bfs_minor(dev_t dev) {
#ifdef minor
	return minor(dev);
#else
	return dev & 0xFF;
#endif
}

size_t xread(int fd, void *buf, size_t nbytes) {
	size_t count = 0;

	while (count < nbytes) {
		ssize_t ret = read(fd, (char *)buf + count, nbytes - count);
		if (ret < 0) {
			if (errno == EINTR) {
				continue;
			} else {
				break;
			}
		} else if (ret == 0) {
			// EOF
			errno = 0;
			break;
		} else {
			count += ret;
		}
	}

	return count;
}

size_t xwrite(int fd, const void *buf, size_t nbytes) {
	size_t count = 0;

	while (count < nbytes) {
		ssize_t ret = write(fd, (const char *)buf + count, nbytes - count);
		if (ret < 0) {
			if (errno == EINTR) {
				continue;
			} else {
				break;
			}
		} else if (ret == 0) {
			// EOF?
			errno = 0;
			break;
		} else {
			count += ret;
		}
	}

	return count;
}

char *xconfstr(int name) {
	size_t len = confstr(name, NULL, 0);
	if (len == 0) {
		return NULL;
	}

	char *str = malloc(len);
	if (!str) {
		return NULL;
	}

	if (confstr(name, str, len) != len) {
		free(str);
		return NULL;
	}

	return str;
}

char *xgetdelim(FILE *file, char delim) {
	char *chunk = NULL;
	size_t n = 0;
	ssize_t len = getdelim(&chunk, &n, delim, file);
	if (len >= 0) {
		if (chunk[len] == delim) {
			chunk[len] = '\0';
		}
		return chunk;
	} else {
		free(chunk);
		if (!ferror(file)) {
			errno = 0;
		}
		return NULL;
	}
}

FILE *xfopen(const char *path, int flags) {
	char mode[4];

	switch (flags & O_ACCMODE) {
	case O_RDONLY:
		strcpy(mode, "rb");
		break;
	case O_WRONLY:
		strcpy(mode, "wb");
		break;
	case O_RDWR:
		strcpy(mode, "r+b");
		break;
	default:
		assert(!"Invalid access mode");
		errno = EINVAL;
		return NULL;
	}

	if (flags & O_APPEND) {
		mode[0] = 'a';
	}

	int fd;
	if (flags & O_CREAT) {
		fd = open(path, flags, 0666);
	} else {
		fd = open(path, flags);
	}

	if (fd < 0) {
		return NULL;
	}

	FILE *ret = fdopen(fd, mode);
	if (!ret) {
		close_quietly(fd);
		return NULL;
	}

	return ret;
}

int xclose(int fd) {
	int ret = close(fd);
	if (ret != 0) {
		assert(errno != EBADF);
	}
	return ret;
}

void close_quietly(int fd) {
	int error = errno;
	xclose(fd);
	errno = error;
}