#include "fuse.h"
#include <sys/device.h>
#include <sys/devfs.h>
#include <sys/nlookup.h>
#include <sys/file.h>
#include <sys/sysctl.h>
#include <sys/statvfs.h>
#include <sys/caps.h>
#include <sys/spinlock.h>
#include <sys/spinlock2.h>
int fuse_debug = 0;
SYSCTL_NODE(_vfs, OID_AUTO, fuse, CTLFLAG_RD, 0, "FUSE");
SYSCTL_INT(_vfs_fuse, OID_AUTO, version_major, CTLFLAG_RD, NULL,
FUSE_KERNEL_VERSION, "FUSE kernel version (major)");
SYSCTL_INT(_vfs_fuse, OID_AUTO, version_minor, CTLFLAG_RD, NULL,
FUSE_KERNEL_MINOR_VERSION, "FUSE kernel version (minor)");
SYSCTL_INT(_vfs_fuse, OID_AUTO, debug, CTLFLAG_RW, &fuse_debug, 1, "");
int
fuse_cmp_version(struct fuse_mount *fmp, uint32_t major, uint32_t minor)
{
if (fmp->abi_major == major && fmp->abi_minor == minor)
return 0;
if (fmp->abi_major > major ||
(fmp->abi_major == major && fmp->abi_minor > minor))
return 1;
return -1;
}
int
fuse_mount_kill(struct fuse_mount *fmp)
{
if (!fuse_test_dead(fmp)) {
fuse_set_dead(fmp);
wakeup(fmp);
KNOTE(&fmp->kq.ki_note, 0);
return 0;
}
return -1;
}
int
fuse_mount_free(struct fuse_mount *fmp)
{
if (refcount_release(&fmp->refcnt)) {
fuse_dbg("fmp=%p free\n", fmp);
mtx_uninit(&fmp->ipc_lock);
mtx_uninit(&fmp->mnt_lock);
mtx_uninit(&fmp->ino_lock);
crfree(fmp->cred);
kfree(fmp, M_TEMP);
return 0;
}
fuse_dbg("fmp=%p %u refcnt left\n", fmp, fmp->refcnt);
return -1;
}
static int
fuse_mount(struct mount *mp, char *mntpt, caddr_t data, struct ucred *cred)
{
struct statfs *sbp = &mp->mnt_stat;
struct vnode *devvp;
struct file *file;
struct nlookupdata nd;
struct fuse_mount_info args;
struct fuse_mount *fmp;
struct fuse_ipc *fip;
struct fuse_init_in *fii;
struct fuse_init_out *fio;
char subtype[512];
int error;
if (mp->mnt_flag & MNT_UPDATE)
return EOPNOTSUPP;
error = copyin(data, &args, sizeof(args));
if (error)
return error;
memset(sbp->f_mntfromname, 0, sizeof(sbp->f_mntfromname));
error = copyinstr(args.from, sbp->f_mntfromname,
sizeof(sbp->f_mntfromname), NULL);
if (error)
return error;
memset(sbp->f_mntonname, 0, sizeof(sbp->f_mntonname));
error = copyinstr(mntpt, sbp->f_mntonname, sizeof(sbp->f_mntonname),
NULL);
if (error)
return error;
memset(subtype, 0, sizeof(subtype));
if (args.subtype != NULL) {
error = copyinstr(args.subtype, subtype, sizeof(subtype), NULL);
if (error)
return error;
if (strlen(subtype)) {
strlcat(sbp->f_fstypename, ".", sizeof(sbp->f_fstypename));
strlcat(sbp->f_fstypename, subtype, sizeof(sbp->f_fstypename));
}
}
error = nlookup_init(&nd, sbp->f_mntfromname, UIO_SYSSPACE, NLC_FOLLOW);
if (!error) {
error = nlookup(&nd);
if (!error)
error = cache_vref(&nd.nl_nch, nd.nl_cred, &devvp);
nlookup_done(&nd);
}
if (error)
return error;
if (!devvp)
return ENODEV;
vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY);
error = VOP_ACCESS(devvp, VREAD | VWRITE, cred);
if (error == 0)
error = caps_priv_check(cred, SYSCAP_NOMOUNT_FUSE);
if (error) {
vput(devvp);
return error;
}
vn_unlock(devvp);
fuse_dbg("fd=%d\n", args.fd);
file = holdfp_fdp(curthread->td_proc->p_fd, args.fd, FREAD | FWRITE);
if (!file) {
vrele(devvp);
return EBADF;
}
error = devfs_get_cdevpriv(file, (void**)&fmp);
dropfp(curthread, args.fd, file);
if (error) {
vrele(devvp);
return error;
}
KKASSERT(fmp);
fmp->mp = mp;
fmp->dead = false;
mtx_init(&fmp->mnt_lock, "fuse_mnt_lock");
mtx_init(&fmp->ipc_lock, "fuse_ipc_lock");
mtx_init(&fmp->ino_lock, "fuse_ino_lock");
TAILQ_INIT(&fmp->request_head);
TAILQ_INIT(&fmp->reply_head);
RB_INIT(&fmp->node_head);
fmp->devvp = devvp;
fmp->cred = crhold(cred);
KKASSERT(fmp->refcnt > 0);
refcount_acquire(&fmp->refcnt);
mp->mnt_flag |= MNT_LOCAL;
mp->mnt_kern_flag |= MNTK_ALL_MPSAFE;
mp->mnt_kern_flag |= MNTK_THR_SYNC;
mp->mnt_data = (qaddr_t)fmp;
fuse_node_new(fmp, FUSE_ROOT_ID, VDIR, &fmp->rfnp);
KKASSERT(fmp->rfnp->ino == FUSE_ROOT_ID);
vfs_getnewfsid(mp);
vfs_add_vnodeops(mp, &fuse_vnode_vops, &mp->mnt_vn_norm_ops);
vfs_add_vnodeops(mp, &fuse_spec_vops, &mp->mnt_vn_spec_ops);
fip = fuse_ipc_get(fmp, sizeof(*fii));
fii = fuse_ipc_fill(fip, FUSE_INIT, FUSE_ROOT_ID, NULL);
fii->major = FUSE_KERNEL_VERSION;
fii->minor = FUSE_KERNEL_MINOR_VERSION;
fii->max_readahead = FUSE_BLKSIZE;
error = fuse_ipc_tx(fip);
if (error) {
vrele(devvp);
return error;
}
fio = fuse_out_data(fip);
fmp->abi_major = fio->major;
fmp->abi_minor = fio->minor;
fmp->max_write = fio->max_write;
if (fuse_cmp_version(fmp, 7, 0) < 0) {
fuse_ipc_put(fip);
vrele(devvp);
return EPROTONOSUPPORT;
}
fuse_print("FUSE UABI %d.%d\n", fmp->abi_major, fmp->abi_minor);
fuse_ipc_put(fip);
VFS_STATFS(mp, &mp->mnt_stat, cred);
spin_init(&fmp->helper_spin, "fuse_spin");
TAILQ_INIT(&fmp->bioq);
lwkt_create(fuse_io_thread, fmp, &fmp->helper_td,
NULL, 0, -1, "fuse_helper");
return 0;
}
static int
fuse_unmount(struct mount *mp, int mntflags)
{
struct fuse_mount *fmp = VFSTOFUSE(mp);
struct fuse_ipc *fip;
int error, flags = 0;
mtx_lock(&fmp->mnt_lock);
if (mntflags & MNT_FORCE)
flags |= FORCECLOSE;
error = vflush(mp, 0, flags);
if (error) {
mtx_unlock(&fmp->mnt_lock);
fuse_dbg("vflush error=%d\n", error);
return error;
}
if (!fuse_test_dead(fmp)) {
fuse_dbg("not dead yet, destroying\n");
fip = fuse_ipc_get(fmp, 0);
fuse_ipc_fill(fip, FUSE_DESTROY, FUSE_ROOT_ID, NULL);
if (!fuse_ipc_tx(fip))
fuse_ipc_put(fip);
fuse_mount_kill(fmp);
}
while (fmp->helper_td) {
wakeup(&fmp->helper_td);
tsleep(&fmp->helper_td, 0, "fusehumnt", 2);
}
KKASSERT(fmp->rfnp->vp == NULL);
fuse_node_free(fmp, fmp->rfnp);
fmp->rfnp = NULL;
vn_lock(fmp->devvp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(fmp->devvp, FREAD | FWRITE, NULL);
vn_unlock(fmp->devvp);
vrele(fmp->devvp);
mtx_unlock(&fmp->mnt_lock);
fuse_mount_free(fmp);
mp->mnt_data = NULL;
mp->mnt_flag &= ~MNT_LOCAL;
fuse_dbg("unmount done\n");
return 0;
}
static int fuse_sync_scan1(struct mount *mp, struct vnode *vp, void *data);
static int fuse_sync_scan2(struct mount *mp, struct vnode *vp, void *data);
struct scaninfo {
int rescan;
int waitfor;
int allerror;
};
static int
fuse_sync(struct mount *mp, int waitfor)
{
struct scaninfo scaninfo;
scaninfo.allerror = 0;
scaninfo.rescan = 1;
scaninfo.waitfor = waitfor;
while (scaninfo.rescan) {
scaninfo.rescan = 0;
vmntvnodescan(mp, VMSC_GETVP|VMSC_NOWAIT,
fuse_sync_scan1, fuse_sync_scan2, &scaninfo);
}
return (scaninfo.allerror);
}
static int
fuse_sync_scan1(struct mount *mp, struct vnode *vp, void *data)
{
if (RB_EMPTY(&vp->v_rbdirty_tree))
return -1;
return 0;
}
static int
fuse_sync_scan2(struct mount *mp, struct vnode *vp, void *data)
{
struct scaninfo *info = data;
int error;
if ((error = VOP_FSYNC(vp, info->waitfor, 0)) != 0)
info->allerror = error;
return 0;
}
static int
fuse_root(struct mount *mp, struct vnode **vpp)
{
struct fuse_mount *fmp = VFSTOFUSE(mp);
int error;
KASSERT(fmp->rfnp, ("no root node"));
KKASSERT(fmp->rfnp->fmp);
error = fuse_node_vn(fmp->rfnp, vpp);
if (!error) {
struct vnode *vp = *vpp;
vsetflags(vp, VROOT);
KKASSERT(vp->v_type == VDIR);
}
return error;
}
static int
fuse_statfs(struct mount *mp, struct statfs *sbp, struct ucred *cred)
{
struct fuse_mount *fmp = VFSTOFUSE(mp);
struct fuse_ipc *fip;
struct fuse_statfs_out *fso;
int error;
fip = fuse_ipc_get(fmp, 0);
fuse_ipc_fill(fip, FUSE_STATFS, FUSE_ROOT_ID, cred);
error = fuse_ipc_tx(fip);
if (error)
return error;
fso = fuse_out_data(fip);
mtx_lock(&fmp->mnt_lock);
sbp->f_bsize = fso->st.frsize;
sbp->f_iosize = FUSE_BLKSIZE;
sbp->f_blocks = fso->st.blocks;
sbp->f_bfree = fso->st.bfree;
sbp->f_bavail = fso->st.bavail;
sbp->f_files = fso->st.files;
sbp->f_ffree = fso->st.ffree;
mtx_unlock(&fmp->mnt_lock);
fuse_ipc_put(fip);
return 0;
}
static int
fuse_statvfs(struct mount *mp, struct statvfs *sbp, struct ucred *cred)
{
struct fuse_mount *fmp = VFSTOFUSE(mp);
struct fuse_ipc *fip;
struct fuse_statfs_out *fso;
int error;
fip = fuse_ipc_get(fmp, 0);
fuse_ipc_fill(fip, FUSE_STATFS, FUSE_ROOT_ID, cred);
error = fuse_ipc_tx(fip);
if (error)
return error;
fso = fuse_out_data(fip);
mtx_lock(&fmp->mnt_lock);
sbp->f_bsize = fso->st.frsize;
sbp->f_frsize = FUSE_BLKSIZE;
sbp->f_blocks = fso->st.blocks;
sbp->f_bfree = fso->st.bfree;
sbp->f_bavail = fso->st.bavail;
sbp->f_files = fso->st.files;
sbp->f_ffree = fso->st.ffree;
mtx_unlock(&fmp->mnt_lock);
fuse_ipc_put(fip);
return 0;
}
static int
fuse_init(struct vfsconf *vfsp)
{
int error;
fuse_node_init();
fuse_ipc_init();
error = fuse_device_init();
if (error) {
fuse_ipc_cleanup();
fuse_node_cleanup();
return error;
}
fuse_print("FUSE ABI %d.%d\n", FUSE_KERNEL_VERSION,
FUSE_KERNEL_MINOR_VERSION);
return 0;
}
static int
fuse_uninit(struct vfsconf *vfsp)
{
fuse_ipc_cleanup();
fuse_node_cleanup();
fuse_device_cleanup();
return 0;
}
static struct vfsops fuse_vfsops = {
.vfs_flags = 0,
.vfs_init = fuse_init,
.vfs_uninit = fuse_uninit,
.vfs_mount = fuse_mount,
.vfs_unmount = fuse_unmount,
.vfs_sync = fuse_sync,
.vfs_root = fuse_root,
.vfs_statfs = fuse_statfs,
.vfs_statvfs = fuse_statvfs,
};
VFS_SET(fuse_vfsops, fuse, VFCF_SYNTHETIC | VFCF_MPSAFE);
MODULE_VERSION(fuse, 1);