/*
 * DF-3027 / DF-3028 / DF-3030 / DF-3029 — parameterized FUSE daemon.
 *
 * Derived from the DF-0780 evidence-pack daemon (same ABI mirroring),
 * extended with behavior modes:
 *
 *   enosys-getattr : replies -ENOSYS to FUSE_GETATTR (latches
 *                    fuse_set_nosys) -> fuse_vop_getattr returns 0 with
 *                    the caller's *a_vap UNINITIALIZED (DF-3027).
 *   vbad-lookup    : replies to FUSE_LOOKUP with attr.mode == 0
 *                    (S_IFMT bits clear) -> nresolve computes vtyp=VBAD
 *                    -> fuse_node_vn default: KKASSERT(0) panic (DF-3028).
 *   normal         : fully well-formed replies (never oversized READ —
 *                    avoids DF-0780; well-formed dirent stream — avoids
 *                    DF-0781) for DF-3030 (rename kmem leak) and
 *                    DF-3029 (io-thread lost wakeup) stress.
 *
 * Build:  cc -O0 -g -o fuse_daemon fuse_daemon.c
 * Run:    ./fuse_daemon <mode> <mountpoint>   (as root)
 */

#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <sys/wait.h>

/* ---- FUSE ABI (mirrors sys/vfs/fuse/fuse_abi.h) ---- */

#define FUSE_KERNEL_VERSION       7
#define FUSE_KERNEL_MINOR_VERSION 28
#define FUSE_ROOT_ID              1

#define FUSE_LOOKUP     1
#define FUSE_FORGET     2
#define FUSE_GETATTR    3
#define FUSE_SETATTR    4
#define FUSE_READLINK   5
#define FUSE_SYMLINK    6
#define FUSE_MKNOD      8
#define FUSE_MKDIR      9
#define FUSE_UNLINK    10
#define FUSE_RMDIR     11
#define FUSE_RENAME    12
#define FUSE_LINK      13
#define FUSE_OPEN      14
#define FUSE_READ      15
#define FUSE_WRITE     16
#define FUSE_STATFS    17
#define FUSE_RELEASE   18
#define FUSE_FSYNC     20
#define FUSE_SETXATTR  21
#define FUSE_GETXATTR  22
#define FUSE_FLUSH     25
#define FUSE_INIT      26
#define FUSE_OPENDIR   27
#define FUSE_READDIR   28
#define FUSE_RELEASEDIR 29
#define FUSE_FSYNCDIR  29
#define FUSE_ACCESS    34
#define FUSE_CREATE    35

