#define _POSIX_C_SOURCE 200809L

#include <sys/stat.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int
parse_errno(const char *text)
{
	char *end;
	long value;

	errno = 0;
	value = strtol(text, &end, 0);
	if (errno != 0 || *text == '\0' || *end != '\0' || value <= 0 ||
	    value > 255)
		errx(2, "invalid errno: %s", text);
	return ((int)value);
}

static int
run_operation(const char *operation, const char *path)
{
	struct stat sb;
	int fd;

	if (strcmp(operation, "stat") == 0)
		return (stat(path, &sb));
	if (strcmp(operation, "lstat") == 0)
		return (lstat(path, &sb));
	if (strcmp(operation, "open-read") == 0) {
		fd = open(path, O_RDONLY);
		if (fd < 0)
			return (-1);
		return (close(fd));
	}
	if (strcmp(operation, "open-rdwr") == 0) {
		fd = open(path, O_RDWR);
		if (fd < 0)
			return (-1);
		return (close(fd));
	}
	if (strcmp(operation, "access-read") == 0)
		return (access(path, R_OK));
	if (strcmp(operation, "access-write") == 0)
		return (access(path, W_OK));
	if (strcmp(operation, "access-exec") == 0)
		return (access(path, X_OK));
	if (strcmp(operation, "chmod") == 0)
		return (chmod(path, 0777));
	if (strcmp(operation, "chown") == 0)
		return (chown(path, 65534, 65534));
	if (strcmp(operation, "truncate") == 0)
		return (truncate(path, 0));
	if (strcmp(operation, "create") == 0) {
		fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
		if (fd < 0)
			return (-1);
		if (close(fd) != 0)
			return (-1);
		return (unlink(path));
	}
	errx(2, "unknown operation: %s", operation);
}

static void
expect_result(const char *operation, const char *path, int expected_errno)
{
	int error, result;

	errno = 0;
	result = run_operation(operation, path);
	error = errno;
	if (expected_errno == 0) {
		if (result != 0)
			errx(1, "%s %s returned errno %d (%s)", operation, path,
			    error, strerror(error));
		printf("op=%s result=success errno=0\n", operation);
		return;
	}
	if (result == 0)
		errx(1, "%s %s unexpectedly succeeded", operation, path);
	if (error != expected_errno)
		errx(1, "%s %s returned errno %d (%s), expected %d", operation,
		    path, error, strerror(error), expected_errno);
	printf("op=%s result=error errno=%d message=%s\n", operation,
	    error, strerror(error));
}

static long
checked_pathconf(const char *path, int name, const char *label, int *errorp)
{
	long value;

	errno = 0;
	value = pathconf(path, name);
	*errorp = value == -1 ? errno : 0;
	if (*errorp != 0)
		printf("%s=error:%d ", label, *errorp);
	else
		printf("%s=%ld ", label, value);
	return (value);
}

static void
show_pathconf(const char *path)
{
	int chown_error, filesizebits_error, link_error, name_error,
	    no_trunc_error, path_error;
	long chown_restricted, filesizebits, link_max, name_max, no_trunc,
	    path_max;

	name_max = checked_pathconf(path, _PC_NAME_MAX, "name_max",
	    &name_error);
	path_max = checked_pathconf(path, _PC_PATH_MAX, "path_max",
	    &path_error);
	filesizebits = checked_pathconf(path, _PC_FILESIZEBITS, "filesizebits",
	    &filesizebits_error);
	link_max = checked_pathconf(path, _PC_LINK_MAX, "link_max",
	    &link_error);
	no_trunc = checked_pathconf(path, _PC_NO_TRUNC, "no_trunc",
	    &no_trunc_error);
	chown_restricted = checked_pathconf(path, _PC_CHOWN_RESTRICTED,
	    "chown_restricted", &chown_error);
	putchar('\n');
	if (name_error != 0 || path_error != 0 || filesizebits_error != 0 ||
	    link_error != 0 || no_trunc_error != 0 || chown_error != 0 ||
	    name_max != 255 || path_max <= 0 || filesizebits != 64 ||
	    link_max <= 0 || no_trunc != 1 || chown_restricted != 1)
		errx(1, "unexpected EROFS pathconf values");
}

static void
show_readlink(const char *path)
{
	unsigned char target[4096];
	uint64_t hash;
	ssize_t length;

	length = readlink(path, (char *)target, sizeof(target));
	if (length < 0)
		err(1, "readlink %s", path);
	if ((size_t)length == sizeof(target))
		errx(1, "symlink target is too long");
	hash = UINT64_C(14695981039346656037);
	for (ssize_t index = 0; index < length; index++) {
		hash ^= target[index];
		hash *= UINT64_C(1099511628211);
	}
	target[length] = '\0';
	printf("length=%zd fnv1a64=%016" PRIx64 " target=%s\n", length,
	    hash, target);
}

static void
show_stat(const char *path)
{
	struct stat sb;

	if (lstat(path, &sb) != 0)
		err(1, "lstat %s", path);
	printf("ino=%ju mode=%#jo uid=%ju gid=%ju nlink=%ju size=%jd "
	    "blocks=%jd blksize=%jd",
	    (uintmax_t)sb.st_ino, (uintmax_t)sb.st_mode,
	    (uintmax_t)sb.st_uid, (uintmax_t)sb.st_gid,
	    (uintmax_t)sb.st_nlink, (intmax_t)sb.st_size,
	    (intmax_t)sb.st_blocks, (intmax_t)sb.st_blksize);
#ifdef __FreeBSD__
	printf(" gen=%ju", (uintmax_t)sb.st_gen);
#endif
	putchar('\n');
}

int
main(int argc, char **argv)
{
	if (argc == 5 && strcmp(argv[1], "expect-error") == 0) {
		expect_result(argv[2], argv[3], parse_errno(argv[4]));
		return (0);
	}
	if (argc == 4 && strcmp(argv[1], "expect-success") == 0) {
		expect_result(argv[2], argv[3], 0);
		return (0);
	}
	if (argc == 3 && strcmp(argv[1], "pathconf") == 0) {
		show_pathconf(argv[2]);
		return (0);
	}
	if (argc == 3 && strcmp(argv[1], "readlink") == 0) {
		show_readlink(argv[2]);
		return (0);
	}
	if (argc == 3 && strcmp(argv[1], "stat") == 0) {
		show_stat(argv[2]);
		return (0);
	}
	errx(2, "usage: %s expect-error OP PATH ERRNO | "
	    "expect-success OP PATH | pathconf PATH | readlink PATH | stat PATH",
	    argv[0]);
}
