/*
 * biopoison.c - DF-5787 KLD rig
 *
 * Demonstrates that vfs_bio.c buffer-cache recycling does NOT clear
 * b_bio1.bio_flags: a buffer released with a stale BIO_DONE flag carries
 * that flag into its next life, and the very next bread() on the same
 * (vp, loffset) trips vn_strategy()'s KKASSERT((bio->bio_flags &
 * BIO_DONE) == 0) (sys/kern/vfs_bio.c:3385) - or, on a kernel without
 * that assertion, makes biowait() return before the device I/O completes
 * (torn data + brelse() while DMA is in flight).
 *
 * The poison step emulates exactly what breadcb()'s B_CACHE fast path
 * does (sys/kern/vfs_bio.c:883 sets BIO_DONE and relies on the callback
 * to clear it) minus the callback's cleanup - i.e. any forgetful/abandoning
 * completion path.  The kernel provides no defense at recycling time
 * (getnewbuf() resets b_flags only, vfs_bio.c:2095; reinitbufbio() only
 * clears bio_done/bio_offset, vfs_bio.c:743-751).
 *
 * Case 0 (control): same manipulation but WITHOUT the stale flag.
 * Case 1 (poison):  stale BIO_DONE set before bqrelse().
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/buf.h>
#include <sys/bio.h>
#include <sys/vnode.h>
#include <sys/nlookup.h>
#include <sys/namecache.h>
#include <sys/ucred.h>
#include <sys/proc.h>
#include <sys/buf2.h>

#define BPIO_BLKSIZE	16384
#define BPIO_LOFF	0

static struct vnode *bpio_vp;

static int
bpio_getvp(const char *path)
{
	struct nlookupdata nd;
	struct vnode *vp = NULL;
	int error;

	error = nlookup_init(&nd, path, UIO_SYSSPACE, NLC_FOLLOW);
	if (error == 0)
		error = nlookup(&nd);
	if (error == 0)
		error = cache_vref(&nd.nl_nch, curthread->td_ucred, &vp);
	nlookup_done(&nd);
	if (error == 0)
		bpio_vp = vp;
	return (error);
}

static void
bpio_case(int poison)
{
	struct buf *bp;
	int error;

	bp = getblk(bpio_vp, BPIO_LOFF, BPIO_BLKSIZE, 0, 0);
	kprintf("biopoison[%d]: getblk -> bp=%p b_flags=%08x "
		"bio1.flags=%08x B_CACHE=%d\n",
		poison, bp, bp->b_flags, bp->b_bio1.bio_flags,
		!!(bp->b_flags & B_CACHE));

	/*
	 * Force the next bread() to issue real I/O, and (poison case)
	 * leave a stale BIO_DONE behind, emulating a completion callback
	 * that forgot to clear it (breadcb()/cluster_readcb() contract).
	 */
	bp->b_flags &= ~B_CACHE;
	if (poison)
		bp->b_bio1.bio_flags |= BIO_DONE;
	bqrelse(bp);

	kprintf("biopoison[%d]: released (stale BIO_DONE=%d), "
		"issuing bread()\n", poison, poison);

	error = bread(bpio_vp, BPIO_LOFF, BPIO_BLKSIZE, &bp);
	kprintf("biopoison[%d]: bread returned %d (no panic)\n",
		poison, error);
	if (error == 0)
		brelse(bp);
}

static int
biopoison_handler(struct module *m, int what, void *arg)
{
	int error = 0;

	switch (what) {
	case MOD_LOAD:
		error = bpio_getvp("/etc/fstab");
		if (error) {
			kprintf("biopoison: cannot resolve vp: %d\n", error);
			return (error);
		}
		kprintf("biopoison: vp=%p v_type=%d\n",
			bpio_vp, bpio_vp->v_type);
		vn_lock(bpio_vp, LK_SHARED | LK_RETRY);

		kprintf("biopoison: === control run (no stale flag) ===\n");
		bpio_case(0);

		kprintf("biopoison: === poisoned run (stale BIO_DONE across "
			"bqrelse -> getblk recycling) ===\n");
		bpio_case(1);

		kprintf("biopoison: bread SURVIVED - stale flag was cleared "
			"somewhere during recycling (finding refuted)\n");
		vn_unlock(bpio_vp);
		vrele(bpio_vp);
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t biopoison_mod = { "biopoison", biopoison_handler, NULL };
DECLARE_MODULE(biopoison, biopoison_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(biopoison, 1);
