/*
 * DF-2642 — zero_write() drops hammer2_chain_delete() failure:
 * an all-zero 64KB block overwrite "succeeds" (write/fsync rc=0, buffer
 * completed clean) while the backend chain deletion failed (ENOSPC wall).
 * The old data chain survives in the topology, so once the clean logical
 * buffer is evicted from the buffer cache, reads return the PRE-OVERWRITE
 * content with no error ever reported.
 *
 * Stages (driven by run.sh):
 *   setup <mp> <nvictim>     create victim files: 128KB, block0/block1
 *                            filled with distinct patterns; fsync.
 *   fill <mp>                append incompressible 64KB blocks to fill.bin
 *                            until free <= 8MB (3 polls) or write fails.
 *   drip <mp>                slow single-block appends (walks the wall).
 *   overwrite <mp> <nvictim> sequentially pwrite 64KB of ZEROS over
 *                            block1 (offset 65536) of every victim,
 *                            ~30ms apart.  Records rc/errno/fsync-rc.
 *   churn <mp> <passes>      re-read fill.bin to cycle the buffer cache.
 *   hog <MB>                 allocate+touch anonymous memory (VM pressure).
 *   readback <mp> <nvictim>  pread block1 of every victim and classify:
 *                            ZEROS (correct) / OLD (stale = bug) / MIXED /
 *                            error.  Also verifies block0 pattern intact.
 *
 * Output is KEY=VALUE lines, unbuffered.
 */
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define BLSZ   65536
#define NVICT  64

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 int
writen(int fd, const uint8_t *buf, size_t n)
{
	size_t off = 0;
	while (off < n) {
		ssize_t r = write(fd, buf + off, n - off);
		if (r < 0)
			return -1;
		off += r;
	}
	return 0;
}

static int64_t
getfree(const char *mp)
{
	struct statfs st;
	if (statfs(mp, &st) < 0)
		return -1;
	return (int64_t)st.f_bavail * (int64_t)st.f_bsize;
}

static void
mkpattern(char *out, size_t len, int victim, int block)
{
	char head[32];
	int hl;
	size_t i;
	hl = snprintf(head, sizeof(head), "DF2642-V%02d-B%d-OLD-", victim, block);
	memset(out, '.', len);
	for (i = 0; i < len; i += hl)
		memcpy(out + i, head, (len - i >= (size_t)hl) ? (size_t)hl : len - i);
}

static int
do_setup(const char *mp, int nvictim)
{
	char path[1024];
	uint8_t *blk;
	int fd, i, rc;

	blk = malloc(BLSZ);
	if (!blk) { perror("malloc"); return 2; }

	for (i = 0; i < nvictim; ++i) {
		snprintf(path, sizeof(path), "%s/v%02d", mp, i);
		fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644);
		if (fd < 0) { perror("open victim"); return 2; }
		mkpattern((char *)blk, BLSZ, i, 0);
		if (writen(fd, blk, BLSZ) < 0) { perror("write b0"); return 2; }
		mkpattern((char *)blk, BLSZ, i, 1);
		if (writen(fd, blk, BLSZ) < 0) { perror("write b1"); return 2; }
		rc = fsync(fd);
		printf("SETUP_VICTIM=%d fsync=%d errno=%d\n", i, rc, errno);
		close(fd);
	}
	printf("SETUP_DONE n=%d free=%lld\n", nvictim,
	    (long long)getfree(mp));
	return 0;
}