struct fuse_in_header {
    uint32_t len;
    uint32_t opcode;
    uint64_t unique;
    uint64_t nodeid;
    uint32_t uid;
    uint32_t gid;
    uint32_t pid;
    uint32_t 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_attr {
    uint64_t ino, size, blocks, atime, mtime, ctime;
    uint32_t atimensec, mtimensec, ctimensec, mode, nlink, uid, gid, rdev, blksize, padding;
};

struct fuse_attr_out {
    uint64_t attr_valid;
    uint32_t attr_valid_nsec;
    uint32_t dummy;
    struct fuse_attr attr;
};

struct fuse_entry_out {
    uint64_t nodeid, generation, entry_valid, attr_valid;
    uint32_t entry_valid_nsec, attr_valid_nsec;
    struct fuse_attr attr;
};

struct fuse_getattr_in {
    uint32_t getattr_flags, dummy;
    uint64_t fh;
};

struct fuse_open_in { uint32_t flags, unused; };
struct fuse_open_out { uint64_t fh; uint32_t open_flags, padding; };

struct fuse_read_in {
    uint64_t fh, offset;
    uint32_t size, read_flags;
    uint64_t lock_owner;
    uint32_t flags, padding;
};

struct fuse_write_in {
    uint64_t fh, offset;
    uint32_t size, write_flags;
    uint64_t lock_owner;
    uint32_t flags, padding;
};
struct fuse_write_out { uint32_t size, padding; };

struct fuse_create_in { uint32_t flags, mode, umask, padding; };

struct fuse_rename_in { uint64_t newdir; };

struct fuse_kstatfs {
    uint64_t blocks, bfree, bavail, files, ffree;
    uint32_t bsize, namelen, frsize, padding, spare[6];
};
struct fuse_statfs_out { struct fuse_kstatfs st; };

struct fuse_access_in { uint32_t mask, padding; };

struct fuse_dirent {
    uint64_t ino;
    uint64_t off;
    uint32_t namelen;
    uint32_t type;
    char name[];
};
#define DIRENT_ALIGN(x) (((x) + 7U) & ~7U)
#define DIRENT_SIZE(namelen) (DIRENT_ALIGN(24 + (namelen)))

/* synthetic fs: root(1) + regular file "target" ino 2, size FILE_SIZE */
#define FILE_INO  2
static uint64_t FILE_SIZE = 8192;

static const char *DEV = "/dev/fuse";
static const char *g_mode;
static volatile int g_post_open;  /* 1 after first FUSE_OPEN -> latch ENOSYS */

/* tiny synthetic namespace so rename(2) semantics work */
#define NNAME 256
struct nentry { char name[256]; uint64_t ino; };
static struct nentry ntab[NNAME];
static uint64_t next_ino = 100;

static struct nentry *
nfind(const char *n) {
    int i;
    for (i = 0; i < NNAME; i++)
        if (ntab[i].name[0] && strcmp(ntab[i].name, n) == 0)
            return &ntab[i];
    return NULL;
}
static struct nentry *
nadd(const char *n, uint64_t ino) {
    int i;
    struct nentry *e = nfind(n);
    if (e) return e;
    for (i = 0; i < NNAME; i++)
        if (!ntab[i].name[0]) {
            strlcpy(ntab[i].name, n, sizeof(ntab[i].name));
            ntab[i].ino = ino;
            return &ntab[i];
        }
    return NULL;
}

static void
reply(int fd, uint64_t unique, int error, const void *data, size_t datalen)
{
    struct fuse_out_header oh;
    struct iovec iov[2];
    oh.len     = sizeof(oh) + datalen;
    oh.error   = error;
    oh.unique  = unique;
    iov[0].iov_base = &oh;
    iov[0].iov_len  = sizeof(oh);
    iov[1].iov_base = (void *)(uintptr_t)data;
    iov[1].iov_len  = datalen;
    if (writev(fd, iov, data ? 2 : 1) < 0)
        warn("daemon writev failed");
}

static void
make_attr(struct fuse_attr *a, uint64_t ino, uint32_t mode, uint64_t size)
{
    memset(a, 0, sizeof(*a));
    a->ino      = ino;
    a->size     = size;
    a->blocks   = (size + 511) / 512;
    a->atime = a->mtime = a->ctime = 1000000000ULL;
    a->mode     = mode;
    a->nlink    = 1;
    a->uid      = 0;
    a->gid      = 0;
    a->blksize  = 4096;
}

static void
reply_getattr(int fd, uint64_t unique, uint64_t nodeid)
{
    struct fuse_attr_out ao;
    memset(&ao, 0, sizeof(ao));
    ao.attr_valid = 3600;
    if (nodeid == FUSE_ROOT_ID)
        make_attr(&ao.attr, FUSE_ROOT_ID, S_IFDIR | 0777, 0);
    else
        make_attr(&ao.attr, FILE_INO, S_IFREG | 0644, FILE_SIZE);
    reply(fd, unique, 0, &ao, sizeof(ao));
}

static void
reply_lookup(int fd, uint64_t unique)
{
    struct fuse_entry_out eo;
    memset(&eo, 0, sizeof(eo));
    eo.nodeid = FILE_INO;
    eo.generation = 1;
    eo.entry_valid = eo.attr_valid = 3600;
    if (strcmp(g_mode, "vbad-lookup") == 0) {
        /* S_IFMT bits clear -> kernel computes vtyp = VBAD
         * (fuse_vnops.c:568) -> fuse_node_vn default: KKASSERT(0) */
        make_attr(&eo.attr, FILE_INO, 0644, FILE_SIZE);
    } else {
        make_attr(&eo.attr, FILE_INO, S_IFREG | 0644, FILE_SIZE);
    }
    reply(fd, unique, 0, &eo, sizeof(eo));
}

/* well-formed dirent stream for "." ".." "target" (DF-0781-safe) */
static size_t
make_dirents(unsigned char *buf, size_t bufsz)
{
    static const char *names[3] = { ".", "..", "target" };
    static const uint32_t types[3] = { 4, 4, 8 }; /* DT_DIR, DT_DIR, DT_REG */
    size_t off = 0;
    int i;
    for (i = 0; i < 3; i++) {
        size_t nlen = strlen(names[i]);
        size_t reclen = DIRENT_SIZE(nlen);
        struct fuse_dirent *d = (struct fuse_dirent *)(buf + off);
        memset(buf + off, 0, reclen);
        d->ino = (i == 2) ? FILE_INO : FUSE_ROOT_ID;
        d->off = off + reclen;
        d->namelen = nlen;
        d->type = types[i];
        memcpy(d->name, names[i], nlen);
        off += reclen;
    }
    return off;
}

static void
serve(int fd)
{
    unsigned char buf[262144];
    static unsigned char readdata[1 << 20];
    unsigned char dirbuf[1024];
    size_t dirlen = make_dirents(dirbuf, sizeof(dirbuf));

    fprintf(stderr, "[daemon:%s] serving fd=%d\n", g_mode, fd);
    for (;;) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n < 0) {
            if (errno == EINTR) continue;
            warn("daemon read failed");
            return;
        }
        if (n == 0) {
            fprintf(stderr, "[daemon] EOF on device\n");
            return;
        }
        if ((size_t)n < sizeof(struct fuse_in_header))
            continue;
        struct fuse_in_header *ih = (struct fuse_in_header *)buf;
        void *in = buf + sizeof(*ih);
        uint64_t uniq = ih->unique;
        fprintf(stderr, "[daemon] <- op=%u uniq=%lu node=%lu uid=%u\n",
            ih->opcode, (unsigned long)uniq,
            (unsigned long)ih->nodeid, ih->uid);

