/*
 * Mount 2.xBSD filesystem via FUSE interface.
 *
 * Copyright (C) 2014 Serge Vakulenko, <serge@vak.ru>
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>

#ifdef ENABLE_FUSE
#include <fuse.h>

#include "bsdfs.h"

extern int verbose;

/*
 * Print a message to log file.
 */
static void printlog(const char *format, ...)
{
    va_list ap;

    if (verbose) {
        va_start(ap, format);
        vfprintf(stderr, format, ap);
        va_end(ap);
        fflush(stderr);
    }
}

static dev_t make_rdev(unsigned raw)
{
    return makedev (raw >> 8, raw & 0xff);
}

/*
 * Copy data to struct stat.
 */
static int getstat (fs_inode_t *inode, struct stat *statbuf)
{
    statbuf->st_mode   = inode->mode & 07777;    /* protection */
    statbuf->st_ino    = inode->number;          /* inode number */
    statbuf->st_nlink  = inode->nlink;           /* number of hard links */
    statbuf->st_uid    = inode->uid;             /* user ID of owner */
    statbuf->st_gid    = inode->gid;             /* group ID of owner */
    statbuf->st_rdev   = 0;                      /* device ID (if special file) */
    statbuf->st_size   = inode->size;            /* total size, in bytes */
    statbuf->st_blocks = inode->size >> 9;       /* number of blocks allocated */
    statbuf->st_atime  = inode->atime;           /* time of last access */
    statbuf->st_mtime  = inode->mtime;           /* time of last modification */
    statbuf->st_ctime  = inode->ctime;           /* time of last status change */

    switch (inode->mode & INODE_MODE_FMT) {      /* type of file */
    case INODE_MODE_FREG:                       /* regular */
        statbuf->st_mode |= S_IFREG;
        break;
    case INODE_MODE_FDIR:                       /* directory */
        statbuf->st_mode |= S_IFDIR;
        break;
    case INODE_MODE_FCHR:                       /* character special */
        statbuf->st_mode |= S_IFCHR;
        statbuf->st_rdev = make_rdev (inode->addr[1]);
        break;
    case INODE_MODE_FBLK:                       /* block special */
        statbuf->st_mode |= S_IFBLK;
        statbuf->st_rdev = make_rdev (inode->addr[1]);
        break;
    case INODE_MODE_FLNK:                       /* symbolic link */
        statbuf->st_mode |= S_IFLNK;
        break;
    case INODE_MODE_FSOCK:                      /* socket */
        statbuf->st_mode |= S_IFSOCK;
        break;
    default:                                    /* cannot happen */
        printlog("--- unknown file type %#x\n", inode->mode & INODE_MODE_FMT);
        return -ENOENT;
    }
    return 0;
}

/*
 * Get file attributes.
 */
int op_getattr(const char *path, struct stat *statbuf)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_getattr(path=\"%s\", statbuf=%p)\n", path, statbuf);

    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- search failed\n");
        return -ENOENT;
    }
    return getstat (&inode, statbuf);
}

/*
 * Get attributes from an open file.
 */
int op_fgetattr(const char *path, struct stat *statbuf, struct fuse_file_info *fi)
{
    fs_file_t *fh = (fs_file_t*) (intptr_t) fi->fh;

    printlog("--- op_fgetattr(path=\"%s\", statbuf=%p, fi=%p)\n",
        path, statbuf, fi);

    if (strcmp(path, "/") == 0)
	return op_getattr(path, statbuf);

    if (! fh)
        return -EBADF;

    return getstat (&fh->inode, statbuf);
}

/*
 * File open operation.
 */
