/* Copyright (C) Eugene 'Vindex' Stulin, 2023
 *
 * This example is written according to the original from libfuse.
 * Distributed under
 * the Boost Software License 1.0 or (at your option)
 * the GNU General Public License 3.0 or later.
 * This file is offered as-is, without any warranty.
 */

/**
 * Simple file system containing a single file.
 * The file name and its content are determined by command line arguments.
 */

module perm_exp;
pragma(lib, "fuse3");

import std.stdio;
import std.conv : o = octal;
import std.getopt;

import core.sys.posix.sys.stat : stat_t, S_IFDIR, S_IFREG;
import core.stdc.errno : ENOENT, EACCES;
import core.sys.posix.fcntl : O_ACCMODE, O_RDONLY;
import core.stdc.string : memcpy;
import std.file;

import oxfuse;


void showHelp(string progname) {
    writefln!"usage: %s [options] <mountpoint>\n"(progname);
    writefln!"%s\n%s\n%s\n%s\n%s"(
        "File-system specific options:",
        "    --name=<s>          Name of the 'hello' file",
        "                        (default: 'hello')",
        "    --contents=<s>      Contents 'hello' file",
        "                        (default 'Hello, World!\\n')"
    );
}

import core.sys.posix.pwd;
import core.sys.posix.grp;
import std.conv : to;
import std.string : fromStringz;
import core.sys.posix.unistd : getuid, geteuid;
string getNameByUID(uint uid) {
    auto p = getpwuid(uid);
    if (p is null) {
        return uid.to!string;
    }
    return (*p).pw_name.fromStringz.to!string;
}


struct ContextMode {
    uid_t uid;
    gid_t gid;
    mode_t umask;
}


ContextMode getContextMode() {
    fuse_context* context = fuse_get_context();
    return ContextMode(context.uid, context.gid, context.umask);
}


class Hello : FileSystem {
    this(string filename, string contents) {
        this.filename = filename;
        this.contents = contents;
    }

    override void initialize(fuse_conn_info* conn, fuse_config* cfg) {
        cfg.kernel_cache = 1;
    }

    override stat_t getattr(string path, ffi_t* fi) {
        writeln("uid:  ", getuid());
        writeln("euid: ", geteuid());
        writeln("name by uid:  ", getNameByUID(getuid()));
        writeln("name by euid: ", getNameByUID(geteuid()));
        writeln("context: ", getContextMode());
        writeln("umask: ", umask(0));
        /* writeln("umask: ", readText("/proc/self/status")); */
        /* writeln("name of caller: ", getNameByUID(callerID)); */
        stat_t st;
        if (path == "/") {
            st.st_mode = S_IFDIR | o!755;
            st.st_nlink = 2;
        } else if (this.filename == path[1 .. $]) {
            st.st_mode = S_IFREG | o!0;
            st.st_nlink = 1;
            st.st_size = this.contents.length;
        } else {
            fe(false, ENOENT);
        }
        return st;
    }

    override string[] readdir(string path, ffi_t* fi) {
        fe(path == "/", ENOENT);
        return [".", "..", this.filename];
    }

    override void open(string path, ffi_t* fi) {
        fe(path[1 .. $] == this.filename, ENOENT);
        fe((fi.flags & O_ACCMODE) == O_RDONLY, EACCES);
    }

    override bool access(string path, int mode) {
        return false;
    }

    override int read(string p, ubyte* buf, size_t size, off_t off, ffi_t* fi) {
        fe(p[1 .. $] == this.filename, ENOENT);
        if (off < contents.length) {
            if (off + size > contents.length) {
                size = contents.length - off;
            }
            memcpy(buf, this.contents.ptr + off, size);
        } else {
            size = 0;
        }
        return cast(int)size;
    }

    private string filename, contents;
}


int main(string[] args) {
    /* writeln("early context: ", getContextMode()); */
    string filename = "hello";
    string contents = "Hello, World!\n";
    auto getoptResult = args.getopt(
        config.passThrough,
        "name", &filename,
        "contents", &contents
    );
    if (getoptResult.helpWanted) {
        showHelp(args[0]);
        showFuseHelp();
        return 0;
    }
    string mountpoint = args[$-1];
    args = args[1 .. $-1] ~ [];
    auto fs = new Hello(filename, contents);
    auto ret = oxfuse.mount("Hello", fs, mountpoint, args);
    if (0 != ret) {
        stderr.writefln!"FUSE has been exited with code %s"(ret);
    }
    return ret;
}