static int
do_fill(const char *mp, long cap)
{
	char path[1024];
	uint8_t *blk;
	uint64_t seed = 0xC0FFEE123456789ULL;
	long blocks = 0, lowrun = 0;
	int fd;

	blk = malloc(BLSZ);
	if (!blk) { perror("malloc"); return 2; }
	snprintf(path, sizeof(path), "%s/fill.bin", mp);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd < 0) { perror("open fill.bin"); return 2; }

	{
		long errs = 0;
		for (;;) {
			fillbuf(seed++, blk, BLSZ);
			if (writen(fd, blk, BLSZ) < 0) {
				if (errno == ENOSPC) {
					/* keep the pressure on through the
					 * cutoff: retry ~30s */
					if (++errs > 600)
						break;
					usleep(50000);
					continue;
				}
				printf("FILL_WRITE_ERR blocks=%ld "
				    "errno=%d\n", blocks, errno);
				break;
			}
			errs = 0;
			++blocks;
		if ((blocks & 63) == 0) {
			int64_t fr = getfree(mp);
			printf("FILL blocks=%ld free=%lld\n", blocks,
			    (long long)fr);
			if (fr >= 0 && fr <= 8 * 1024 * 1024) {
				if (++lowrun >= 3)
					break;
			} else {
				lowrun = 0;
			}
		}
		if (cap && blocks >= cap)
			break;
		}
	}
	{
		int rc = fsync(fd);
		printf("FILL_DONE blocks=%ld free=%lld fsync=%d errno=%d\n",
		    blocks, (long long)getfree(mp), rc, errno);
	}
	close(fd);
	return 0;
}

static int
do_drip(const char *mp)
{
	char path[1024];
	uint8_t *blk;
	uint64_t seed = 0xDEADF00DULL;
	long blocks = 0, fails = 0;
	int fd;

	blk = malloc(BLSZ);
	if (!blk) { perror("malloc"); return 2; }
	snprintf(path, sizeof(path), "%s/fill.bin", mp);
	fd = open(path, O_WRONLY | O_APPEND, 0644);
	if (fd < 0) { perror("open fill.bin append"); return 2; }

	for (;;) {
		fillbuf(seed++, blk, BLSZ);
		if (writen(fd, blk, BLSZ) < 0) {
			printf("DRIP_ERR blocks=%ld errno=%d\n", blocks, errno);
			if (++fails >= 2)
				break;
		} else {
			fails = 0;
			++blocks;
		}
		if ((blocks & 7) == 0)
			printf("DRIP blocks=%ld free=%lld\n", blocks,
			    (long long)getfree(mp));
		usleep(100000);
	}
	printf("DRIP_DONE blocks=%ld free=%lld\n", blocks,
	    (long long)getfree(mp));
	close(fd);
	return 0;
}

/* slow walk into the wall: one 64KB append every 100ms until ENOSPC */
static int
do_fillslow(const char *mp)
{
	char path[1024];
	uint8_t *blk;
	uint64_t seed = 0x5EED5EEDULL;
	long blocks = 0, fails = 0;
	int fd;

	blk = malloc(BLSZ);
	if (!blk) { perror("malloc"); return 2; }
	snprintf(path, sizeof(path), "%s/fill.bin", mp);
	fd = open(path, O_WRONLY | O_APPEND, 0644);
	if (fd < 0) { perror("open fill.bin append"); return 2; }

	for (;;) {
		fillbuf(seed++, blk, BLSZ);
		if (writen(fd, blk, BLSZ) < 0) {
			printf("FILLSLOW_ERR blocks=%ld errno=%d free=%lld\n",
			    blocks, errno, (long long)getfree(mp));
			if (++fails >= 3)
				break;
		} else {
			fails = 0;
			++blocks;
		}
		if ((blocks & 7) == 0)
			printf("FILLSLOW blocks=%ld free=%lld\n", blocks,
			    (long long)getfree(mp));
		usleep(100000);
	}
	{
		int rc = fsync(fd);
		printf("FILLSLOW_DONE blocks=%ld free=%lld fsync=%d errno=%d\n",
		    blocks, (long long)getfree(mp), rc, errno);
	}
	close(fd);
	return 0;
}

/*
 * Adaptive overwrite:
 *   - v0: one healthy-phase control (delete succeeds -> hole -> zeros);
 *   - wait for free <= 12MB, then BLITZ: round-robin pwrite attempts
 *     over all remaining (pre-opened) victims every ~3ms, riding the
 *     descent through the reserve band where the frontend still passes
 *     while the backend allocation (the chain_delete parent COW) fails.
 */