int op_open(const char *path, struct fuse_file_info *fi)
{
    fs_t *fs = fuse_get_context()->private_data;
    int write_flag = (fi->flags & O_ACCMODE) != O_RDONLY;

    printlog("--- op_open(path=\"%s\", fi=%p) flags=%#x \n",
        path, fi, fi->flags);

    fs_file_t *fh = calloc (1, sizeof(fs_file_t));
    if (! fh) {
        printlog("--- out of memory\n");
        return -ENOMEM;
    }

    if (! fs_file_open (fs, fh, path, write_flag)) {
        printlog("--- open failed\n");
        free (fh);
        fi->fh = 0;
        return -ENOENT;
    }

    if ((fh->inode.mode & INODE_MODE_FMT) != INODE_MODE_FREG) {
        /* Cannot open special files. */
        free (fh);
        fi->fh = 0;
        return -ENXIO;
    }

    if (fi->flags & O_APPEND) {
        fh->offset = fh->inode.size;
    }
    fi->fh = (intptr_t) fh;
    return 0;
}

/*
 * Create and open a file.
 */
int op_create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
    fs_t *fs = fuse_get_context()->private_data;

    printlog("--- op_create(path=\"%s\", mode=0%03o, fi=%p)\n",
        path, mode, fi);

    fs_file_t *fh = calloc (1, sizeof(fs_file_t));
    if (! fh) {
        printlog("--- out of memory\n");
        return -ENOMEM;
    }

    if (! fs_file_create (fs, fh, path, mode & 07777)) {
        printlog("--- create failed\n");
	if ((fh->inode.mode & INODE_MODE_FMT) == INODE_MODE_FDIR) {
            free (fh);
            fi->fh = 0;
            return -EISDIR;
        }
        free (fh);
        fi->fh = 0;
        return -EIO;
    }
    fh->inode.mtime = time(0);
    fh->inode.dirty = 1;
    fs_file_close (fh);
    fi->fh = (intptr_t) fh;
    return 0;
}

/*
 * Read data from an open file.
 */
int op_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
    fs_file_t *fh = (fs_file_t*) (intptr_t) fi->fh;

    printlog("--- op_read(path=\"%s\", buf=%p, size=%d, offset=%lld, fi=%p)\n",
        path, buf, size, offset, fi);

    if (offset >= fh->inode.size)
        return 0;

    fh->offset = offset;
    if (size > fh->inode.size - offset)
        size = fh->inode.size - offset;

    if (! fs_file_read (fh, (unsigned char*) buf, size)) {
        printlog("--- read failed\n");
        return -EIO;
    }
    printlog("--- read returned %u\n", size);
    return size;
}

/*
 * Write data to an open file.
 */
int op_write(const char *path, const char *buf, size_t size, off_t offset,
	     struct fuse_file_info *fi)
{
    fs_file_t *fh = (fs_file_t*) (intptr_t) fi->fh;

    printlog("--- op_write(path=\"%s\", buf=%p, size=%d, offset=%lld, fi=%p)\n",
        path, buf, size, offset, fi);

    fh->offset = offset;
    if (! fs_file_write (fh, (unsigned char*) buf, size)) {
        printlog("--- read failed\n");
        return -EIO;
    }
    fh->inode.mtime = time(0);
    fh->inode.dirty = 1;
    return size;
}

/*
 * Release an open file.
 */
int op_release(const char *path, struct fuse_file_info *fi)
{
    fs_file_t *fh = (fs_file_t*) (intptr_t) fi->fh;

    printlog("--- op_release(path=\"%s\", fi=%p)\n", path, fi);

    if (! fh)
        return -EBADF;

    fs_file_close (fh);
    free (fh);
    fi->fh = 0;
    return 0;
}

/*
 * Change the size of a file
 */
int op_truncate(const char *path, off_t newsize)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_file_t f;

    printlog("--- op_truncate(path=\"%s\", newsize=%lld)\n", path, newsize);

    if (! fs_file_open (fs, &f, path, 1)) {
        printlog("--- open failed\n");
        return -ENOENT;
    }

    if ((f.inode.mode & INODE_MODE_FMT) != INODE_MODE_FREG) {
        /* Cannot truncate special files. */
        return -EINVAL;
    }
    fs_inode_truncate (&f.inode, newsize);
    f.inode.mtime = time(0);
    f.inode.dirty = 1;
    fs_file_close (&f);
    return 0;
}

/*
 * Change the size of an open file.
 */
