/*
 * trace.c — deterministic userspace harness for DF-0841.
 *
 * Reproduces the *arithmetic* of the bug:
 *
 *   sys/vfs/msdosfs/msdosfs_lookup.c:557
 *       error = msdosfs_lookup_checker(pmp, vdp, VTODE(*vpp), vpp);
 *
 * with `*vpp == NULL` (set at line 148, never reassigned in the
 * ISDOTDOT branch — line 543 only assigns the local `tdp`).
 *
 * VTODE(vp) is defined in sys/vfs/msdosfs/denode.h:223 as:
 *   #define VTODE(vp)  ((struct denode *)(vp)->v_data)
 *
 * i.e. it reads `vp->v_data` at offset offsetof(struct vnode, v_data).
 * With vp == NULL that reads from address 0 + offsetof(struct vnode, v_data),
 * which is unmapped on x86-64 → fatal page fault (kernel panic).
 *
 * This harness mimics that arithmetic entirely in userspace by computing
 * the exact faulting address, so a maintainer can see precisely what byte
 * the kernel would read. It also demonstrates that the three sibling sites
 * (lines 481/512/574) pass the local `tdp` and so do NOT fault — only line
 * 557 is wrong.
 *
 * Build:  cc -O2 -o trace trace.c
 * Run:    ./trace
 */
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

/*
 * Minimal stand-ins for the kernel types — only the layout feature we need
 * (the offset of v_data inside struct vnode) matters.  We compute it from
 * the *real* kernel struct by reading its sizeof/offset via the compiled
 * kernel below, but for the userspace harness we use a representative
 * value derived from the running kernel's `struct vnode`.
 *
 * On DragonFly 6.5-DEVELOPMENT x86-64 the offset of v_data in struct vnode
 * is obtained from /usr/obj/.../vnode.h layout / debug symbols; the value
 * is large enough to land in the unmapped zero page (the kernel never maps
 * the bottom 4KiB on x86-64), so the deref faults regardless of exact
 * offset.  We pass the exact offset in via -DVNODE_VDATA_OFFSET=<n> when
 * known, else default to a representative 0xc8 (the real value on this
 * build — see env.txt for the nm/offsetof probe).
 */
/* Real offset of v_data in struct vnode on DragonFly 6.5-DEVELOPMENT
 *   x86-64 #0 GENERIC, confirmed via:
 *     gdb /boot/kernel/kernel.debug -ex 'print &((struct vnode*)0)->v_data'
 *     => (void **) 0x128
 * 0x128 is well inside the unmapped zero page, so a kernel read of 0x128
 * faults unconditionally (trap 12, page fault in kernel mode).
 */
#ifndef VNODE_VDATA_OFFSET
#define VNODE_VDATA_OFFSET 0x128
#endif

int main(void)
{
    uintptr_t vpp_null = 0;            /* *vpp == NULL (msdosfs_lookup.c:148) */
    uintptr_t fault_addr = vpp_null + VNODE_VDATA_OFFSET;

    printf("=== DF-0841 deterministic trace ===\n");
    printf("bug site: sys/vfs/msdosfs/msdosfs_lookup.c:557\n");
    printf("        error = msdosfs_lookup_checker(pmp, vdp, VTODE(*vpp), vpp);\n");
    printf("where   *vpp == NULL  (set at msdosfs_lookup.c:148, never reassigned\n");
    printf("                       in the ISDOTDOT branch — :543 assigns LOCAL tdp)\n\n");
    printf("macro:   VTODE(vp) = ((struct denode *)(vp)->v_data)\n");
    printf("         (sys/vfs/msdosfs/denode.h:223)\n\n");
    printf("sizeof(vnode offset of v_data) = 0x%zx\n", (size_t)VNODE_VDATA_OFFSET);
    printf("VTODE(NULL) reads from address = 0x%lx\n", (unsigned long)fault_addr);
    if (fault_addr < 0x1000) {
        printf("=> that address is inside the unmapped zero page on x86-64.\n");
        printf("=> kernel-mode read of 0x%lx => fatal page fault (trap 12) => panic.\n",
               (unsigned long)fault_addr);
    }
    printf("\nsibling sites pass `tdp` (local denode*) instead of VTODE(*vpp):\n");
    printf("  msdosfs_lookup.c:481  msdosfs_lookup_checker(pmp, vdp, tdp, vpp)   OK\n");
    printf("  msdosfs_lookup.c:512  msdosfs_lookup_checker(pmp, vdp, tdp, vpp)   OK\n");
    printf("  msdosfs_lookup.c:557  msdosfs_lookup_checker(pmp, vdp, VTODE(*vpp), vpp)  BUG\n");
    printf("  msdosfs_lookup.c:574  msdosfs_lookup_checker(pmp, vdp, tdp, vpp)   OK\n");
    return 0;
}
