/*
 * Copyright (C) Eugene 'Vindex' Stulin, 2023-2024
 *
 * Distributed under
 * the Boost Software License 1.0 or (at your option)
 * the GNU Lesser General Public License 3.0 or later.
 * This file is offered as-is, without any warranty.
 *
 * This D wrapper was written following some header files
 * of the FUSE library: https://github.com/libfuse/libfuse
 */

/**
 * The module contains basic definitions
 * of functions and structures from *libfuse*.
 */

module oxfuse.cfuse;  // low level wrapper
pragma(lib, "fuse3");

import std.bitmanip : bitfields;

public import core.sys.posix.sys.stat;
public import core.sys.posix.sys.types;
public import core.sys.posix.signal : timespec;
public import core.sys.posix.sys.statvfs : statvfs_t;
public import core.sys.posix.fcntl : flock_t = flock;

extern(C):


/** Handle for a FUSE filesystem */
struct fuse;

struct fuse_pollhandle;

/**
 * Readdir flags, passed to readdir()
 */
enum fuse_readdir_flags {
    /**
     * "Plus" mode.
     *
     * The kernel wants to prefill the inode cache during readdir.  The
     * filesystem may honour this by filling in the attributes and setting
     * FUSE_FILL_DIR_FLAGS for the filler function.  The filesystem may also
     * just ignore this flag completely.
     */
    FUSE_READDIR_PLUS = (1 << 0)
}
private alias frf_t = fuse_readdir_flags;

enum fuse_fill_dir_flags {
    /**
     * "Plus" mode: all file attributes are valid
     *
     * The attributes are used by the kernel to prefill the inode cache
     * during a readdir.
     *
     * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set
     * and vice versa.
     */
    FUSE_FILL_DIR_PLUS = (1 << 1)
}


/** Function to add an entry in a readdir() operation
 *
 * The *offset* parameter can be any non-zero value that enables the
 * filesystem to identify the current point in the directory
 * stream. It does not need to be the actual physical position. A
 * value of zero is reserved to indicate that seeking in directories
 * is not supported.
 * Params:
 *   buf      = the buffer passed to the readdir() operation
 *   name     = the file name of the directory entry
 *   stbuf    = file attributes, can be null
 *   offset   = offset of the next entry or zero
 *   fill_dir = fill flags
 * Returns:
 *   1 if buffer is full, zero otherwise
 */
alias fuse_fill_dir_t = int function(
    void* buf, char* name, stat_t* stbuf, off_t offset, int fill_dir
) nothrow @nogc;
private alias ffd_t = fuse_fill_dir_t;


/**
 * Configuration of the high-level API.
 */
struct fuse_config {
    /**
     * If `set_gid` is non-zero, the st_gid attribute of each file
     * is overwritten with the value of `gid`.
     */
    int set_gid;
    uint gid;

    /**
     * If `set_uid` is non-zero, the st_uid attribute of each file
     * is overwritten with the value of `uid`.
     */
    int set_uid;
    uint uid;

    /**
     * If `set_mode` is non-zero, the any permissions bits set in
     * `umask` are unset in the st_mode attribute of each file.
     */
    int set_mode;
    uint umask;

    /**
     * The timeout in seconds for which name lookups will be
     * cached.
     */
    double entry_timeout;

    /**
     * The timeout in seconds for which a negative lookup will be
     * cached. This means, that if file did not exist (lookup
     * returned ENOENT), the lookup will only be redone after the
     * timeout, and the file/directory will be assumed to not
     * exist until then. A value of zero means that negative
     * lookups are not cached.
     */
    double negative_timeout;

    /**
     * The timeout in seconds for which file/directory attributes
     * (as returned by e.g. the `getattr` handler) are cached.
     */
    double attr_timeout;

    /**
     * Allow requests to be interrupted
     */
    int intr;

    /**
     * Specify which signal number to send to the filesystem when
     * a request is interrupted.  The default is hardcoded to
     * USR1.
     */
    int intr_signal;

    /**
     * Normally, FUSE assigns inodes to paths only for as long as
     * the kernel is aware of them. With this option inodes are
     * instead remembered for at least this many seconds.  This
     * will require more memory, but may be necessary when using
     * applications that make use of inode numbers.
     *
     * A number of -1 means that inodes will be remembered for the
     * entire life-time of the file-system process.
     */
    int remember;