static int
do_overwrite(const char *mp, int nvictim, long unused)
{
	char path[1024];
	uint8_t *zblk;
	int fd[NVICT];
	int done[NVICT];
	int i, r, ndone, ngiveup;

	(void)unused;
	memset(done, 0, sizeof(done));
	memset(fd, -1, sizeof(fd));
	zblk = calloc(1, BLSZ);
	if (!zblk) { perror("calloc"); return 2; }

	/* healthy control v0 */
	snprintf(path, sizeof(path), "%s/v%02d", mp, 0);
	fd[0] = open(path, O_RDWR, 0644);
	if (fd[0] >= 0) {
		int rc = pwrite(fd[0], zblk, BLSZ, BLSZ);
		int frc = (rc == BLSZ) ? fsync(fd[0]) : -1;
		printf("OV_OK i=0 phase=H fsync=%d errno=%d\n", frc, errno);
		done[0] = 1;
	}

	/* pre-open the rest */
	for (i = 1; i < nvictim && i < NVICT; ++i) {
		snprintf(path, sizeof(path), "%s/v%02d", mp, i);
		fd[i] = open(path, O_RDWR, 0644);
		if (fd[i] < 0)
			done[i] = 1;	/* skip */
	}

	/* wait for approach band */
	for (;;) {
		int64_t fr = getfree(mp);
		if (fr >= 0 && fr <= 12 * 1024 * 1024)
			break;
		usleep(5000);
	}
	printf("OV_BLITZ_START free=%lld\n", (long long)getfree(mp));

	/*
	 * DRAIN: mmap-dirty a chunk ~= current free + 8MB.  mmap page
	 * dirtying bypasses the vop_write frontend ENOSPC gate (that gate
	 * lives only in hammer2_vop_write), so the putpages flushes drain
	 * the freemap to TRUE exhaustion while pmp->free_nominal is still
	 * cached high.  The hammer below then slips victim pwrites past
	 * the (stale) frontend gate whose backend chain_delete allocation
	 * fails.
	 */
	{
		int64_t fr0 = getfree(mp);
		size_t dsz = (size_t)(fr0 + 8 * 1024 * 1024);
		pid_t pid;
		char dpath[1024];

		if (dsz > 400ULL * 1024 * 1024)
			dsz = 400ULL * 1024 * 1024;
		snprintf(dpath, sizeof(dpath), "%s/drain.bin", mp);
		pid = fork();
		if (pid == 0) {
			uint8_t *pp;
			int dfd = open(dpath, O_CREAT | O_TRUNC | O_RDWR,
			    0644);
			if (dfd < 0)
				_exit(1);
			if (ftruncate(dfd, (off_t)dsz) < 0)
				_exit(2);
			pp = mmap(NULL, dsz, PROT_READ | PROT_WRITE,
			    MAP_SHARED, dfd, 0);
			if (pp == MAP_FAILED)
				_exit(3);
			memset(pp, 1, dsz);
			msync(pp, dsz, MS_ASYNC);
			munmap(pp, dsz);
			close(dfd);
			_exit(0);
		}
		printf("OV_DRAIN_SPAWNED pid=%d sz=%llu\n",
		    (int)pid, (unsigned long long)dsz);
	}

	for (r = 0; r < 4000; ++r) {
		int64_t fr = getfree(mp);
		ndone = 0;
		for (i = 1; i < nvictim && i < NVICT; ++i) {
			int rc;
			int64_t thr;
			if (done[i]) { ++ndone; continue; }
			/* descending ladder 16MB..8MB, then free-for-all
			 * hammer on everything below 8MB (the crossing
			 * zone) */
			thr = 16 * 1024 * 1024 - (int64_t)i * 125 * 1024;
			if (thr > 8 * 1024 * 1024)
				thr = 8 * 1024 * 1024;
			if (fr > thr)
				continue;
			rc = pwrite(fd[i], zblk, BLSZ, BLSZ);
			if (rc == BLSZ) {
				int frc = fsync(fd[i]);
				printf("OV_OK i=%d phase=W rot=%d "
				    "fsync=%d errno=%d free=%lld\n",
				    i, r, frc, errno, (long long)fr);
				done[i] = 1;
				++ndone;
			}
		}
		if ((r % 200) == 0)
			printf("OV_ROT r=%d done=%d free=%lld\n",
			    r, ndone, (long long)fr);
		if (ndone >= nvictim - 1)
			break;
		usleep(3000);
	}

	ngiveup = 0;
	for (i = 1; i < nvictim && i < NVICT; ++i) {
		if (!done[i]) {
			printf("OV_GIVEUP i=%d errno=%d free=%lld\n",
			    i, errno, (long long)getfree(mp));
			++ngiveup;
		}
		if (fd[i] >= 0)
			close(fd[i]);
	}
	if (fd[0] >= 0)
		close(fd[0]);
	printf("OVERWRITE_DONE giveup=%d\n", ngiveup);
	return 0;
}