int op_ftruncate(const char *path, off_t offset, struct fuse_file_info *fi)
{
    fs_file_t *fh = (fs_file_t*) (intptr_t) fi->fh;

    printlog("--- op_ftruncate(path=\"%s\", offset=%lld, fi=%p)\n",
        path, offset, fi);

    if (! fh->writable)
        return -EACCES;

    if ((fh->inode.mode & INODE_MODE_FMT) != INODE_MODE_FREG) {
        /* Cannot truncate special files. */
        return -EINVAL;
    }
    fs_inode_truncate (&fh->inode, offset);
    fh->inode.mtime = time(0);
    fh->inode.dirty = 1;
    fs_file_close (fh);
    return 0;
}

/*
 * Remove a file.
 */
int op_unlink(const char *path)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_unlink(path=\"%s\")\n", path);

    /* Get the file type. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- search failed\n");
        return -ENOENT;
    }
    if ((inode.mode & INODE_MODE_FMT) == INODE_MODE_FDIR) {
        /* Cannot unlink directories. */
        return -EISDIR;
    }

    /* Delete file. */
    if (! fs_inode_delete (fs, &inode, path)) {
        printlog("--- delete failed\n");
        return -EIO;
    }
    fs_inode_save (&inode, 1);
    return 0;
}

/*
 * Remove a directory.
 */
int op_rmdir(const char *path)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode, parent;
    char buf [BSDFS_BSIZE], *p;

    printlog("--- op_rmdir(path=\"%s\")\n", path);

    /* Get the file type. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- search failed\n");
        return -ENOENT;
    }
    if ((inode.mode & INODE_MODE_FMT) != INODE_MODE_FDIR) {
        /* Cannot remove files. */
        return -ENOTDIR;
    }
    if (inode.nlink > 2) {
        /* Cannot remove non-empty directories. */
        return -ENOTEMPTY;
    }

    /* Open parent directory. */
    strcpy (buf, path);
    p = strrchr (buf, '/');
    if (p)
        *p = 0;
    else
        *buf = 0;
    if (! fs_inode_lookup (fs, &parent, buf)) {
        printlog("--- parent not found\n");
        return -ENOENT;
    }

    /* Delete directory.
     * Need to decrease a link count first. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- directory not found\n");
        return -ENOENT;
    }
    --inode.nlink;
    fs_inode_save (&inode, 1);
    if (! fs_inode_delete (fs, &inode, path)) {
        printlog("--- delete failed\n");
        return -EIO;
    }
    fs_inode_save (&inode, 1);

    /* Decrease a parent's link counter. */
    if (! fs_inode_get (fs, &parent, parent.number)) {
        printlog("--- cannot reopen parent\n");
        return -EIO;
    }
    --parent.nlink;
    fs_inode_save (&parent, 1);
    return 0;
}

/*
 * Create a directory.
 */
int op_mkdir(const char *path, mode_t mode)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t dir, parent;
    char buf [BSDFS_BSIZE], *p;

    printlog("--- op_mkdir(path=\"%s\", mode=0%3o)\n", path, mode);

    /* Open parent directory. */
    strcpy (buf, path);
    p = strrchr (buf, '/');
    if (p)
        *p = 0;
    else
        *buf = 0;
    if (! fs_inode_lookup (fs, &parent, buf)) {
        printlog("--- parent not found\n");
        return -ENOENT;
    }

    /* Create directory. */
    mode &= 07777;
    mode |= INODE_MODE_FDIR;
    int done = fs_inode_create (fs, &dir, path, mode);
    if (! done) {
        printlog("--- cannot create dir inode\n");
        return -ENOENT;
    }
    if (done == 1) {
        /* The directory already existed. */
        return -EEXIST;
    }
    fs_inode_save (&dir, 0);

    /* Make parent link '..' */
    strcpy (buf, path);
    strcat (buf, "/..");
    if (! fs_inode_link (fs, &dir, buf, parent.number)) {
        printlog("--- dotdot link failed\n");
        return -EIO;
    }
    if (! fs_inode_get (fs, &parent, parent.number)) {
        printlog("--- cannot reopen parent\n");
        return -EIO;
    }
    ++parent.nlink;
    fs_inode_save (&parent, 1);
    return 0;
}