    /**
     * The default behavior is that if an open file is deleted,
     * the file is renamed to a hidden file (.fuse_hiddenXXX), and
     * only removed when the file is finally released.  This
     * relieves the filesystem implementation of having to deal
     * with this problem. This option disables the hiding
     * behavior, and files are removed immediately in an unlink
     * operation (or in a rename operation which overwrites an
     * existing file).
     *
     * It is recommended that you not use the hard_remove
     * option. When hard_remove is set, the following libc
     * functions fail on unlinked files (returning errno of
     * ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2),
     * ftruncate(2), fstat(2), fchmod(2), fchown(2)
     */
    int hard_remove;

    /**
     * Honor the st_ino field in the functions getattr() and
     * fill_dir(). This value is used to fill in the st_ino field
     * in the stat(2), lstat(2), fstat(2) functions and the d_ino
     * field in the readdir(2) function. The filesystem does not
     * have to guarantee uniqueness, however some applications
     * rely on this value being unique for the whole filesystem.
     *
     * Note that this does *not* affect the inode that libfuse
     * and the kernel use internally (also called the "nodeid").
     */
    int use_ino;

    /**
     * If use_ino option is not given, still try to fill in the
     * d_ino field in readdir(2). If the name was previously
     * looked up, and is still in the cache, the inode number
     * found there will be used.  Otherwise it will be set to -1.
     * If use_ino option is given, this option is ignored.
     */
    int readdir_ino;

    /**
     * This option disables the use of page cache (file content cache)
     * in the kernel for this filesystem. This has several affects:
     *
     * 1. Each read(2) or write(2) system call will initiate one
     *    or more read or write operations, data will not be
     *    cached in the kernel.
     *
     * 2. The return value of the read() and write() system calls
     *    will correspond to the return values of the read and
     *    write operations. This is useful for example if the
     *    file size is not known in advance (before reading it).
     *
     * Internally, enabling this option causes fuse to set the
     * `direct_io` field of `struct fuse_file_info` - overwriting
     * any value that was put there by the file system.
     */
    int direct_io;

    /**
     * This option disables flushing the cache of the file
     * contents on every open(2).  This should only be enabled on
     * filesystems where the file data is never changed
     * externally (not through the mounted FUSE filesystem).  Thus
     * it is not suitable for network filesystems and other
     * intermediate filesystems.
     *
     * NOTE: if this option is not specified (and neither
     * direct_io) data is still cached after the open(2), so a
     * read(2) system call will not always initiate a read
     * operation.
     *
     * Internally, enabling this option causes fuse to set the
     * `keep_cache` field of `struct fuse_file_info` - overwriting
     * any value that was put there by the file system.
     */
    int kernel_cache;

    /**
     * This option is an alternative to `kernel_cache`. Instead of
     * unconditionally keeping cached data, the cached data is
     * invalidated on open(2) if if the modification time or the
     * size of the file has changed since it was last opened.
     */
    int auto_cache;

    /**
     * The timeout in seconds for which file attributes are cached
     * for the purpose of checking if auto_cache should flush the
     * file data on open.
     */
    int ac_attr_timeout_set;
    double ac_attr_timeout;

    /**
     * If this option is given the file-system handlers for the
     * following operations will not receive path information:
     * read, write, flush, release, fallocate, fsync, readdir,
     * releasedir, fsyncdir, lock, ioctl and poll.
     *
     * For the truncate, getattr, chmod, chown and utimens
     * operations the path will be provided only if the struct
     * fuse_file_info argument is null.
     */
    int nullpath_ok;

    /**
     * The remaining options are used by libfuse internally and
     * should not be touched.
     */
    int show_help;
    char* modules;
    int debug_;
}


/**
 * Get the current context.
 *
 * The context is only valid for the duration of a filesystem
 * operation, and thus must not be stored and used later.
 *
 * Returns:
 *   the context
 */
fuse_context* fuse_get_context() nothrow @nogc;