static int
do_churn(const char *mp, int passes)
{
	char path[1024];
	uint8_t *blk;
	long total = 0;
	int fd, p;
	ssize_t r;

	blk = malloc(BLSZ);
	if (!blk) { perror("malloc"); return 2; }
	snprintf(path, sizeof(path), "%s/fill.bin", mp);
	fd = open(path, O_RDONLY);
	if (fd < 0) { perror("open fill.bin read"); return 2; }
	for (p = 0; p < passes; ++p) {
		long got = 0;
		lseek(fd, 0, SEEK_SET);
		for (;;) {
			r = read(fd, blk, BLSZ);
			if (r <= 0)
				break;
			got += r;
		}
		total += got;
		printf("CHURN pass=%d bytes=%ld\n", p, got);
	}
	close(fd);
	printf("CHURN_DONE total=%ld\n", total);
	return 0;
}

static int
do_hog(long mb)
{
	size_t sz = (size_t)mb * 1024 * 1024;
	uint8_t *p = malloc(sz);
	size_t i;
	if (!p) { perror("malloc hog"); return 2; }
	for (i = 0; i < sz; i += 4096)
		p[i] = 1;
	printf("HOG_TOUCHED mb=%ld\n", mb);
	sleep(120);		/* hold it while readback runs */
	printf("HOG_RELEASE\n");
	free(p);
	return 0;
}

/*
 * churnroot: read files under a root-filesystem directory until a byte
 * budget is consumed, to cycle the kernel buffer cache WITHOUT touching
 * the (possibly wedged) test mount.  Clean buffers of other mounts (the
 * victims' zero-filled logical buffers) get recycled under the pressure.
 */
static int64_t g_budget;

static int
churn_file(const char *path)
{
	uint8_t *blk;
	int fd;
	ssize_t r;
	int64_t got = 0;

	if (g_budget <= 0)
		return 0;
	fd = open(path, O_RDONLY);
	if (fd < 0)
		return 0;
	blk = malloc(BLSZ);
	if (!blk) { close(fd); return 0; }
	for (;;) {
		r = read(fd, blk, BLSZ);
		if (r <= 0)
			break;
		got += r;
		g_budget -= r;
		if (g_budget <= 0)
			break;
	}
	free(blk);
	close(fd);
	return got;
}

static int64_t
churn_dir(const char *dir)
{
	DIR *d;
	struct dirent *de;
	struct stat st;
	char sub[2048];
	int64_t got = 0;

	if (g_budget <= 0)
		return 0;
	d = opendir(dir);
	if (!d)
		return 0;
	while ((de = readdir(d)) != NULL && g_budget > 0) {
		if (de->d_name[0] == '.' && (de->d_name[1] == 0 ||
		    (de->d_name[1] == '.' && de->d_name[2] == 0)))
			continue;
		snprintf(sub, sizeof(sub), "%s/%s", dir, de->d_name);
		if (stat(sub, &st) < 0)
			continue;
		if (S_ISDIR(st.st_mode))
			got += churn_dir(sub);
		else if (S_ISREG(st.st_mode) && st.st_size > 0)
			got += churn_file(sub);
	}
	closedir(d);
	return got;
}

