/*
 * DF-2633 phase 2 — exercised once the PFS is at/over the ENOSPC wall:
 * creates, tail write, mmap write, fsync rc's.  Prints KEY=VALUE lines.
 */
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static uint64_t
xs64(uint64_t *s)
{
	uint64_t x = *s;
	x ^= x << 13; x ^= x >> 7; x ^= x << 17;
	*s = x;
	return x;
}

static void
fillbuf(uint64_t seed, uint8_t *buf, size_t n)
{
	size_t i;
	for (i = 0; i < n; i += 8) {
		uint64_t r = xs64(&seed);
		memcpy(buf + i, &r, (n - i >= 8) ? 8 : n - i);
	}
}

static sigjmp_buf bus_jmp;
static volatile sig_atomic_t got_bus;
static void
bus_handler(int sig)
{
	(void)sig;
	got_bus = 1;
	siglongjmp(bus_jmp, 1);
}

#define NCREATE 500
#define TAILSZ (128 * 4096)
#define MMSZ (1 << 20)

int
main(int argc, char **argv)
{
	const char *mp, *ctrl;
	char path[1024];
	uint8_t *tbuf;
	int fd, rc, i;
	long ok, fail;
	struct stat sb;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <mountpoint> <controldir>\n", argv[0]);
		return 2;
	}
	mp = argv[1];
	ctrl = argv[2];
	setvbuf(stdout, NULL, _IONBF, 0);
	tbuf = malloc(TAILSZ > MMSZ ? TAILSZ : MMSZ);

	/* creates */
	ok = fail = 0;
	for (i = 0; i < NCREATE; ++i) {
		snprintf(path, sizeof(path), "%s/c%04d", mp, i);
		rc = open(path, O_CREAT | O_EXCL | O_WRONLY, 0644);
		if (rc < 0)
			++fail;
		else {
			++ok;
			close(rc);
		}
	}
	{
		long exist = 0;
		for (i = 0; i < NCREATE; ++i) {
			snprintf(path, sizeof(path), "%s/c%04d", mp, i);
			if (stat(path, &sb) == 0)
				++exist;
		}
		printf("REGIME_CREATES rc_ok=%ld rc_fail=%ld exist_now=%ld\n",
		    ok, fail, exist);
	}

	/* small single-block write to existing fill.bin (append 4KB) */
	snprintf(path, sizeof(path), "%s/fill.bin", mp);
	fd = open(path, O_WRONLY | O_APPEND);
	if (fd < 0) {
		printf("APPEND_OPEN_FAIL errno=%d (%s)\n", errno, strerror(errno));
	} else {
		fillbuf(0x1111, tbuf, 4096);
		rc = write(fd, tbuf, 4096);
		printf("APPEND_WRITE rc=%d errno=%d (%s)\n", rc, errno,
		    rc < 0 ? strerror(errno) : "ok");
		errno = 0;
		rc = fsync(fd);
		printf("APPEND_FSYNC rc=%d errno=%d (%s)\n", rc, errno,
		    rc ? strerror(errno) : "ok");
		close(fd);
	}

	/* tail */
	fillbuf(0x5EEDAB1E, tbuf, TAILSZ);
	snprintf(path, sizeof(path), "%s/tail.control", ctrl);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd >= 0) {
		size_t off = 0;
		int crc = 0;
		while (off < TAILSZ) {
			ssize_t r = write(fd, tbuf + off, TAILSZ - off);
			if (r < 0) { crc = -1; break; }
			off += r;
		}
		printf("TAIL_CONTROL rc=%d\n", crc);
		fsync(fd);
		close(fd);
	}
	snprintf(path, sizeof(path), "%s/tail.bin", mp);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd < 0) {
		printf("TAIL_OPEN_FAIL errno=%d (%s)\n", errno, strerror(errno));
	} else {
		ok = fail = 0;
		for (i = 0; i < TAILSZ / 4096; ++i) {
			rc = write(fd, tbuf + i * 4096, 4096);
			if (rc < 0) ++fail; else ++ok;
		}
		printf("TAIL_WRITES ok=%ld fail=%ld\n", ok, fail);
		errno = 0;
		rc = fsync(fd);
		printf("TAIL_FSYNC rc=%d errno=%d (%s)\n", rc, errno,
		    rc ? strerror(errno) : "ok");
		errno = 0;
		rc = close(fd);
		printf("TAIL_CLOSE rc=%d errno=%d\n", rc, errno);
	}

	/* mmap */
	fillbuf(0xAB1E5EED, tbuf, MMSZ);
	snprintf(path, sizeof(path), "%s/mm.control", ctrl);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd >= 0) {
		size_t off = 0;
		int crc = 0;
		while (off < MMSZ) {
			ssize_t r = write(fd, tbuf + off, MMSZ - off);
			if (r < 0) { crc = -1; break; }
			off += r;
		}
		printf("MMAP_CONTROL rc=%d\n", crc);
		fsync(fd);
		close(fd);
	}
	snprintf(path, sizeof(path), "%s/mm.bin", mp);
	fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644);
	if (fd < 0) {
		printf("MMAP_OPEN_FAIL errno=%d (%s)\n", errno, strerror(errno));
	} else {
		if (ftruncate(fd, MMSZ) < 0) {
			printf("MMAP_FTRUNCATE_FAIL errno=%d (%s)\n",
			    errno, strerror(errno));
		} else {
			uint8_t *m = mmap(NULL, MMSZ, PROT_READ | PROT_WRITE,
			    MAP_SHARED, fd, 0);
			if (m == MAP_FAILED) {
				printf("MMAP_MAP_FAIL errno=%d (%s)\n",
				    errno, strerror(errno));
			} else {
				got_bus = 0;
				signal(SIGBUS, bus_handler);
				if (sigsetjmp(bus_jmp, 1) == 0) {
					memcpy(m, tbuf, MMSZ);
					printf("MMAP_STORES ok=1\n");
				} else {
					printf("MMAP_STORES sigbus=1\n");
				}
				errno = 0;
				rc = msync(m, MMSZ, MS_SYNC);
				printf("MMAP_MSYNC rc=%d errno=%d (%s) sigbus=%d\n",
				    rc, errno, rc ? strerror(errno) : "ok",
				    got_bus);
				munmap(m, MMSZ);
				signal(SIGBUS, SIG_DFL);
			}
		}
		errno = 0;
		rc = close(fd);
		printf("MMAP_CLOSE rc=%d errno=%d\n", rc, errno);
	}

	printf("PHASE2_DONE\n");
	return 0;
}