/**
 * The file system operations:
 *
 * Most of these should work very similarly to the well known UNIX
 * file system operations.  A major exception is that instead of
 * returning an error in 'errno', the operation should return the
 * negated error value (-errno) directly.
 *
 * All methods are optional, but some are essential for a useful
 * filesystem (e.g. getattr).  Open, flush, release, fsync, opendir,
 * releasedir, fsyncdir, access, create, truncate, lock, init and
 * destroy are special purpose methods, without which a full featured
 * filesystem can still be implemented.
 *
 * In general, all methods are expected to perform any necessary
 * permission checking. However, a filesystem may delegate this task
 * to the kernel by passing the `default_permissions` mount option to
 * `fuse_new()`. In this case, methods will only be called if
 * the kernel's permission check has succeeded.
 *
 * Almost all operations take a path which can be of any length.
 */
struct fuse_operations {
    int function(const char*, stat_t*, ffi_t* fi) getattr;
    int function(const char*, char*, size_t) readlink;
    int function(const char*, mode_t, dev_t) mknod;
    int function(const char*, mode_t) mkdir;
    int function(const char*) unlink;
    int function(const char*) rmdir;
    int function(const char*, const char*) symlink;
    int function(const char *, const char *, uint flags) rename;
    int function(const char*, const char*) link;
    int function(const char*, mode_t, ffi_t* fi) chmod;
    int function(const char*, uid_t, gid_t, ffi_t* fi) chown;
    int function(const char*, off_t, ffi_t* fi) truncate;
    int function(const char*, ffi_t*) open;
    int function(const char*, ubyte*, size_t, off_t, ffi_t*) read;
    int function(const char*, const ubyte*, size_t, off_t, ffi_t*) write;
    int function(const char*, statvfs_t*) statfs;
    int function(const char*, ffi_t*) flush;
    int function(const char*, ffi_t*) release;
    int function(const char*, int, ffi_t*) fsync;
    int function(const char*, const char*, const char*, size_t, int) setxattr;
    int function(const char*, const char*, char*, size_t) getxattr;
    int function(const char*, char*, size_t) listxattr;
    int function(const char*, const char*) removexattr;
    int function(const char*, ffi_t*) opendir;
    int function(const char*, void*, ffd_t, off_t, ffi_t*, frf_t) readdir;
    int function(const char*, ffi_t*) releasedir;
    int function(const char*, int, ffi_t*) fsyncdir;
    void* function(fuse_conn_info*, fuse_config*) init;
    void function(void* private_data) destroy;
    int function(const char*, int) access;
    int function(const char*, mode_t, ffi_t*) create;
    int function(const char*, ffi_t*, int, flock_t*) lock;
    int function(const char*, ref const timespec[2], ffi_t*) utimens;
    int function(const char*, size_t, ulong*) bmap;
    int function(const char*, uint, void*, ffi_t*, uint, void*) ioctl;
    int function(const char*, ffi_t*, fuse_pollhandle*, uint*) poll;
    int function(const char*, fuse_bufvec*, off_t, ffi_t*) write_buf;
    int function(const char*, fuse_bufvec**, size_t, off_t, ffi_t*) read_buf;
    int function(const char*, ffi_t*, int) flock;
    int function(const char*, int, off_t, off_t, ffi_t*) fallocate;
    ssize_t function(
        const char*, ffi_t*, off_t, const char*, ffi_t*, off_t, size_t, int
    ) copy_file_range;
    off_t function(const char*, off_t off, int whence, ffi_t*) lseek;
}


/**
 * Information about an open file.
 *
 * File Handles are created by the open, opendir, and create methods and closed
 * by the release and releasedir methods. Multiple file handles may be
 * concurrently open for the same file. Generally, a client will create one
 * file handle per file descriptor, though in some cases multiple file
 * descriptors can share a single file handle.
 */
struct fuse_file_info {
    /// Open flags. Available in open() and release().
    int flags;

    mixin(bitfields!(
        uint, "writepage", 1,
        uint, "direct_io", 1,
        uint, "keep_cache", 1,
        uint, "flush", 1,
        uint, "nonseekable", 1,
        uint, "flock_release", 1,
        uint, "cache_readdir", 1,
        uint, "noflush", 1,
        uint, "padding", 24
    ));
    uint padding2;