        switch (ih->opcode) {
        case FUSE_INIT: {
            struct fuse_init_in *fi = in;
            struct fuse_init_out fo;
            memset(&fo, 0, sizeof(fo));
            fo.major = FUSE_KERNEL_VERSION;
            fo.minor = (fi->minor < FUSE_KERNEL_MINOR_VERSION) ?
                fi->minor : FUSE_KERNEL_MINOR_VERSION;
            fo.max_readahead = 4096;
            fo.max_write = 1 << 20;
            fprintf(stderr, "[daemon] INIT %u.%u\n", fi->major, fi->minor);
            reply(fd, uniq, 0, &fo, sizeof(fo));
            break;
        }
        case FUSE_STATFS: {
            struct fuse_statfs_out so;
            memset(&so, 0, sizeof(so));
            so.st.blocks = 1024; so.st.bfree = 512; so.st.bavail = 512;
            so.st.files = 16; so.st.ffree = 8;
            so.st.bsize = 4096; so.st.namelen = 255; so.st.frsize = 4096;
            reply(fd, uniq, 0, &so, sizeof(so));
            break;
        }
        case FUSE_GETATTR:
            if (strcmp(g_mode, "enosys-getattr") == 0 && g_post_open) {
                /* latches fuse_set_nosys(FUSE_GETATTR) in the kernel and
                 * makes fuse_ipc_tx return ENOSYS, which
                 * fuse_vop_getattr converts to success WITHOUT
                 * initializing *a_vap (fuse_vnops.c:331-332,315-316) */
                reply(fd, uniq, -ENOSYS, NULL, 0);
            } else {
                reply_getattr(fd, uniq, ih->nodeid);
            }
            break;
        case FUSE_SETATTR:
            reply_getattr(fd, uniq, ih->nodeid);
            break;
        case FUSE_LOOKUP: {
            const char *name = (const char *)in;
            struct nentry *e;
            if (strcmp(g_mode, "vbad-lookup") == 0) {
                reply_lookup(fd, uniq);   /* DF-3028: mode=0 panic */
                break;
            }
            if (strcmp(name, "target") == 0) {
                reply_lookup(fd, uniq);
                break;
            }
            e = nfind(name);
            if (e) {
                struct fuse_entry_out eo;
                memset(&eo, 0, sizeof(eo));
                eo.nodeid = e->ino;
                eo.entry_valid = eo.attr_valid = 3600;
                make_attr(&eo.attr, e->ino, S_IFREG | 0644, 0);
                reply(fd, uniq, 0, &eo, sizeof(eo));
            } else {
                reply(fd, uniq, -ENOENT, NULL, 0);
            }
            break;
        }
        case FUSE_ACCESS:
            reply(fd, uniq, 0, NULL, 0);
            break;
        case FUSE_OPEN:
        case FUSE_OPENDIR:
        case FUSE_CREATE: {
            g_post_open = 1;
            if (ih->opcode == FUSE_CREATE) {
                struct fuse_create_in *ci = in;
                const char *name = (const char *)(ci + 1);
                struct nentry *e = nadd(name, next_ino++);
                struct fuse_entry_out eo;
                struct fuse_open_out *o;
                unsigned char buf2[sizeof(eo) + sizeof(*o)];
                if (!e) { reply(fd, uniq, -ENOSPC, NULL, 0); break; }
                memset(buf2, 0, sizeof(buf2));
                eo = *(struct fuse_entry_out *)buf2;
                o = (struct fuse_open_out *)(buf2 + sizeof(eo));
                ((struct fuse_entry_out *)buf2)->nodeid = e->ino;
                ((struct fuse_entry_out *)buf2)->entry_valid = 3600;
                ((struct fuse_entry_out *)buf2)->attr_valid = 3600;
                make_attr(&((struct fuse_entry_out *)buf2)->attr,
                    e->ino, S_IFREG | 0644, 0);
                o->fh = e->ino;
                reply(fd, uniq, 0, buf2, sizeof(buf2));
                break;
            }
            struct fuse_open_out oo;
            memset(&oo, 0, sizeof(oo));
            oo.fh = FILE_INO;
            if (ih->opcode == FUSE_CREATE) {
                /* entry_out followed by open_out */
                unsigned char eo[sizeof(struct fuse_entry_out) +
                                sizeof(struct fuse_open_out)];
                struct fuse_entry_out *e = (struct fuse_entry_out *)eo;
                struct fuse_open_out *o =
                    (struct fuse_open_out *)(eo + sizeof(*e));
                memset(eo, 0, sizeof(eo));
                e->nodeid = FILE_INO;
                e->entry_valid = e->attr_valid = 3600;
                make_attr(&e->attr, FILE_INO, S_IFREG | 0644, 0);
                o->fh = FILE_INO;
                reply(fd, uniq, 0, eo, sizeof(eo));
            } else {
                reply(fd, uniq, 0, &oo, sizeof(oo));
            }
            break;
        }
        case FUSE_READ: {
            struct fuse_read_in *ri = in;
            uint32_t sz = ri->size;
            uint64_t remain = (ri->offset < FILE_SIZE) ?
                FILE_SIZE - ri->offset : 0;
            if (sz > remain) sz = (uint32_t)remain;   /* NEVER oversized */
            if (sz > sizeof(readdata)) sz = sizeof(readdata);
            memset(readdata, 'A', sz);
            reply(fd, uniq, 0, readdata, sz);
            break;
        }
        case FUSE_WRITE: {
            struct fuse_write_in *wi = in;
            struct fuse_write_out wo;
            memset(&wo, 0, sizeof(wo));
            wo.size = wi->size;            /* claim full write */
            if (wi->offset + wi->size > FILE_SIZE)
                FILE_SIZE = wi->offset + wi->size;
            reply(fd, uniq, 0, &wo, sizeof(wo));
            break;
        }
        case FUSE_READDIR:
            reply(fd, uniq, 0, dirbuf, dirlen);
            break;
        case FUSE_RENAME: {
            struct fuse_rename_in *ri = in;
            const char *oldn = (const char *)(ri + 1);
            const char *newn = oldn + strlen(oldn) + 1;
            struct nentry *e = nfind(oldn);
            (void)ri;
            if (e) {
                uint64_t ino = e->ino;
                /* drop target if present, move source */
                struct nentry *t = nfind(newn);
                if (t) t->name[0] = 0;
                e->name[0] = 0;
                nadd(newn, ino);
                reply(fd, uniq, 0, NULL, 0);
            } else {
                reply(fd, uniq, -ENOENT, NULL, 0);
            }
            break;
        }
        case FUSE_UNLINK: {
            const char *name = (const char *)in;
            struct nentry *e = nfind(name);
            if (e) e->name[0] = 0;
            reply(fd, uniq, 0, NULL, 0);
            break;
        }
        case FUSE_RMDIR:
        case FUSE_MKDIR:
        case FUSE_MKNOD:
        case FUSE_SYMLINK:
        case FUSE_LINK:
            reply(fd, uniq, 0, NULL, 0);
            break;
        case FUSE_FORGET:
            break;                            /* no reply */
        case FUSE_FLUSH:
        case FUSE_RELEASE:
        case FUSE_RELEASEDIR:
        case FUSE_FSYNC:
            reply(fd, uniq, 0, NULL, 0);
            break;
        default:
            fprintf(stderr, "[daemon] unhandled opcode %u\n", ih->opcode);
            reply(fd, uniq, -ENOSYS, NULL, 0);
            break;
        }
    }
}

