#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <err.h>
#include <dirent.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 uint64_t
parse_u64(const char *text)
{
	char *end;
	uintmax_t value;

	errno = 0;
	value = strtoumax(text, &end, 0);
	if (errno != 0 || *text == '\0' || *end != '\0' || value > UINT64_MAX)
		errx(2, "invalid integer: %s", text);
	return ((uint64_t)value);
}

static void
stat_blocks(const char *path, const char *expected_text)
{
	fhandle_t handle;
	struct stat direct, by_handle;
	uint64_t expected;

	expected = parse_u64(expected_text);
	if (stat(path, &direct) != 0)
		err(1, "stat %s", path);
	if (getfh(path, &handle) != 0)
		err(1, "getfh %s", path);
	if (fhstat(&handle, &by_handle) != 0)
		err(1, "fhstat %s", path);
	if ((uint64_t)direct.st_blocks != expected ||
	    (uint64_t)by_handle.st_blocks != expected)
		errx(1, "st_blocks direct=%jd handle=%jd expected=%" PRIu64,
		    (intmax_t)direct.st_blocks, (intmax_t)by_handle.st_blocks,
		    expected);
	printf("direct_blocks=%jd handle_blocks=%jd size=%jd expected=%" PRIu64
	    "\n", (intmax_t)direct.st_blocks, (intmax_t)by_handle.st_blocks,
	    (intmax_t)direct.st_size, expected);
}

static void
expect_stat_error(const char *path, const char *expected_text)
{
	struct stat status;
	int expected;

	expected = (int)parse_u64(expected_text);
	errno = 0;
	if (stat(path, &status) != -1)
		errx(1, "stat unexpectedly succeeded: %s", path);
	if (errno != expected)
		errx(1, "stat errno=%d expected=%d", errno, expected);
	printf("stat_errno=%d path=%s\n", errno, path);
}

static void
readdir_offmax(const char *path, const char *expected_text)
{
	char buffer[512];
	off_t base, current;
	ssize_t bytes;
	int descriptor, expected;

	expected = (int)parse_u64(expected_text);
	descriptor = open(path, O_RDONLY | O_DIRECTORY);
	if (descriptor < 0)
		err(1, "open %s", path);
	if (lseek(descriptor, OFF_MAX, SEEK_SET) != OFF_MAX)
		err(1, "lseek OFF_MAX");
	base = 0;
	errno = 0;
	bytes = getdirentries(descriptor, buffer, sizeof(buffer), &base);
	if (bytes != -1 || errno != expected)
		errx(1, "OFF_MAX getdirentries bytes=%zd errno=%d expected=%d",
		    bytes, errno, expected);
	current = lseek(descriptor, 0, SEEK_CUR);
	if (current < 0 || current != OFF_MAX)
		errx(1, "negative or changed post-error offset: %jd",
		    (intmax_t)current);
	if (lseek(descriptor, 0, SEEK_SET) != 0)
		err(1, "lseek zero");
	base = 0;
	errno = 0;
	bytes = getdirentries(descriptor, buffer, sizeof(buffer), &base);
	if (bytes != -1 || errno != expected)
		errx(1, "zero-offset getdirentries bytes=%zd errno=%d expected=%d",
		    bytes, errno, expected);
	current = lseek(descriptor, 0, SEEK_CUR);
	if (current < 0 || current != 0)
		errx(1, "negative or changed zero offset: %jd", (intmax_t)current);
	if (close(descriptor) != 0)
		err(1, "close %s", path);
	printf("offmax_errno=%d offmax_offset=%jd zero_errno=%d zero_offset=%jd\n",
	    expected, (intmax_t)OFF_MAX, expected, (intmax_t)current);
}

int
main(int argc, char **argv)
{
	if (argc == 4 && strcmp(argv[1], "stat-blocks") == 0) {
		stat_blocks(argv[2], argv[3]);
		return (0);
	}
	if (argc == 4 && strcmp(argv[1], "expect-stat-error") == 0) {
		expect_stat_error(argv[2], argv[3]);
		return (0);
	}
	if (argc == 4 && strcmp(argv[1], "readdir-offmax") == 0) {
		readdir_offmax(argv[2], argv[3]);
		return (0);
	}
	errx(2, "usage: %s stat-blocks PATH EXPECTED | "
	    "expect-stat-error PATH ERRNO | readdir-offmax PATH ERRNO", argv[0]);
}
