/*
 * rawfuse.c — Self-contained raw FUSE protocol daemon for DF-0925 PoC.
 *
 * Speaks the FUSE kernel ABI directly over /dev/fuse — NO libfuse needed.
 * Serves a minimal filesystem with one regular file "target" at nodeid 100.
 *
 * Build:  cc -O2 -o rawfuse rawfuse.c
 *
 * Usage:  ./rawfuse /mnt/fuse
 *         (root: opens /dev/fuse, mounts, serves until killed)
 *
 * Protocol: read(2) /dev/fuse -> fuse_in_header + payload;
 *           write(2) /dev/fuse <- fuse_out_header + payload (match unique).
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/ioctl.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

/* ---- FUSE ABI structs (from fuse_abi.h, hardcoded for portability) ---- */

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

enum fuse_opcode {
    FUSE_LOOKUP     = 1,
    FUSE_FORGET     = 2,
    FUSE_GETATTR    = 3,
    FUSE_SETATTR    = 4,
    FUSE_STATFS     = 17,
    FUSE_INIT       = 26,
    FUSE_OPEN       = 14,
    FUSE_READ       = 15,
    FUSE_WRITE      = 16,
    FUSE_FLUSH      = 20,
    FUSE_RELEASE    = 18,
    FUSE_FSYNC      = 3, /* reuse */
    FUSE_OPENDIR    = 27,
    FUSE_READDIR    = 28,
    FUSE_RELEASEDIR = 29,
};

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;
    uint32_t minor;
    uint32_t max_readahead;
    uint32_t flags;
};

struct fuse_init_out {
    uint32_t major;
    uint32_t minor;
    uint32_t max_readahead;
    uint32_t flags;
    uint16_t max_background;
    uint16_t congestion_threshold;
    uint32_t max_write;
    uint32_t time_gran;
    uint16_t max_pages;
    uint16_t padding;
    uint32_t unused[8];
};

struct fuse_attr {
    uint64_t ino;
    uint64_t size;
    uint64_t blocks;
    uint64_t atime;
    uint64_t mtime;
    uint64_t ctime;
    uint32_t atimensec;
    uint32_t mtimensec;
    uint32_t ctimensec;
    uint32_t mode;
    uint32_t nlink;
    uint32_t uid;
    uint32_t gid;
    uint32_t rdev;
    uint32_t blksize;
    uint32_t padding;
};

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

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

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

struct fuse_statfs_out {
    struct fuse_kstatfs st;
};

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

struct fuse_open_in {
    uint32_t flags;
    uint32_t unused;
};

struct fuse_open_out {
    uint64_t fh;
    uint32_t open_flags;
    uint32_t padding;
};

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

struct fuse_release_in {
    uint64_t fh;
    uint32_t flags;
    uint32_t release_flags;
    uint64_t lock_owner;
};

/* fuse_mount_info from <vfs/fuse/fuse_mount.h> */
struct fuse_mount_info {
    int flags;
    int fd;
    int max_read;
    const char *subtype;
    const char *from;
};

#define FUSE_MOUNT_DEFAULT_PERMISSIONS 0x01
#define FUSE_MOUNT_ALLOW_OTHER         0x02

#define TARGET_INO  100
#define TARGET_NAME "target"

static volatile int g_running = 1;

static void
sighandler(int sig)
{
    (void)sig;
    g_running = 0;
}

/* Fill a fuse_attr for the given inode */
static void
fill_attr(struct fuse_attr *a, uint64_t ino)
{
    memset(a, 0, sizeof(*a));
    a->ino = ino;
    a->nlink = 1;
    a->uid = 0;
    a->gid = 0;
    a->blksize = 4096;
    if (ino == FUSE_ROOT_ID) {
        a->mode = 0040755;  /* dir */
        a->nlink = 2;
    } else {
        a->mode = 0100644;  /* regular file */
        a->size = 0;
    }
}

/* Write a reply: out_header + optional payload */
static int
write_reply(int fd, uint64_t unique, int error, const void *payload, size_t plen)
{
    struct {
        struct fuse_out_header ohd;
        char buf[8192];
    } resp;

    memset(&resp.ohd, 0, sizeof(resp.ohd));
    resp.ohd.unique = unique;
    resp.ohd.error = error;
    resp.ohd.len = sizeof(resp.ohd) + plen;
    if (plen > 0 && plen <= sizeof(resp.buf))
        memcpy(resp.buf, payload, plen);

    return write(fd, &resp, resp.ohd.len);
}

static int
write_err(int fd, uint64_t unique, int err)
{
    return write_reply(fd, unique, err, NULL, 0);
}

