/*
 * DF-2815 - direct-syscall variant.
 *
 * Main thread calls reboot(2) directly (RB_DUMP), churn threads on other
 * CPUs keep issuing DIOCGKERNELDUMP set/clear against the global `dumper'
 * while the kernel is inside boot() -> dumpsys() -> md_dumpsys(&dumper).
 *
 * A clear (set_dumper(NULL) bzero, kern_shutdown.c:952) landing after
 * dumpsys() has passed its `dumper.dumper != NULL' check (kern_shutdown.c:980)
 * leaves di->priv == NULL for the next dev_ddump() in minidump_machdep.c
 * -> dev_needmplock(NULL) dereferences NULL->si_ops -> kernel page fault
 * while dumping.
 *
 * usage: race2 <device> <nthreads> <clearmask-log2>
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/diskslice.h>
#include <sys/reboot.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <pthread.h>

static int fd;
static int clear_shift = 8;	/* clear every (1<<shift) ioctls */
static volatile unsigned long n_ok, n_err, n_set, n_clr;
static volatile int stop;
static FILE *logf;

static void
logline(const char *fmt, ...)
{
	va_list ap;
	time_t t;
	char b[128];

	time(&t);
	va_start(ap, fmt);
	vsnprintf(b, sizeof(b), fmt, ap);
	va_end(ap);
	fprintf(logf, "%ld %s", (long)t, b);
	fflush(logf);
}

static void *
churn(void *x __unused)
{
	u_int u;
	unsigned long i = 0;

	while (!stop) {
		u = ((i & ((1UL << clear_shift) - 1)) == 0) ? 0 : 1;
		if (ioctl(fd, DIOCGKERNELDUMP, &u) == 0) {
			n_ok++;
			if (u)
				n_set++;
			else
				n_clr++;
		} else {
			n_err++;
		}
		i++;
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t tid[64];
	int nthreads = 4;
	int i;

	if (argc > 1)
		fd = open(argv[1], O_RDONLY);
	else
		fd = open("/dev/vbd0s1b", O_RDONLY);
	if (argc > 2)
		nthreads = atoi(argv[2]);
	if (argc > 3)
		clear_shift = atoi(argv[3]);
	if (nthreads > 64)
		nthreads = 64;
	logf = fopen("/root/race2.log", "w");
	if (logf == NULL)
		logf = stderr;
	setvbuf(logf, NULL, _IONBF, 0);
	if (fd < 0) {
		logline("open failed\n");
		exit(1);
	}
	logline("race2: starting %d churn threads clear_shift=%d\n",
	    nthreads, clear_shift);
	for (i = 0; i < nthreads; i++)
		pthread_create(&tid[i], NULL, churn, NULL);
	sleep(1);	/* let churn ramp up */
	logline("race2: calling reboot(RB_DUMP|RB_NOSYNC) now, ok=%lu clr=%lu\n",
	    n_ok, n_clr);
	syscall(SYS_reboot, RB_AUTOBOOT | RB_DUMP | RB_NOSYNC);
	logline("race2: reboot returned?!\n");
	stop = 1;
	for (i = 0; i < nthreads; i++)
		pthread_join(tid[i], NULL);
	logline("race2: done ok=%lu err=%lu set=%lu clr=%lu\n",
	    n_ok, n_err, n_set, n_clr);
	return (0);
}