static int
do_churnroot(const char *dir, long mb)
{
	int64_t got;
	g_budget = (int64_t)mb * 1024 * 1024;
	got = churn_dir(dir);
	printf("CHURNROOT_DONE dir=%s mb=%ld got=%lld\n", dir, mb,
	    (long long)got);
	return 0;
}

static int
do_readback(const char *mp, int nvictim)
{
	char path[1024], expect[BLSZ];
	uint8_t *blk;
	int fd, i, old = 0, zeros = 0, mixed = 0, err = 0;

	blk = malloc(BLSZ);
	if (!blk) { perror("malloc"); return 2; }

	for (i = 0; i < nvictim; ++i) {
		ssize_t r;
		size_t j;
		int iszero = 1, isold = 1;

		snprintf(path, sizeof(path), "%s/v%02d", mp, i);
		fd = open(path, O_RDONLY);
		if (fd < 0) {
			printf("RB i=%d OPEN_ERR errno=%d\n", i, errno);
			++err;
			continue;
		}
		r = pread(fd, blk, BLSZ, BLSZ);
		if (r != BLSZ) {
			printf("RB i=%d READ_ERR rc=%zd errno=%d\n", i, r, errno);
			++err;
			close(fd);
			continue;
		}
		mkpattern(expect, BLSZ, i, 1);
		for (j = 0; j < BLSZ; ++j) {
			if (blk[j] != 0)
				iszero = 0;
			if (blk[j] != (uint8_t)expect[j])
				isold = 0;
		}
		if (isold)
			++old;
		else if (iszero)
			++zeros;
		else
			++mixed;
		printf("RB i=%d CLASS=%s first16=%02x,%02x,%02x,%02x\n", i,
		    isold ? "OLD" : (iszero ? "ZEROS" : "MIXED"),
		    blk[0], blk[1], blk[2], blk[3]);

		/* block0 sanity (never overwritten) */
		r = pread(fd, blk, 16, 0);
		printf("RB i=%d B0_SANITY=%s\n", i,
		    (r == 16 && blk[0] == 'D' && blk[1] == 'F') ? "ok" : "bad");
		close(fd);
	}
	printf("READBACK_DONE old=%d zeros=%d mixed=%d err=%d\n",
	    old, zeros, mixed, err);
	return 0;
}

int
main(int argc, char **argv)
{
	if (argc < 3) {
		fprintf(stderr,
		    "usage: %s setup|fill|drip|overwrite|churn|hog|readback "
		    "<mp> [n|cap|passes|MB]\n", argv[0]);
		return 2;
	}
	setvbuf(stdout, NULL, _IONBF, 0);

	if (!strcmp(argv[1], "setup"))
		return do_setup(argv[2], atoi(argv[3]));
	if (!strcmp(argv[1], "fill"))
		return do_fill(argv[2], atol(argv[3]));
	if (!strcmp(argv[1], "fillslow"))
		return do_fillslow(argv[2]);
	if (!strcmp(argv[1], "drip"))
		return do_drip(argv[2]);
	if (!strcmp(argv[1], "overwrite")) {
		long dms = (argc > 4) ? atol(argv[4]) : 2500;
		return do_overwrite(argv[2], atoi(argv[3]), dms);
	}
	if (!strcmp(argv[1], "churn"))
		return do_churn(argv[2], atoi(argv[3]));
	if (!strcmp(argv[1], "churnroot"))
		return do_churnroot(argv[2], atol(argv[3]));
	if (!strcmp(argv[1], "hog"))
		return do_hog(atol(argv[3]));
	if (!strcmp(argv[1], "readback"))
		return do_readback(argv[2], atoi(argv[3]));
	fprintf(stderr, "bad stage\n");
	return 2;
}
