/*
 * eater.c -- DF-5833 rig: pin VM free pages below v_free_reserved and hold,
 * then serve "drips" on stdin. Persistent across attempts.
 *
 * Usage: eater <target_free_pages> <ready_marker> <start_gate>
 * Commands on stdin (line-based, polled via kqueue/select):
 *    D <bytes>  -- munmap ~<bytes> of eaten memory (instant page release)
 *    R          -- re-eat until free <= target again (after a drip burst)
 *    Q          -- munmap everything and exit
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/sysctl.h>

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define CHUNK   (1UL << 20)	/* 1MB eat units */
#define MAXCHUNK 8192

static void *chunks[MAXCHUNK];
static int nchunks;
static size_t eaten;
static long target;

static long
free_count(void)
{
	size_t val = 0;
	size_t len = sizeof(val);
	if (sysctlbyname("vm.stats.vm.v_free_count", &val, &len, NULL, 0) < 0) {
		perror("sysctl");
		exit(1);
	}
	return (long)val;
}

/* Eat one chunk; returns 0 on success, -1 if we could not (mmap fail). */
static int
eat_one(void)
{
	if (nchunks == MAXCHUNK)
		return (-1);
	void *p = mmap(NULL, CHUNK, PROT_READ | PROT_WRITE,
		       MAP_ANON | MAP_PRIVATE, -1, 0);
	if (p == MAP_FAILED)
		return (-1);
	memset(p, 0x5a, CHUNK);
	chunks[nchunks++] = p;
	eaten += CHUNK;
	return (0);
}

int
main(int argc, char **argv)
{
	target = (argc > 1) ? atol(argv[1]) : 2000;
	const char *marker = (argc > 2) ? argv[2] : NULL;
	const char *startgate = (argc > 3) ? argv[3] : NULL;
	char line[128];
	long fc;
	int i;

	setvbuf(stdout, NULL, _IONBF, 0);

	if (startgate) {
		while (access(startgate, F_OK) != 0)
			usleep(2000);
	}

	/* Phase 1: eat until free_count <= target or we self-stall. */
	for (;;) {
		fc = free_count();
		if (fc <= target)
			break;
		struct timeval t0, t1;
		gettimeofday(&t0, NULL);
		if (eat_one() != 0) {
			fprintf(stderr, "eater: mmap failed (free=%ld)\n", fc);
			break;
		}
		gettimeofday(&t1, NULL);
		long us = (t1.tv_sec - t0.tv_sec) * 1000000 +
			  (t1.tv_usec - t0.tv_usec);
		if (us > 500000) {
			fprintf(stderr, "eater: stalled (%ld us) - at floor\n", us);
			break;
		}
	}
	fprintf(stdout, "EATER-READY free=%ld eaten=%zuMB chunks=%d\n",
		free_count(), eaten >> 20, nchunks);
	if (marker) {
		int fd = open(marker, O_CREAT | O_WRONLY | O_TRUNC, 0644);
		if (fd >= 0) {
			dprintf(fd, "ready\n");
			close(fd);
		}
	}

	/* Phase 2: serve commands. NO hold loop -- sustained pinning
	 * drives the system into swap exhaustion and the OOM killer.
	 * Pressure windows are short (eat, park, crash, drip). */
	int ci = 0;			/* drip cursor: chunk index */
	size_t off = 0;		/* drip cursor: offset inside chunk */
	for (;;) {
		fd_set rfds;
		struct timeval tv;
		FD_ZERO(&rfds);
		FD_SET(0, &rfds);
		tv.tv_sec = 0;
		tv.tv_usec = 50000;
		if (select(1, &rfds, NULL, NULL, &tv) <= 0)
			continue;
		if (fgets(line, sizeof(line), stdin) == NULL)
			break;
		if (line[0] == 'Q')
			break;
		if (line[0] == 'R') {
			/* re-pin: eat until free <= target again, then
			 * re-arm the ready marker */
			unlink(marker);
			while (free_count() > target) {
				if (eat_one() != 0)
					break;
			}
			fprintf(stdout, "RE-EATEN free=%ld chunks=%d\n",
				free_count(), nchunks);
			if (marker) {
				int fd = open(marker, O_CREAT | O_WRONLY |
					      O_TRUNC, 0644);
				if (fd >= 0)
					close(fd);
			}
			continue;
		}
		if (line[0] != 'D')
			continue;
		size_t want = strtoul(line + 2, NULL, 10);
		size_t given = 0;
		while (given < want && ci < nchunks) {
			char *base = (char *)chunks[ci] + off;
			size_t piece = CHUNK - off;
			if (piece > 65536)
				piece = 65536;
			if (piece > (want - given))
				piece = (want - given) & ~4095UL;
			if (piece == 0)
				piece = 4096;
			munmap(base, piece);
			off += piece;
			given += piece;
			if (off >= CHUNK) {
				off = 0;
				ci++;
			}
		}
		if (ci >= nchunks) {	/* exhausted drip region: reset cursor */
			ci = 0;
			off = 0;
		}
		fprintf(stdout, "DRIP %zu free=%ld\n", given, free_count());
	}
	for (i = 0; i < nchunks; i++)
		munmap(chunks[i], CHUNK);
	fprintf(stdout, "EATER-DONE free=%ld\n", free_count());
	return (0);
}