int
main(int argc, char **argv)
{
    const char *mnt;
    int fd, status;
    pid_t pid;

    if (argc != 3) {
        fprintf(stderr, "usage: %s <enosys-getattr|vbad-lookup|normal> <mountpoint>\n", argv[0]);
        return 2;
    }
    g_mode = argv[1];
    mnt = argv[2];
    setvbuf(stderr, NULL, _IOLBF, 0);

    fd = open(DEV, O_RDWR);
    if (fd < 0)
        err(1, "open %s", DEV);
    fprintf(stderr, "[main] opened %s fd=%d mode=%s\n", DEV, fd, g_mode);

    mkdir(mnt, 0777);

    pid = fork();
    if (pid < 0)
        err(1, "fork");
    if (pid == 0) {
        serve(fd);
        _exit(0);
    }

    sleep(1);
    struct fuse_mount_info {
        int flags;
        int fd;
        int max_read;
        const char *subtype;
        const char *from;
    } args;
    memset(&args, 0, sizeof(args));
    args.flags = 0;
    args.fd = fd;
    args.max_read = 0;
    args.subtype = NULL;
    args.from = DEV;

    if (mount("fuse", mnt, 0, &args) < 0)
        err(1, "mount fuse on %s", mnt);
    fprintf(stderr, "[main] mounted fuse on %s (daemon pid %d)\n", mnt, pid);

    waitpid(pid, &status, 0);
    fprintf(stderr, "[main] daemon exited status=%d\n", status);
    return 0;
}