/*
 * Create a hard link to a file.
 */
int op_link(const char *path, const char *newpath)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t source, target;

    printlog("--- op_link(path=\"%s\", newpath=\"%s\")\n", path, newpath);

    /* Find source. */
    if (! fs_inode_lookup (fs, &source, path)) {
        printlog("--- source not found\n");
        return -ENOENT;
    }

    if ((source.mode & INODE_MODE_FMT) == INODE_MODE_FDIR) {
        /* Cannot link directories. */
        return -EPERM;
    }

    /* Create target link. */
    if (! fs_inode_link (fs, &target, newpath, source.number)) {
        printlog("--- link failed\n");
        return -EIO;
    }
    source.nlink++;
    fs_inode_save (&source, 1);
    return 0;
}

/*
 * Rename a file.
 */
int op_rename(const char *path, const char *newpath)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t source, target;

    printlog("--- op_rename(path=\"%s\", newpath=\"%s\")\n", path, newpath);

    /* Find source and increase the link count. */
    if (! fs_inode_lookup (fs, &source, path)) {
        printlog("--- source not found\n");
        return -ENOENT;
    }
    source.nlink++;
    fs_inode_save (&source, 1);

    /* Create target link. */
    if (! fs_inode_link (fs, &target, newpath, source.number)) {
        printlog("--- link failed\n");
        return -EIO;
    }

    /* Delete the source. */
    if (! fs_inode_delete (fs, &source, path)) {
        printlog("--- delete failed\n");
        return -EIO;
    }
    fs_inode_save (&source, 1);
    return 0;
}

/*
 * Create a file node.
 */
int op_mknod(const char *path, mode_t mode, dev_t dev)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_mknod(path=\"%s\", mode=0%3o, dev=%lld)\n",
        path, mode, dev);

    /* Check if the file already exists. */
    if (fs_inode_lookup (fs, &inode, path)) {
        printlog("--- already exists\n");
        return -EEXIST;
    }

    /* Encode a mode bitmask. */
    if (S_ISREG(mode)) {
        mode = (mode & 07777) | INODE_MODE_FREG;
    } else if (S_ISCHR(mode)) {
        mode = (mode & 07777) | INODE_MODE_FCHR;
    } else if (S_ISBLK(mode)) {
        mode = (mode & 07777) | INODE_MODE_FBLK;
    } else
        return -EINVAL;

    /* Create the file. */
    if (! fs_inode_create (fs, &inode, path, mode)) {
        printlog("--- create failed\n");
        return -EIO;
    }
    if (S_ISCHR(mode) || S_ISBLK(mode)) {
        inode.addr[1] = major(dev) << 8 | minor(dev);
    }
    inode.mtime = time(0);
    inode.dirty = 1;
    if (! fs_inode_save (&inode, 0)) {
        printlog("--- create failed\n");
        return -EIO;
    }
    return 0;
}

/*
 * Read the target of a symbolic link.
 */
int op_readlink(const char *path, char *link, size_t size)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_readlink(path=\"%s\", link=\"%s\", size=%d)\n",
        path, link, size);

    /* Open the file. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- file not found\n");
        return -ENOENT;
    }

    if ((inode.mode & INODE_MODE_FMT) != INODE_MODE_FLNK)
        return -EINVAL;

    /* Leave one byte for the terminating null. */
    if (size > inode.size + 1)
        size = inode.size + 1;
    if (! fs_inode_read (&inode, 0, (unsigned char*)link, size-1)) {
        printlog("--- read failed\n");
        return -EIO;
    }
    link[size-1] = 0;
    return 0;
}

/*
 * Create a symbolic link.
 */
