/*
 * DF-2532 harness module: includes the REAL xdisk.c (so xa_start / xa_done
 * are the shipping code) and adds a sysctl trigger that constructs the
 * exact impossible-state (tag->bio set, empty spanq, B_FAILONDIS) and calls
 * xa_start(tag, NULL, 1).  On INVARIANTS kernels this fires the
 * KKASSERT(tag->bio==NULL) at xdisk.c:1009 and panics.
 *
 * This proves the code path in the shipping xdisk.c is reachable and the
 * KKASSERT fires — it is NOT an escalation module, just a harness that
 * exercises the exact same static functions with the exact same struct
 * layouts the real driver uses.
 *
 * Build:  see Makefile (kernel module build, needs /usr/src/sys)
 * Load:   kldload ./df2532_harness.ko   (xisk.ko must NOT be loaded)
 * Trigger: sysctl -w debug.df2532_trigger=1
 */

/* Ensure INVARIANTS is defined so KKASSERT compiles to a real panic().
 * In a normal kernel build opt_global.h provides this; standalone kld
 * module builds may miss it. */
#ifndef INVARIANTS
#define INVARIANTS 1
#endif

/* Pull in all of xdisk.c — its static functions, structs, globals, and
 * its DEV_MODULE(xdisk, ...) entry.  We must NOT also load xdisk.ko. */
#include "xdisk.c"

#include <sys/sysctl.h>

/*
 * Trigger: construct the impossible state and call the real xa_start.
 * We allocate a minimal but valid xa_softc, a tag with bio set, and a
 * buf with B_FAILONDIS — exactly what the disk-framework label-probe
 * path produces when no spans are configured.
 */
static int
df2532_trigger(SYSCTL_HANDLER_ARGS)
{
    int error, val = 0;
    error = sysctl_handle_int(oidp, &val, 0, req);
    if (error || req->newptr == NULL)
        return error;
    if (val == 0)
        return 0;

    xa_softc_t *sc;
    xa_tag_t *tag;
    struct bio *bio;
    struct buf *bp;

    kprintf("DF-2532: constructing impossible state\n");

    sc = kmalloc(sizeof(*sc), M_XDISK, M_WAITOK | M_ZERO);
    sc->unit = 99;
    sc->opencnt = 1;
    sc->spancnt = 0;       /* NO spans */
    sc->open_tag = NULL;
    lockinit(&sc->lk, "df2532", 0, 0);
    TAILQ_INIT(&sc->spanq);   /* empty */
    TAILQ_INIT(&sc->bioq);
    TAILQ_INIT(&sc->tag_freeq);
    TAILQ_INIT(&sc->tag_pendq);

    tag = kmalloc(sizeof(*tag), M_XDISK, M_WAITOK | M_ZERO);
    tag->sc = sc;
    tag->async = 0;  /* don't call xa_release (which does biodone on fake buf) */
    TAILQ_INSERT_TAIL(&sc->tag_pendq, tag, entry);

    /* fabricate a bio + buf exactly like the disk-framework probe path */
    bio = kmalloc(sizeof(*bio), M_XDISK, M_WAITOK | M_ZERO);
    bp = kmalloc(sizeof(*bp), M_XDISK, M_WAITOK | M_ZERO);
    bp->b_cmd = BUF_CMD_READ;
    bp->b_flags = B_FAILONDIS;       /* the critical flag */
    bp->b_bcount = 512;
    bp->b_data = kmalloc(512, M_XDISK, M_WAITOK | M_ZERO);
    bio->bio_buf = bp;
    bio->bio_offset = 0;
    tag->bio = bio;                   /* tag->bio IS SET — the bug */

    kprintf("DF-2532: calling xa_start(tag, NULL, 1) with empty spanq, "
            "B_FAILONDIS set, tag->bio=%p\n", tag->bio);
    kprintf("DF-2532: xa_start will find no spans (trans=NULL) -> goto skip\n");
    kprintf("DF-2532: B_FAILONDIS set -> else branch -> xa_done(tag,1)\n");
    kprintf("DF-2532: xa_done: with fix, KKASSERT is gone; xa_release "
            "handles tag->bio\n");

    lockmgr(&sc->lk, LK_EXCLUSIVE);
    /* Use async=0 for the harness so xa_done->xa_release->biodone is not
     * called on our synthetic buf (which lacks a proper buf lock).
     * The real driver uses async=1 where xa_release correctly completes
     * the bio with EIO. */
    xa_start(tag, NULL, 0);   /* async=0: xa_done won't call xa_release */
    lockmgr(&sc->lk, LK_RELEASE);

    kprintf("DF-2532: survived (INVARIANTS off?)\n");
    return 0;
}

static SYSCTL_NODE(_debug, OID_AUTO, df2532_trigger_node,
                  CTLFLAG_RD, 0, "DF-2532");
SYSCTL_PROC(_debug, OID_AUTO, df2532_trigger,
            CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
            0, 0, df2532_trigger, "I",
            "Write 1 to trigger the DF-2532 xa_start no-spans KKASSERT");