    /**
     * File handle id.
     * May be filled in by filesystem in create, open, and opendir().
     * Available in most other file operations on the same file handle.
     */
    ulong fh;

    /** Lock owner id. Available in locking operations and flush */
    ulong lock_owner;

    /**
     * Requested poll events. Available in poll.
     * Only set on kernels which support it.
     * If unsupported, this field is set to zero.
     */
    uint poll_events;
}
alias ffi_t = fuse_file_info;


/**
 * Extra context that may be needed by some filesystems.
 * The uid, gid and pid fields are not filled in case of a writepage operation.
 */
struct fuse_context {
    .fuse* fuse;  /// pointer to the fuse object
    uid_t uid;  /// user ID of the calling process
    gid_t gid;  /// group ID of the calling process
    pid_t pid;  /// process ID of the calling thread
    void* private_data;  /// private filesystem data
    mode_t umask;  /// umask of the calling process
}


/**
 * Connection information, passed to the init() method
 *
 * Some of the elements are read-write, these can be changed to
 * indicate the value requested by the filesystem.  The requested
 * value must usually be smaller than the indicated value.
 */
struct fuse_conn_info {
    /**
     * Major version of the protocol (read-only)
     */
    uint proto_major;

    /**
     * Minor version of the protocol (read-only)
     */
    uint proto_minor;

    /**
     * Maximum size of the write buffer
     */
    uint max_write;

    /**
     * Maximum size of read requests. A value of zero indicates no
     * limit. However, even if the filesystem does not specify a
     * limit, the maximum size of read requests will still be
     * limited by the kernel.
     *
     * NOTE: For the time being, the maximum size of read requests
     * must be set both here *and* passed to fuse_session_new()
     * using the ``-o max_read=<n>`` mount option. At some point
     * in the future, specifying the mount option will no longer
     * be necessary.
     */
    uint max_read;

    /**
     * Maximum readahead
     */
    uint max_readahead;

    /**
     * Capability flags that the kernel supports (read-only)
     */
    uint capable;

    /**
     * Capability flags that the filesystem wants to enable.
     *
     * libfuse attempts to initialize this field with
     * reasonable default values before calling the init() handler.
     */
    uint want;

    /**
     * Maximum number of pending "background" requests. A
     * background request is any type of request for which the
     * total number is not limited by other means. As of kernel
     * 4.8, only two types of requests fall into this category:
     *
     *   1. Read-ahead requests
     *   2. Asynchronous direct I/O requests
     *
     * Read-ahead requests are generated (if max_readahead is
     * non-zero) by the kernel to preemptively fill its caches
     * when it anticipates that userspace will soon read more
     * data.
     *
     * Asynchronous direct I/O requests are generated if
     * FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large
     * direct I/O request. In this case the kernel will internally
     * split it up into multiple smaller requests and submit them
     * to the filesystem concurrently.
     *
     * Note that the following requests are *not* background
     * requests: writeback requests (limited by the kernel's
     * flusher algorithm), regular (i.e., synchronous and
     * buffered) userspace read/write requests (limited to one per
     * thread), asynchronous read requests (Linux's io_submit(2)
     * call actually blocks, so these are also limited to one per
     * thread).
     */
    uint max_background;

    /**
     * Kernel congestion threshold parameter. If the number of pending
     * background requests exceeds this number, the FUSE kernel module will
     * mark the filesystem as "congested". This instructs the kernel to
     * expect that queued requests will take some time to complete, and to
     * adjust its algorithms accordingly (e.g. by putting a waiting thread
     * to sleep instead of using a busy-loop).
     */
    uint congestion_threshold;

    /**
     * When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible
     * for updating mtime and ctime when write requests are received. The
     * updated values are passed to the filesystem with setattr() requests.
     * However, if the filesystem does not support the full resolution of
     * the kernel timestamps (nanoseconds), the mtime and ctime values used
     * by kernel and filesystem will differ (and result in an apparent
     * change of times after a cache flush).
     *
     * To prevent this problem, this variable can be used to inform the
     * kernel about the timestamp granularity supported by the file-system.
     * The value should be power of 10.  The default is 1, i.e. full
     * nano-second resolution. Filesystems supporting only second resolution
     * should set this to 1000000000.
     */
    uint time_gran;