int op_symlink(const char *path, const char *newpath)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;
    int len, mode;

    printlog("--- op_symlink(path=\"%s\", newpath=\"%s\")\n", path, newpath);

    /* Check if the file already exists. */
    if (fs_inode_lookup (fs, &inode, newpath)) {
        printlog("--- already exists\n");
        return -EEXIST;
    }

    /* Create symlink. */
    mode = 0777 | INODE_MODE_FLNK;
    if (! fs_inode_create (fs, &inode, newpath, mode)) {
        printlog("--- create failed\n");
        return -EIO;
    }
    fs_inode_save (&inode, 0);

    len = strlen (path);
    if (! fs_inode_write (&inode, 0, (unsigned char*)path, len)) {
        printlog("--- write failed\n");
        return -EIO;
    }
    inode.mtime = time(0);
    inode.dirty = 1;
    if (! fs_inode_save (&inode, 0)) {
        printlog("--- create failed\n");
        return -EIO;
    }
    return 0;
}

/*
 * Change the permission bits of a file.
 */
int op_chmod(const char *path, mode_t mode)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_chmod(path=\"%s\", mode=0%03o)\n", path, mode);

    /* Open the file. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- file not found\n");
        return -ENOENT;
    }

    /* Modify the access mode. */
    inode.mode &= ~07777;
    inode.mode |= mode;
    inode.dirty = 1;
    fs_inode_save (&inode, 0);
    return 0;
}

/*
 * Change the owner and group of a file.
 */
int op_chown(const char *path, uid_t uid, gid_t gid)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_chown(path=\"%s\", uid=%d, gid=%d)\n", path, uid, gid);

    /* Open the file. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- file not found\n");
        return -ENOENT;
    }

    /* Modify the owner and group. */
    inode.uid = uid;
    inode.gid = gid;
    inode.dirty = 1;
    fs_inode_save (&inode, 0);
    return 0;
}

/*
 * Change the access and/or modification times of a file.
 */
int op_utime(const char *path, struct utimbuf *ubuf)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t inode;

    printlog("--- op_utime(path=\"%s\", ubuf=%p)\n", path, ubuf);

    /* Open the file. */
    if (! fs_inode_lookup (fs, &inode, path)) {
        printlog("--- file not found\n");
        return -ENOENT;
    }

    /* Modify the access and modification times. */
    inode.atime = ubuf->actime;
    inode.mtime = ubuf->modtime;
    inode.dirty = 1;
    fs_inode_save (&inode, 0);
    return 0;
}

/*
 * Get file system statistics.
 *
 * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored
 */
int op_statfs(const char *path, struct statvfs *statv)
{
    fs_dirent_t dirent;

    printlog("--- op_statfs(path=\"%s\", statv=%p)\n", path, statv);

    /* The maximum length in bytes of a file name on this file system. */
    statv->f_namemax = sizeof(dirent.name) - 1;

    /* The preferred length of I/O requests for files on this file system. */
    statv->f_bsize = BSDFS_BSIZE;
    return 0;
}

/*
 * Possibly flush cached data.
 */
int op_flush(const char *path, struct fuse_file_info *fi)
{
    printlog("--- op_flush(path=\"%s\", fi=%p)\n", path, fi);
    return 0;
}

/*
 * Synchronize file contents.
 * If the datasync parameter is non-zero, then only the user data
 * should be flushed, not the meta data.
 */
int op_fsync(const char *path, int datasync, struct fuse_file_info *fi)
{
    fs_file_t *fh = (fs_file_t*) (intptr_t) fi->fh;

    printlog("--- op_fsync(path=\"%s\", datasync=%d, fi=%p)\n",
        path, datasync, fi);

    if (datasync == 0 && fh->writable) {
        if (! fs_inode_save (&fh->inode, 0))
            return -EIO;
    }
    return 0;
}

/*
 * Open directory.
 */
int op_opendir(const char *path, struct fuse_file_info *fi)
{
    printlog("--- op_opendir(path=\"%s\", fi=%p)\n", path, fi);
    return 0;
}

/*
 * Read directory.
 */
int op_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
	       struct fuse_file_info *fi)
{
    fs_t *fs = fuse_get_context()->private_data;
    fs_inode_t dir;
    char name [BSDFS_BSIZE - 12];
    struct {
        unsigned int inum;
        unsigned short reclen;
        unsigned short namlen;
    } dirent;

