aboutsummaryrefslogtreecommitdiff
path: root/sys/fs/fuse/fuse_vfsops.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/fs/fuse/fuse_vfsops.c')
-rw-r--r--sys/fs/fuse/fuse_vfsops.c242
1 files changed, 202 insertions, 40 deletions
diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c
index bf54df32e743..8b25234ded4b 100644
--- a/sys/fs/fuse/fuse_vfsops.c
+++ b/sys/fs/fuse/fuse_vfsops.c
@@ -81,7 +81,6 @@ __FBSDID("$FreeBSD$");
#include <sys/fcntl.h>
#include "fuse.h"
-#include "fuse_param.h"
#include "fuse_node.h"
#include "fuse_ipc.h"
#include "fuse_internal.h"
@@ -89,13 +88,13 @@ __FBSDID("$FreeBSD$");
#include <sys/priv.h>
#include <security/mac/mac_framework.h>
-SDT_PROVIDER_DECLARE(fuse);
+SDT_PROVIDER_DECLARE(fusefs);
/*
* Fuse trace probe:
* arg0: verbosity. Higher numbers give more verbose messages
* arg1: Textual message
*/
-SDT_PROBE_DEFINE2(fuse, , vfsops, trace, "int", "char*");
+SDT_PROBE_DEFINE2(fusefs, , vfsops, trace, "int", "char*");
/* This will do for privilege types for now */
#ifndef PRIV_VFS_FUSE_ALLOWOTHER
@@ -108,29 +107,27 @@ SDT_PROBE_DEFINE2(fuse, , vfsops, trace, "int", "char*");
#define PRIV_VFS_FUSE_SYNC_UNMOUNT PRIV_VFS_MOUNT_NONUSER
#endif
+static vfs_fhtovp_t fuse_vfsop_fhtovp;
static vfs_mount_t fuse_vfsop_mount;
static vfs_unmount_t fuse_vfsop_unmount;
static vfs_root_t fuse_vfsop_root;
static vfs_statfs_t fuse_vfsop_statfs;
+static vfs_vget_t fuse_vfsop_vget;
struct vfsops fuse_vfsops = {
+ .vfs_fhtovp = fuse_vfsop_fhtovp,
.vfs_mount = fuse_vfsop_mount,
.vfs_unmount = fuse_vfsop_unmount,
.vfs_root = fuse_vfsop_root,
.vfs_statfs = fuse_vfsop_statfs,
+ .vfs_vget = fuse_vfsop_vget,
};
-SYSCTL_INT(_vfs_fusefs, OID_AUTO, init_backgrounded, CTLFLAG_RD,
- SYSCTL_NULL_INT_PTR, 1, "indicate async handshake");
static int fuse_enforce_dev_perms = 0;
SYSCTL_INT(_vfs_fusefs, OID_AUTO, enforce_dev_perms, CTLFLAG_RW,
&fuse_enforce_dev_perms, 0,
"enforce fuse device permissions for secondary mounts");
-static unsigned sync_unmount = 1;
-
-SYSCTL_UINT(_vfs_fusefs, OID_AUTO, sync_unmount, CTLFLAG_RW,
- &sync_unmount, 0, "specify when to use synchronous unmount");
MALLOC_DEFINE(M_FUSEVFS, "fuse_filesystem", "buffer for fuse vfs layer");
@@ -208,11 +205,90 @@ fuse_getdevice(const char *fspec, struct thread *td, struct cdev **fdevp)
vfs_flagopt(opts, "__" #fnam, &__mntopts, fval); \
} while (0)
-SDT_PROBE_DEFINE1(fuse, , vfsops, mntopts, "uint64_t");
-SDT_PROBE_DEFINE4(fuse, , vfsops, mount_err, "char*", "struct fuse_data*",
+SDT_PROBE_DEFINE1(fusefs, , vfsops, mntopts, "uint64_t");
+SDT_PROBE_DEFINE4(fusefs, , vfsops, mount_err, "char*", "struct fuse_data*",
"struct mount*", "int");
static int
+fuse_vfs_remount(struct mount *mp, struct thread *td, uint64_t mntopts,
+ uint32_t max_read, int daemon_timeout)
+{
+ int err = 0;
+ struct fuse_data *data = fuse_get_mpdata(mp);
+ /* Don't allow these options to be changed */
+ const static unsigned long long cant_update_opts =
+ MNT_USER; /* Mount owner must be the user running the daemon */
+
+ FUSE_LOCK();
+
+ if ((mp->mnt_flag ^ data->mnt_flag) & cant_update_opts) {
+ err = EOPNOTSUPP;
+ SDT_PROBE4(fusefs, , vfsops, mount_err,
+ "Can't change these mount options during remount",
+ data, mp, err);
+ goto out;
+ }
+ if (((data->dataflags ^ mntopts) & FSESS_MNTOPTS_MASK) ||
+ (data->max_read != max_read) ||
+ (data->daemon_timeout != daemon_timeout)) {
+ // TODO: allow changing options where it makes sense
+ err = EOPNOTSUPP;
+ SDT_PROBE4(fusefs, , vfsops, mount_err,
+ "Can't change fuse mount options during remount",
+ data, mp, err);
+ goto out;
+ }
+
+ if (fdata_get_dead(data)) {
+ err = ENOTCONN;
+ SDT_PROBE4(fusefs, , vfsops, mount_err,
+ "device is dead during mount", data, mp, err);
+ goto out;
+ }
+
+ /* Sanity + permission checks */
+ if (!data->daemoncred)
+ panic("fuse daemon found, but identity unknown");
+ if (mntopts & FSESS_DAEMON_CAN_SPY)
+ err = priv_check(td, PRIV_VFS_FUSE_ALLOWOTHER);
+ if (err == 0 && td->td_ucred->cr_uid != data->daemoncred->cr_uid)
+ /* are we allowed to do the first mount? */
+ err = priv_check(td, PRIV_VFS_FUSE_MOUNT_NONUSER);
+
+out:
+ FUSE_UNLOCK();
+ return err;
+}
+
+static int
+fuse_vfsop_fhtovp(struct mount *mp, struct fid *fhp, int flags,
+ struct vnode **vpp)
+{
+ struct fuse_fid *ffhp = (struct fuse_fid *)fhp;
+ struct fuse_vnode_data *fvdat;
+ struct vnode *nvp;
+ int error;
+
+ if (!(fuse_get_mpdata(mp)->dataflags & FSESS_EXPORT_SUPPORT))
+ return EOPNOTSUPP;
+
+ error = VFS_VGET(mp, ffhp->nid, LK_EXCLUSIVE, &nvp);
+ if (error) {
+ *vpp = NULLVP;
+ return (error);
+ }
+ fvdat = VTOFUD(nvp);
+ if (fvdat->generation != ffhp->gen ) {
+ vput(nvp);
+ *vpp = NULLVP;
+ return (ESTALE);
+ }
+ *vpp = nvp;
+ vnode_create_vobject(*vpp, 0, curthread);
+ return (0);
+}
+
+static int
fuse_vfsop_mount(struct mount *mp)
{
int err;
@@ -238,12 +314,8 @@ fuse_vfsop_mount(struct mount *mp)
__mntopts = 0;
td = curthread;
- if (mp->mnt_flag & MNT_UPDATE)
- return EOPNOTSUPP;
-
MNT_ILOCK(mp);
mp->mnt_flag |= MNT_SYNCHRONOUS;
- mp->mnt_data = NULL;
MNT_IUNLOCK(mp);
/* Get the new options passed to mount */
opts = mp->mnt_optnew;
@@ -255,19 +327,6 @@ fuse_vfsop_mount(struct mount *mp)
if (!vfs_getopts(opts, "fspath", &err))
return err;
- /* `from' contains the device name (eg. /dev/fuse0); REQUIRED */
- fspec = vfs_getopts(opts, "from", &err);
- if (!fspec)
- return err;
-
- /* `fd' contains the filedescriptor for this session; REQUIRED */
- if (vfs_scanopt(opts, "fd", "%d", &fd) != 1)
- return EINVAL;
-
- err = fuse_getdevice(fspec, td, &fdev);
- if (err != 0)
- return err;
-
/*
* With the help of underscored options the mount program
* can inform us from the flags it sets by default
@@ -280,7 +339,6 @@ fuse_vfsop_mount(struct mount *mp)
FUSE_FLAGOPT(no_datacache, FSESS_NO_DATACACHE);
FUSE_FLAGOPT(no_namecache, FSESS_NO_NAMECACHE);
FUSE_FLAGOPT(no_mmap, FSESS_NO_MMAP);
- FUSE_FLAGOPT(brokenio, FSESS_BROKENIO);
(void)vfs_scanopt(opts, "max_read=", "%u", &max_read);
if (vfs_scanopt(opts, "timeout=", "%u", &daemon_timeout) == 1) {
@@ -293,11 +351,29 @@ fuse_vfsop_mount(struct mount *mp)
}
subtype = vfs_getopts(opts, "subtype=", &err);
- SDT_PROBE1(fuse, , vfsops, mntopts, mntopts);
+ SDT_PROBE1(fusefs, , vfsops, mntopts, mntopts);
+
+ if (mp->mnt_flag & MNT_UPDATE) {
+ return fuse_vfs_remount(mp, td, mntopts, max_read,
+ daemon_timeout);
+ }
+
+ /* `from' contains the device name (eg. /dev/fuse0); REQUIRED */
+ fspec = vfs_getopts(opts, "from", &err);
+ if (!fspec)
+ return err;
+
+ /* `fd' contains the filedescriptor for this session; REQUIRED */
+ if (vfs_scanopt(opts, "fd", "%d", &fd) != 1)
+ return EINVAL;
+
+ err = fuse_getdevice(fspec, td, &fdev);
+ if (err != 0)
+ return err;
err = fget(td, fd, &cap_read_rights, &fp);
if (err != 0) {
- SDT_PROBE2(fuse, , vfsops, trace, 1,
+ SDT_PROBE2(fusefs, , vfsops, trace, 1,
"invalid or not opened device");
goto out;
}
@@ -307,16 +383,17 @@ fuse_vfsop_mount(struct mount *mp)
td->td_fpop = fptmp;
fdrop(fp, td);
FUSE_LOCK();
- if (err != 0 || data == NULL || data->mp != NULL) {
+
+ if (err != 0 || data == NULL) {
err = ENXIO;
- SDT_PROBE4(fuse, , vfsops, mount_err,
+ SDT_PROBE4(fusefs, , vfsops, mount_err,
"invalid or not opened device", data, mp, err);
FUSE_UNLOCK();
goto out;
}
if (fdata_get_dead(data)) {
err = ENOTCONN;
- SDT_PROBE4(fuse, , vfsops, mount_err,
+ SDT_PROBE4(fusefs, , vfsops, mount_err,
"device is dead during mount", data, mp, err);
FUSE_UNLOCK();
goto out;
@@ -338,12 +415,17 @@ fuse_vfsop_mount(struct mount *mp)
data->dataflags |= mntopts;
data->max_read = max_read;
data->daemon_timeout = daemon_timeout;
+ data->mnt_flag = mp->mnt_flag & MNT_UPDATEMASK;
FUSE_UNLOCK();
vfs_getnewfsid(mp);
MNT_ILOCK(mp);
mp->mnt_data = data;
- mp->mnt_flag |= MNT_LOCAL;
+ /*
+ * FUSE file systems can be either local or remote, but the kernel
+ * can't tell the difference.
+ */
+ mp->mnt_flag &= ~MNT_LOCAL;
mp->mnt_kern_flag |= MNTK_USES_BCACHE;
MNT_IUNLOCK(mp);
/* We need this here as this slot is used by getnewvnode() */
@@ -366,9 +448,10 @@ out:
* Destroy device only if we acquired reference to
* it
*/
- SDT_PROBE4(fuse, , vfsops, mount_err,
+ SDT_PROBE4(fusefs, , vfsops, mount_err,
"mount failed, destroy device", data, mp, err);
data->mp = NULL;
+ mp->mnt_data = NULL;
fdata_trydestroy(data);
}
FUSE_UNLOCK();
@@ -429,7 +512,6 @@ alreadydead:
MNT_ILOCK(mp);
mp->mnt_data = NULL;
- mp->mnt_flag &= ~MNT_LOCAL;
MNT_IUNLOCK(mp);
dev_rel(fdev);
@@ -437,6 +519,86 @@ alreadydead:
return 0;
}
+SDT_PROBE_DEFINE1(fusefs, , vfsops, invalidate_without_export,
+ "struct mount*");
+static int
+fuse_vfsop_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
+{
+ struct fuse_data *data = fuse_get_mpdata(mp);
+ uint64_t nodeid = ino;
+ struct thread *td = curthread;
+ struct fuse_dispatcher fdi;
+ struct fuse_entry_out *feo;
+ struct fuse_vnode_data *fvdat;
+ const char dot[] = ".";
+ off_t filesize;
+ enum vtype vtyp;
+ int error;
+
+ if (!(data->dataflags & FSESS_EXPORT_SUPPORT)) {
+ /*
+ * Unreachable unless you do something stupid, like export a
+ * nullfs mount of a fusefs file system.
+ */
+ SDT_PROBE1(fusefs, , vfsops, invalidate_without_export, mp);
+ return (EOPNOTSUPP);
+ }
+
+ error = fuse_internal_get_cached_vnode(mp, ino, flags, vpp);
+ if (error || *vpp != NULL)
+ return error;
+
+ /* Do a LOOKUP, using nodeid as the parent and "." as filename */
+ fdisp_init(&fdi, sizeof(dot));
+ fdisp_make(&fdi, FUSE_LOOKUP, mp, nodeid, td, td->td_ucred);
+ memcpy(fdi.indata, dot, sizeof(dot));
+ error = fdisp_wait_answ(&fdi);
+
+ if (error)
+ return error;
+
+ feo = (struct fuse_entry_out *)fdi.answ;
+ if (feo->nodeid == 0) {
+ /* zero nodeid means ENOENT and cache it */
+ error = ENOENT;
+ goto out;
+ }
+
+ vtyp = IFTOVT(feo->attr.mode);
+ error = fuse_vnode_get(mp, feo, nodeid, NULL, vpp, NULL, vtyp);
+ if (error)
+ goto out;
+ filesize = feo->attr.size;
+
+ /*
+ * In the case where we are looking up a FUSE node represented by an
+ * existing cached vnode, and the true size reported by FUSE_LOOKUP
+ * doesn't match the vnode's cached size, then any cached writes beyond
+ * the file's current size are lost.
+ *
+ * We can get here:
+ * * following attribute cache expiration, or
+ * * due a bug in the daemon, or
+ */
+ fvdat = VTOFUD(*vpp);
+ if (vnode_isreg(*vpp) &&
+ filesize != fvdat->cached_attrs.va_size &&
+ fvdat->flag & FN_SIZECHANGE) {
+ printf("%s: WB cache incoherent on %s!\n", __func__,
+ vnode_mount(*vpp)->mnt_stat.f_mntonname);
+
+ fvdat->flag &= ~FN_SIZECHANGE;
+ }
+
+ fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid,
+ feo->attr_valid_nsec, NULL);
+ fuse_validity_2_bintime(feo->entry_valid, feo->entry_valid_nsec,
+ &fvdat->entry_cache_timeout);
+out:
+ fdisp_destroy(&fdi);
+ return error;
+}
+
static int
fuse_vfsop_root(struct mount *mp, int lkflags, struct vnode **vpp)
{
@@ -454,13 +616,13 @@ fuse_vfsop_root(struct mount *mp, int lkflags, struct vnode **vpp)
FUSE_LOCK();
MPASS(data->vroot == NULL || data->vroot == *vpp);
if (data->vroot == NULL) {
- SDT_PROBE2(fuse, , vfsops, trace, 1,
+ SDT_PROBE2(fusefs, , vfsops, trace, 1,
"new root vnode");
data->vroot = *vpp;
FUSE_UNLOCK();
vref(*vpp);
} else if (data->vroot != *vpp) {
- SDT_PROBE2(fuse, , vfsops, trace, 1,
+ SDT_PROBE2(fusefs, , vfsops, trace, 1,
"root vnode race");
FUSE_UNLOCK();
VOP_UNLOCK(*vpp, 0);
@@ -523,7 +685,7 @@ fake:
sbp->f_files = 0;
sbp->f_ffree = 0;
sbp->f_namemax = 0;
- sbp->f_bsize = FUSE_DEFAULT_BLOCKSIZE;
+ sbp->f_bsize = S_BLKSIZE;
return 0;
}