    /**
     * For future use.
     */
    uint[22] reserved;
}


/**
 * Data buffer vector
 *
 * An array of data buffers, each containing a memory pointer or a
 * file descriptor.
 *
 * Allocate dynamically to add more than one buffer.
 */
struct fuse_bufvec {
    size_t count;  // number of buffers in the array
    size_t idx;  // index of current buffer within the array
    size_t off;  // current offset within the current buffer
    fuse_buf[1] buf;  /// array of buffers
}


/**
 * Buffer flags
 */
enum fuse_buf_flags {
    /**
     * Buffer contains a file descriptor
     *
     * If this flag is set, the .fd field is valid, otherwise the
     * .mem fields is valid.
     */
    FUSE_BUF_IS_FD = (1 << 1),

    /**
     * Seek on the file descriptor
     *
     * If this flag is set then the .pos field is valid and is
     * used to seek to the given offset before performing
     * operation on file descriptor.
     */
    FUSE_BUF_FD_SEEK = (1 << 2),

    /**
     * Retry operation on file descriptor
     *
     * If this flag is set then retry operation on file descriptor
     * until .size bytes have been copied or an error or EOF is
     * detected.
     */
    FUSE_BUF_FD_RETRY = (1 << 3)
}


/**
 * Single data buffer
 *
 * Generic data buffer for I/O, extended attributes, etc...  Data may
 * be supplied as a memory pointer or as a file descriptor
 */
struct fuse_buf {
    size_t size;  /// size of data in bytes
    fuse_buf_flags flags;  /// buffer flags
    void* mem;  /// memory pointer; used unless FUSE_BUF_IS_FD flag is set
    int fd;  /// file descriptor; used if FUSE_BUF_IS_FD flag is set
    off_t pos;  /// file position, used if FUSE_BUF_FD_SEEK is set
}


/**
 * Argument list
 */
struct fuse_args {
    int argc;  /// argument count
    char **argv;  /// argument vector; null terminated
    int allocated;  /// is 'argv' allocated?
}


/**
 * Initializer for 'struct fuse_args'
 */
fuse_args FUSE_ARGS_INIT()(int argc, char** argv) nothrow @nogc {
    return fuse_args(argc, argv, 0);
}


/* ----------------------------------------------------------- *
 * Filesystem setup & teardown                                 *
 * ----------------------------------------------------------- */
struct fuse_cmdline_opts {
    int singlethread;
    int foreground;
    int debug_;
    int nodefault_subtype;
    char* mountpoint;
    int show_version;
    int show_help;
    int clone_fd;
    uint max_idle_threads;
}


/**
 * Main function of FUSE.
 *
 * This is for the lazy.  This is all that has to be called from the
 * main() function.
 *
 * This function does the following:
 *   - parses command line options (-d -s and -h);
 *   - passes relevant mount options to the fuse_mount();
 *   - installs signal handlers for INT, HUP, TERM and PIPE;
 *   - registers an exit handler to unmount the filesystem on program exit;
 *   - creates a fuse handle;
 *   - registers the operations;
 *   - calls either the single-threaded or the multi-threaded event loop.
 *
 * Params:
 *   argc      = the argument counter passed to the main() function
 *   argv      = the argument vector passed to the main() function
 *   op        = the file system operation
 *   userData = user data supplied in the context during the init() method
 * Returns:
 *   0 on success, nonzero on failure
 */
int fuse_main(int argc, char** argv, fuse_operations* op, void* userData)
nothrow @nogc {
    return fuse_main_real(argc, argv, op, fuse_operations.sizeof, userData);
}

private int fuse_main_real(
    int argc,
    char** argv,
    const fuse_operations* op,
    size_t op_size,
    void* user_data
) nothrow @nogc;


/**
 * This function is used to notify that some expected event has occurred.
 * Calling this function initiates a new poll() call.
 */
int fuse_notify_poll(fuse_pollhandle* ph) nothrow @nogc;

/// Destroy poll handle.
void fuse_pollhandle_destroy(fuse_pollhandle* ph);


/// Get the version of the library.
int fuse_version();


/// Get the full package version string of the library.
const(char)* fuse_pkgversion();

