#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>

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

static void
pread_all(int fd, unsigned char *buf, size_t len)
{
	size_t done;
	ssize_t nr;

	for (done = 0; done < len; done += nr) {
		nr = pread(fd, buf + done, len - done, done);
		if (nr < 0)
			err(1, "pread");
		if (nr == 0)
			errx(1, "unexpected EOF at %zu", done);
	}
}

static uint64_t
fnv1a64(const unsigned char *buf, size_t len)
{
	uint64_t hash;

	hash = UINT64_C(14695981039346656037);
	for (size_t i = 0; i < len; i++) {
		hash ^= buf[i];
		hash *= UINT64_C(1099511628211);
	}
	return (hash);
}

static uint32_t
xorshift32(uint32_t *state)
{
	uint32_t value;

	value = *state;
	value ^= value << 13;
	value ^= value >> 17;
	value ^= value << 5;
	*state = value;
	return (value);
}

static void
verify_random_faults(const unsigned char *map, const unsigned char *expected,
    size_t size, size_t pagesize)
{
	size_t *order;
	size_t npages, page, span;
	uint32_t state;

	npages = (size + pagesize - 1) / pagesize;
	order = calloc(npages, sizeof(*order));
	if (order == NULL)
		err(1, "calloc page order");
	for (size_t i = 0; i < npages; i++)
		order[i] = i;
	state = UINT32_C(0x5eed22);
	for (size_t i = npages; i > 1; i--) {
		size_t other;

		other = xorshift32(&state) % i;
		page = order[i - 1];
		order[i - 1] = order[other];
		order[other] = page;
	}
	for (size_t i = 0; i < npages; i++) {
		page = order[i] * pagesize;
		span = size - page < pagesize ? size - page : pagesize;
		if (memcmp(map + page, expected + page, span) != 0)
			errx(1, "random page mismatch at offset %zu", page);
	}
	free(order);
}

static void
verify_sigbus(const unsigned char *map, size_t eof_page)
{
	pid_t child;
	int status;

	child = fork();
	if (child < 0)
		err(1, "fork");
	if (child == 0) {
		volatile unsigned char value;

		value = map[eof_page];
		_exit(value == 0 ? 0 : 1);
	}
	if (waitpid(child, &status, 0) != child)
		err(1, "waitpid");
	if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGBUS)
		errx(1, "full page beyond EOF did not raise SIGBUS (status=%#x)",
		    status);
}

int
main(int argc, char **argv)
{
	unsigned char *expected, *map, *private_map, byte;
	struct stat sb;
	size_t eof_page, map_len, pagesize, private_offset;
	uint64_t hash;
	int fd, rwfd, shared_errno;

	if (argc != 2)
		errx(2, "usage: mmap_fault file");
	pagesize = (size_t)getpagesize();
	fd = open(argv[1], O_RDONLY);
	if (fd < 0)
		err(1, "open %s", argv[1]);
	if (fstat(fd, &sb) != 0)
		err(1, "fstat %s", argv[1]);
	if (sb.st_size <= (off_t)(pagesize * 3))
		errx(1, "fixture must exceed three VM pages");
	if ((uintmax_t)sb.st_size > SIZE_MAX - pagesize * 2)
		errx(1, "fixture is too large");
	eof_page = ((size_t)sb.st_size + pagesize - 1) / pagesize * pagesize;
	map_len = eof_page + pagesize;
	expected = malloc((size_t)sb.st_size);
	if (expected == NULL)
		err(1, "malloc expected data");
	pread_all(fd, expected, (size_t)sb.st_size);

	map = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
	if (map == MAP_FAILED)
		err(1, "mmap read-only private");
	if (madvise(map, map_len, MADV_DONTNEED) != 0)
		err(1, "madvise MADV_DONTNEED");
	verify_random_faults(map, expected, (size_t)sb.st_size, pagesize);
	if (memcmp(map, expected, (size_t)sb.st_size) != 0)
		errx(1, "sequential mmap data differs from pread");
	for (size_t i = (size_t)sb.st_size; i < eof_page; i++) {
		if (map[i] != 0)
			errx(1, "non-zero byte in partial EOF page at %zu", i);
	}
	verify_sigbus(map, eof_page);
	hash = fnv1a64(map, (size_t)sb.st_size);

	errno = 0;
	private_map = mmap(NULL, eof_page, PROT_READ | PROT_WRITE, MAP_SHARED,
	    fd, 0);
	shared_errno = errno;
	if (private_map != MAP_FAILED) {
		munmap(private_map, eof_page);
		errx(1, "writable MAP_SHARED unexpectedly succeeded");
	}
	if (shared_errno != EACCES)
		errx(1, "writable MAP_SHARED returned %s, expected Permission denied",
		    strerror(shared_errno));

	private_map = mmap(NULL, eof_page, PROT_READ | PROT_WRITE, MAP_PRIVATE,
	    fd, 0);
	if (private_map == MAP_FAILED)
		err(1, "mmap writable private");
	private_offset = (size_t)sb.st_size / 2;
	byte = expected[private_offset];
	private_map[private_offset] ^= UINT8_C(0xff);
	if (private_map[private_offset] == byte)
		errx(1, "private mapping did not change");
	if (pread(fd, &byte, 1, private_offset) != 1)
		err(1, "pread private verification");
	if (byte != expected[private_offset])
		errx(1, "MAP_PRIVATE modified the EROFS file");

	errno = 0;
	rwfd = open(argv[1], O_RDWR);
	if (rwfd >= 0) {
		close(rwfd);
		errx(1, "O_RDWR unexpectedly succeeded");
	}
	if (errno != EROFS)
		errx(1, "O_RDWR returned %s, expected Read-only file system",
		    strerror(errno));

	if (munmap(private_map, eof_page) != 0 || munmap(map, map_len) != 0)
		err(1, "munmap");
	free(expected);
	if (close(fd) != 0)
		err(1, "close");
	printf("PASS size=%jd pages=%zu fnv1a64=%016" PRIx64
	    " random-faults=%zu eof-zero=PASS sigbus=PASS private-cow=PASS\n",
	    (intmax_t)sb.st_size, eof_page / pagesize, hash,
	    ((size_t)sb.st_size + pagesize - 1) / pagesize);
	return (0);
}