static void
serve_fuse(int fd)
{
    unsigned char inbuf[65536];
    ssize_t n;

    while (g_running) {
        n = read(fd, inbuf, sizeof(inbuf));
        if (n < 0) {
            if (errno == EINTR) continue;
            perror("read /dev/fuse");
            break;
        }
        if (n < (ssize_t)sizeof(struct fuse_in_header))
            continue;

        struct fuse_in_header *ih = (struct fuse_in_header *)inbuf;
        char *name = (char *)(inbuf + sizeof(*ih));
        uint64_t unique = ih->unique;

        switch (ih->opcode) {
        case 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 = 4096;
            write_reply(fd, unique, 0, &io, sizeof(io));
            break;
        }
        case FUSE_STATFS: {
            struct fuse_statfs_out so;
            memset(&so, 0, sizeof(so));
            so.st.blocks = 1000000;
            so.st.bfree = 500000;
            so.st.bavail = 500000;
            so.st.files = 100;
            so.st.ffree = 50;
            so.st.bsize = 4096;
            so.st.namelen = 255;
            so.st.frsize = 4096;
            write_reply(fd, unique, 0, &so, sizeof(so));
            break;
        }
        case FUSE_LOOKUP: {
            /* name is null-terminated in the request */
            if (name && strcmp(name, TARGET_NAME) == 0) {
                struct fuse_entry_out eo;
                memset(&eo, 0, sizeof(eo));
                eo.nodeid = TARGET_INO;
                eo.generation = 1;
                eo.entry_valid = 1;
                eo.attr_valid = 1;
                fill_attr(&eo.attr, TARGET_INO);
                write_reply(fd, unique, 0, &eo, sizeof(eo));
            } else if (name && (strcmp(name, ".") == 0 ||
                                strcmp(name, "..") == 0)) {
                struct fuse_entry_out eo;
                memset(&eo, 0, sizeof(eo));
                eo.nodeid = FUSE_ROOT_ID;
                eo.generation = 1;
                eo.entry_valid = 1;
                eo.attr_valid = 1;
                fill_attr(&eo.attr, FUSE_ROOT_ID);
                write_reply(fd, unique, 0, &eo, sizeof(eo));
            } else {
                write_err(fd, unique, -ENOENT);
            }
            break;
        }
        case FUSE_GETATTR: {
            struct fuse_attr_out ao;
            memset(&ao, 0, sizeof(ao));
            ao.attr_valid = 1;
            fill_attr(&ao.attr, ih->nodeid);
            write_reply(fd, unique, 0, &ao, sizeof(ao));
            break;
        }
        case FUSE_OPEN:
        case FUSE_OPENDIR: {
            struct fuse_open_out oo;
            memset(&oo, 0, sizeof(oo));
            oo.fh = ih->nodeid;
            write_reply(fd, unique, 0, &oo, sizeof(oo));
            break;
        }
        case FUSE_READ: {
            /* Return empty data */
            write_reply(fd, unique, 0, NULL, 0);
            break;
        }
        case FUSE_READDIR: {
            /* Return empty dir */
            write_reply(fd, unique, 0, NULL, 0);
            break;
        }
        case FUSE_RELEASE:
        case FUSE_RELEASEDIR:
        case FUSE_FLUSH:
            write_reply(fd, unique, 0, NULL, 0);
            break;
        case FUSE_FORGET:
            /* No reply */
            break;
        default:
            write_err(fd, unique, -ENOSYS);
            break;
        }
    }
}

int
main(int argc, char **argv)
{
    const char *mntpt;
    int fusefd;
    pid_t pid;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <mountpoint>\n", argv[0]);
        return 1;
    }
    mntpt = argv[1];

    signal(SIGTERM, sighandler);
    signal(SIGINT, sighandler);

    /* Open /dev/fuse — creates the per-open fuse_mount context */
    fusefd = open("/dev/fuse", O_RDWR);
    if (fusefd < 0) {
        perror("open /dev/fuse");
        fprintf(stderr, "Is the fuse module loaded? (kldload fuse)\n");
        return 1;
    }

    /* Fork: child serves FUSE protocol, parent mounts */
    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    }

    if (pid == 0) {
        /* Child: serve FUSE requests on fusefd */
        serve_fuse(fusefd);
        _exit(0);
    }

    /* Parent: mount the filesystem using the fd */
    struct fuse_mount_info args;
    memset(&args, 0, sizeof(args));
    args.fd = fusefd;
    args.from = "/dev/fuse";
    args.flags = 0;

    if (mount("fuse", mntpt, 0, &args) < 0) {
        perror("mount fuse");
        kill(pid, SIGTERM);
        return 1;
    }

    fprintf(stderr, "rawfuse: mounted %s (fusefd=%d, server pid=%d)\n",
            mntpt, fusefd, pid);

    /* Wait for signal; child keeps serving */
    /* The child process holds the fd and serves forever.
     * Parent can exit — the mount stays up as long as the child runs. */
    /* Actually, keep parent alive so we can unmount cleanly */
    int status;
    waitpid(pid, &status, 0);

    /* Unmount */
    unmount(mntpt, 0);
    fprintf(stderr, "rawfuse: unmounted %s\n", mntpt);

    return 0;
}