    printlog("--- op_readdir(path=\"%s\", buf=%p, filler=%p, offset=%lld, fi=%p)\n",
        path, buf, filler, offset, fi);

    if (! fs_inode_lookup (fs, &dir, path)) {
        printlog("--- cannot find path %s\n", path);
        return -ENOENT;
    }

    /* Copy the entire directory into the buffer. */
    for (offset = 0; offset < dir.size; offset += dirent.reclen) {
        if (! fs_inode_read (&dir, offset, (unsigned char*) &dirent, sizeof(dirent))) {
            printlog("--- read error at offset %ld\n", offset);
            return -EIO;
        }
        //printlog("--- readdir offset %lu: inum=%u, reclen=%u, namlen=%u\n", offset, dirent.inum, dirent.reclen, dirent.namlen);

        if (! fs_inode_read (&dir, offset+sizeof(dirent),
            (unsigned char*)name, (dirent.namlen + 4) / 4 * 4))
        {
            printlog("--- name read error at offset %ld\n", offset);
            return -EIO;
        }
        //printlog("--- readdir offset %lu: name='%s'\n", offset, name);

        if (dirent.inum != 0) {
            //printlog("calling filler with name %s\n", name);
            name[dirent.namlen] = 0;
            if (filler(buf, name, NULL, 0) != 0) {
                printlog("    ERROR op_readdir filler: buffer full");
                return -ENOMEM;
            }
        }
    }
    return 0;
}

/*
 * Release directory.
 */
int op_releasedir(const char *path, struct fuse_file_info *fi)
{
    printlog("--- op_releasedir(path=\"%s\", fi=%p)\n", path, fi);
    return 0;
}

/*
 * Clean up filesystem.
 * Called on filesystem exit.
 */
void op_destroy(void *userdata)
{
    printlog("--- op_destroy(userdata=%p)\n", userdata);
}

/*
 * Check file access permissions.
 */
int op_access(const char *path, int mask)
{
    printlog("--- op_access(path=\"%s\", mask=0%o)\n", path, mask);

    /* Always permitted. */
    return 0;
}

static struct fuse_operations mount_ops = {
    .access     = op_access,
    .chmod      = op_chmod,
    .chown      = op_chown,
    .create     = op_create,
    .destroy    = op_destroy,
    .fgetattr   = op_fgetattr,
    .flush      = op_flush,
    .fsync      = op_fsync,
    .ftruncate  = op_ftruncate,
    .getattr    = op_getattr,
    .link       = op_link,
    .mkdir      = op_mkdir,
    .mknod      = op_mknod,
    .open       = op_open,
    .opendir    = op_opendir,
    .readdir    = op_readdir,
    .readlink   = op_readlink,
    .read       = op_read,
    .release    = op_release,
    .releasedir = op_releasedir,
    .rename     = op_rename,
    .rmdir      = op_rmdir,
    .statfs     = op_statfs,
    .symlink    = op_symlink,
    .truncate   = op_truncate,
    .unlink     = op_unlink,
    .utime      = op_utime,
    .write      = op_write,
};

int fs_mount(fs_t *fs, char *dirname)
{
    char *av[8];
    int ret, ac;

    printf ("Filesystem mounted as %s\n", dirname);
    printf ("Press ^C to unmount\n");

    /* Invoke FUSE to mount the filesystem. */
    ac = 0;
    av[ac++] = "fsutil";
    av[ac++] = "-f";                    /* foreground */
    av[ac++] = "-s";                    /* single-threaded */
    if (verbose > 1)
        av[ac++] = "-d";                /* debug */
    av[ac++] = dirname;
    av[ac] = 0;
    ret = fuse_main(ac, av, &mount_ops, fs);
    if (ret != 0) {
        perror ("fuse_main failed");
        return -1;
    }
    fs_sync (fs, 0);
    fs_close (fs);
    printf ("\nFilesystem %s unmounted\n", dirname);
    return ret;
}

#endif /* ENABLE_FUSE */
