svn commit: r348135 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs
Alan Somers
asomers at FreeBSD.org
Thu May 23 00:44:05 UTC 2019
Author: asomers
Date: Thu May 23 00:44:01 2019
New Revision: 348135
URL: https://svnweb.freebsd.org/changeset/base/348135
Log:
fusefs: Make fuse file systems NFS-exportable
This commit adds the VOPs needed by userspace NFS servers (tested with
net/unfs3). More work is needed to make the in-kernel nfsd work, because of
its stateless nature. It doesn't open files prior to doing I/O. Also, the
NFS-related VOPs currently ignore the entry cache.
Sponsored by: The FreeBSD Foundation
Added:
projects/fuse2/tests/sys/fs/fusefs/nfs.cc (contents, props changed)
Modified:
projects/fuse2/sys/fs/fuse/fuse_internal.c
projects/fuse2/sys/fs/fuse/fuse_ipc.h
projects/fuse2/sys/fs/fuse/fuse_node.c
projects/fuse2/sys/fs/fuse/fuse_node.h
projects/fuse2/sys/fs/fuse/fuse_vfsops.c
projects/fuse2/sys/fs/fuse/fuse_vnops.c
projects/fuse2/tests/sys/fs/fusefs/Makefile
projects/fuse2/tests/sys/fs/fusefs/readdir.cc
projects/fuse2/tests/sys/fs/fusefs/utils.cc
projects/fuse2/tests/sys/fs/fusefs/utils.hh
Modified: projects/fuse2/sys/fs/fuse/fuse_internal.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_internal.c Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/sys/fs/fuse/fuse_internal.c Thu May 23 00:44:01 2019 (r348135)
@@ -727,6 +727,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick,
data->max_write = fiio->max_write;
if (fiio->flags & FUSE_POSIX_LOCKS)
data->dataflags |= FSESS_POSIX_LOCKS;
+ if (fiio->flags & FUSE_EXPORT_SUPPORT)
+ data->dataflags |= FSESS_EXPORT_SUPPORT;
} else {
err = EINVAL;
}
@@ -764,7 +766,7 @@ fuse_internal_send_init(struct fuse_data *data, struct
* the size of a buffer cache block.
*/
fiii->max_readahead = maxbcachebuf;
- fiii->flags = FUSE_POSIX_LOCKS;
+ fiii->flags = FUSE_EXPORT_SUPPORT | FUSE_POSIX_LOCKS;
fuse_insert_callback(fdi.tick, fuse_internal_init_callback);
fuse_insert_message(fdi.tick, false);
Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.h
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_ipc.h Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/sys/fs/fuse/fuse_ipc.h Thu May 23 00:44:01 2019 (r348135)
@@ -222,6 +222,7 @@ struct fuse_data {
#define FSESS_NO_NAMECACHE 0x0400 /* disable name cache */
#define FSESS_NO_MMAP 0x0800 /* disable mmap */
#define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */
+#define FSESS_EXPORT_SUPPORT 0x10000 /* daemon supports NFS-style lookups */
#define FSESS_MNTOPTS_MASK ( \
FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \
FSESS_DEFAULT_PERMISSIONS | FSESS_NO_ATTRCACHE | \
Modified: projects/fuse2/sys/fs/fuse/fuse_node.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_node.c Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/sys/fs/fuse/fuse_node.c Thu May 23 00:44:01 2019 (r348135)
@@ -77,7 +77,6 @@ __FBSDID("$FreeBSD$");
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <sys/fcntl.h>
-#include <sys/fnv_hash.h>
#include <sys/priv.h>
#include <sys/buf.h>
#include <security/mac/mac_framework.h>
@@ -165,18 +164,12 @@ fuse_vnode_destroy(struct vnode *vp)
atomic_subtract_acq_int(&fuse_node_count, 1);
}
-static int
+int
fuse_vnode_cmp(struct vnode *vp, void *nidp)
{
return (VTOI(vp) != *((uint64_t *)nidp));
}
-static uint32_t inline
-fuse_vnode_hash(uint64_t id)
-{
- return (fnv_32_buf(&id, sizeof(id), FNV1_32_INIT));
-}
-
SDT_PROBE_DEFINE3(fusefs, , node, stale_vnode, "struct vnode*", "enum vtype",
"uint64_t");
static int
@@ -215,6 +208,7 @@ fuse_vnode_alloc(struct mount *mp,
return (EAGAIN);
}
MPASS((*vpp)->v_data != NULL);
+ MPASS(VTOFUD(*vpp)->nid == nodeid);
SDT_PROBE2(fusefs, , node, trace, 1, "vnode taken from hash");
return (0);
}
@@ -269,6 +263,7 @@ fuse_vnode_get(struct mount *mp,
enum vtype vtyp)
{
struct thread *td = (cnp != NULL ? cnp->cn_thread : curthread);
+ uint64_t generation = feo ? feo->generation : 1;
int err = 0;
err = fuse_vnode_alloc(mp, td, nodeid, vtyp, vpp);
@@ -293,10 +288,11 @@ fuse_vnode_get(struct mount *mp,
cache_enter_time(dvp, *vpp, cnp, &timeout, NULL);
}
+ VTOFUD(*vpp)->generation = generation;
/*
* In userland, libfuse uses cached lookups for dot and dotdot entries,
* thus it does not really bump the nlookup counter for forget.
- * Follow the same semantic and avoid tu bump it in order to keep
+ * Follow the same semantic and avoid the bump in order to keep
* nlookup counters consistent.
*/
if (cnp == NULL || ((cnp->cn_flags & ISDOTDOT) == 0 &&
Modified: projects/fuse2/sys/fs/fuse/fuse_node.h
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_node.h Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/sys/fs/fuse/fuse_node.h Thu May 23 00:44:01 2019 (r348135)
@@ -60,6 +60,7 @@
#ifndef _FUSE_NODE_H_
#define _FUSE_NODE_H_
+#include <sys/fnv_hash.h>
#include <sys/types.h>
#include <sys/mutex.h>
@@ -75,10 +76,13 @@
*/
#define FN_SIZECHANGE 0x00000100
#define FN_DIRECTIO 0x00000200
+/* Indicates that parent_nid is valid */
+#define FN_PARENT_NID 0x00000400
struct fuse_vnode_data {
/** self **/
uint64_t nid;
+ uint64_t generation;
/** parent **/
uint64_t parent_nid;
@@ -98,6 +102,17 @@ struct fuse_vnode_data {
enum vtype vtype;
};
+/*
+ * This overlays the fid structure (see mount.h). Mostly the same as the types
+ * used by UFS and ext2.
+ */
+struct fuse_fid {
+ uint16_t len; /* Length of structure. */
+ uint16_t pad; /* Force 32-bit alignment. */
+ uint32_t gen; /* Generation number. */
+ uint64_t nid; /* FUSE node id. */
+};
+
#define VTOFUD(vp) \
((struct fuse_vnode_data *)((vp)->v_data))
#define VTOI(vp) (VTOFUD(vp)->nid)
@@ -119,6 +134,12 @@ fuse_vnode_clear_attr_cache(struct vnode *vp)
bintime_clear(&VTOFUD(vp)->attr_cache_timeout);
}
+static uint32_t inline
+fuse_vnode_hash(uint64_t id)
+{
+ return (fnv_32_buf(&id, sizeof(id), FNV1_32_INIT));
+}
+
#define VTOILLU(vp) ((uint64_t)(VTOFUD(vp) ? VTOI(vp) : 0))
#define FUSE_NULL_ID 0
@@ -126,12 +147,15 @@ fuse_vnode_clear_attr_cache(struct vnode *vp)
extern struct vop_vector fuse_fifoops;
extern struct vop_vector fuse_vnops;
+int fuse_vnode_cmp(struct vnode *vp, void *nidp);
+
static inline void
fuse_vnode_setparent(struct vnode *vp, struct vnode *dvp)
{
if (dvp != NULL && vp->v_type == VDIR) {
MPASS(dvp->v_type == VDIR);
VTOFUD(vp)->parent_nid = VTOI(dvp);
+ VTOFUD(vp)->flag |= FN_PARENT_NID;
}
}
Modified: projects/fuse2/sys/fs/fuse/fuse_vfsops.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_vfsops.c Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/sys/fs/fuse/fuse_vfsops.c Thu May 23 00:44:01 2019 (r348135)
@@ -107,16 +107,20 @@ SDT_PROBE_DEFINE2(fusefs, , vfsops, trace, "int", "cha
#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,
};
static int fuse_enforce_dev_perms = 0;
@@ -257,6 +261,34 @@ out:
}
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;
@@ -322,7 +354,6 @@ fuse_vfsop_mount(struct mount *mp)
SDT_PROBE1(fusefs, , vfsops, mntopts, mntopts);
if (mp->mnt_flag & MNT_UPDATE) {
- /*dev_rel(fdev);*/
return fuse_vfs_remount(mp, td, mntopts, max_read,
daemon_timeout);
}
@@ -352,7 +383,8 @@ 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(fusefs, , vfsops, mount_err,
"invalid or not opened device", data, mp, err);
@@ -480,12 +512,82 @@ alreadydead:
MNT_ILOCK(mp);
mp->mnt_data = NULL;
- mp->mnt_flag &= ~MNT_LOCAL;
MNT_IUNLOCK(mp);
dev_rel(fdev);
return 0;
+}
+
+static int
+fuse_vfsop_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
+{
+ 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;
+
+ /*
+ * TODO Check the vnode cache, verifying entry cache timeout. Normally
+ * done during VOP_LOOKUP
+ */
+ /*error = vfs_hash_get(mp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, td, vpp,*/
+ /*fuse_vnode_cmp, &nodeid);*/
+ /*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, td->td_ucred, &feo->attr,
+ feo->attr_valid, feo->attr_valid_nsec, NULL);
+out:
+ fdisp_destroy(&fdi);
+ return error;
}
static int
Modified: projects/fuse2/sys/fs/fuse/fuse_vnops.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_vnops.c Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/sys/fs/fuse/fuse_vnops.c Thu May 23 00:44:01 2019 (r348135)
@@ -151,6 +151,7 @@ static vop_write_t fuse_vnop_write;
static vop_getpages_t fuse_vnop_getpages;
static vop_putpages_t fuse_vnop_putpages;
static vop_print_t fuse_vnop_print;
+static vop_vptofh_t fuse_vnop_vptofh;
struct vop_vector fuse_fifoops = {
.vop_default = &fifo_specops,
@@ -165,6 +166,7 @@ struct vop_vector fuse_fifoops = {
.vop_reclaim = fuse_vnop_reclaim,
.vop_setattr = fuse_vnop_setattr,
.vop_write = VOP_PANIC,
+ .vop_vptofh = fuse_vnop_vptofh,
};
struct vop_vector fuse_vnops = {
@@ -202,6 +204,7 @@ struct vop_vector fuse_vnops = {
.vop_getpages = fuse_vnop_getpages,
.vop_putpages = fuse_vnop_putpages,
.vop_print = fuse_vnop_print,
+ .vop_vptofh = fuse_vnop_vptofh,
};
static u_long fuse_lookup_cache_hits = 0;
@@ -908,6 +911,8 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
return err;
if (flags & ISDOTDOT) {
+ KASSERT(VTOFUD(dvp)->flag & FN_PARENT_NID,
+ ("Looking up .. is TODO"));
nid = VTOFUD(dvp)->parent_nid;
if (nid == 0)
return ENOENT;
@@ -970,8 +975,8 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
if (!lookup_err) {
/* lookup call succeeded */
- nid = ((struct fuse_entry_out *)fdi.answ)->nodeid;
feo = (struct fuse_entry_out *)fdi.answ;
+ nid = feo->nodeid;
if (nid == 0) {
/* zero nodeid means ENOENT and cache it */
struct timespec timeout;
@@ -2446,3 +2451,48 @@ fuse_vnop_print(struct vop_print_args *ap)
return 0;
}
+
+/*
+ * Get an NFS filehandle for a FUSE file.
+ *
+ * This will only work for FUSE file systems that guarantee the uniqueness of
+ * nodeid:generation, which most don't
+ */
+/*
+vop_vptofh {
+ IN struct vnode *a_vp;
+ IN struct fid *a_fhp;
+};
+*/
+static int
+fuse_vnop_vptofh(struct vop_vptofh_args *ap)
+{
+ struct vnode *vp = ap->a_vp;
+ struct fuse_vnode_data *fvdat = VTOFUD(vp);
+ struct fuse_fid *fhp = (struct fuse_fid *)(ap->a_fhp);
+ _Static_assert(sizeof(struct fuse_fid) <= sizeof(struct fid),
+ "FUSE fid type is too big");
+ struct mount *mp = vnode_mount(vp);
+ struct fuse_data *data = fuse_get_mpdata(mp);
+ struct vattr va;
+ int err;
+
+ if (!(data->dataflags & FSESS_EXPORT_SUPPORT))
+ return EOPNOTSUPP;
+
+ err = fuse_internal_getattr(vp, &va, curthread->td_ucred, curthread);
+ if (err)
+ return err;
+
+ /*ip = VTOI(ap->a_vp);*/
+ /*ufhp = (struct ufid *)ap->a_fhp;*/
+ fhp->len = sizeof(struct fuse_fid);
+ fhp->nid = fvdat->nid;
+ if (fvdat->generation <= UINT32_MAX)
+ fhp->gen = fvdat->generation;
+ else
+ return EOVERFLOW;
+ return (0);
+}
+
+
Modified: projects/fuse2/tests/sys/fs/fusefs/Makefile
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/Makefile Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/tests/sys/fs/fusefs/Makefile Thu May 23 00:44:01 2019 (r348135)
@@ -26,6 +26,7 @@ GTESTS+= lookup
GTESTS+= mkdir
GTESTS+= mknod
GTESTS+= mount
+GTESTS+= nfs
GTESTS+= open
GTESTS+= opendir
GTESTS+= read
@@ -52,6 +53,7 @@ SRCS.$p+= utils.cc
TEST_METADATA.default_permissions+= required_user="unprivileged"
TEST_METADATA.default_permissions_privileged+= required_user="root"
TEST_METADATA.mknod+= required_user="root"
+TEST_METADATA.nfs+= required_user="root"
# TODO: drastically increase timeout after test development is mostly complete
TEST_METADATA+= timeout=10
Added: projects/fuse2/tests/sys/fs/fusefs/nfs.cc
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fusefs/nfs.cc Thu May 23 00:44:01 2019 (r348135)
@@ -0,0 +1,307 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2019 The FreeBSD Foundation
+ *
+ * This software was developed by BFF Storage Systems, LLC under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* This file tests functionality needed by NFS servers */
+extern "C" {
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace std;
+using namespace testing;
+
+
+class Nfs: public FuseTest {
+public:
+virtual void SetUp() {
+ if (geteuid() != 0)
+ GTEST_SKIP() << "This test requires a privileged user";
+ FuseTest::SetUp();
+}
+};
+
+class Exportable: public Nfs {
+public:
+virtual void SetUp() {
+ m_init_flags = FUSE_EXPORT_SUPPORT;
+ Nfs::SetUp();
+}
+};
+
+class Fhstat: public Exportable {};
+class FhstatNotExportable: public Nfs {};
+class Getfh: public Exportable {};
+class Readdir: public Exportable {};
+
+/* If the server returns a different generation number, then file is stale */
+TEST_F(Fhstat, estale)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ struct stat sb;
+ const uint64_t ino = 42;
+ const mode_t mode = S_IFDIR | 0755;
+ Sequence seq;
+
+ EXPECT_LOOKUP(1, RELDIRPATH)
+ .InSequence(seq)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ EXPECT_LOOKUP(ino, ".")
+ .InSequence(seq)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 2;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ ASSERT_EQ(-1, fhstat(&fhp, &sb));
+ EXPECT_EQ(ESTALE, errno);
+}
+
+/* If we must lookup an entry from the server, send a LOOKUP request for "." */
+TEST_F(Fhstat, lookup_dot)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ struct stat sb;
+ const uint64_t ino = 42;
+ const mode_t mode = S_IFDIR | 0755;
+ const uid_t uid = 12345;
+
+ EXPECT_LOOKUP(1, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr.uid = uid;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ EXPECT_LOOKUP(ino, ".")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr.uid = uid;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
+ EXPECT_EQ(uid, sb.st_uid);
+ EXPECT_EQ(mode, sb.st_mode);
+}
+
+/* Use a file handle whose entry is still cached */
+/*
+ * Disabled because fuse_vfsop_vget doesn't yet check the entry cache. No PR
+ * because that's a feature request, not a bug
+ */
+TEST_F(Fhstat, DISABLED_cached)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ struct stat sb;
+ const uint64_t ino = 42;
+ const mode_t mode = S_IFDIR | 0755;
+ const uid_t uid = 12345;
+
+ EXPECT_LOOKUP(1, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr.uid = uid;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = UINT64_MAX;
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
+ EXPECT_EQ(uid, sb.st_uid);
+ EXPECT_EQ(mode, sb.st_mode);
+}
+
+/*
+ * If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style
+ * lookups
+ */
+TEST_F(FhstatNotExportable, lookup_dot)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ const uint64_t ino = 42;
+ const mode_t mode = S_IFDIR | 0755;
+
+ EXPECT_LOOKUP(1, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ ASSERT_EQ(-1, getfh(FULLPATH, &fhp));
+ ASSERT_EQ(EOPNOTSUPP, errno);
+}
+
+/* FreeBSD's fid struct doesn't have enough space for 64-bit generations */
+TEST_F(Getfh, eoverflow)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ uint64_t ino = 42;
+
+ EXPECT_LOOKUP(1, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = S_IFDIR | 0755;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = (uint64_t)UINT32_MAX + 1;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = UINT64_MAX;
+ })));
+
+ ASSERT_NE(0, getfh(FULLPATH, &fhp));
+ EXPECT_EQ(EOVERFLOW, errno);
+}
+
+/* Get an NFS file handle */
+TEST_F(Getfh, ok)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ uint64_t ino = 42;
+
+ EXPECT_LOOKUP(1, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = S_IFDIR | 0755;
+ out->body.entry.nodeid = ino;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = UINT64_MAX;
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+}
+
+/*
+ * Call readdir via a file handle.
+ *
+ * This is how a userspace nfs server like nfs-ganesha or unfs3 would call
+ * readdir. The in-kernel NFS server never does any equivalent of open. I
+ * haven't discovered a way to mimic nfsd's behavior short of actually running
+ * nfsd.
+ */
+TEST_F(Readdir, getdirentries)
+{
+ const char FULLPATH[] = "mountpoint/some_dir";
+ const char RELPATH[] = "some_dir";
+ uint64_t ino = 42;
+ mode_t mode = S_IFDIR | 0755;
+ fhandle_t fhp;
+ int fd;
+ char buf[8192];
+ ssize_t r;
+
+ EXPECT_LOOKUP(1, RELPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ EXPECT_LOOKUP(ino, ".")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out->body.entry.attr.mode = mode;
+ out->body.entry.nodeid = ino;
+ out->body.entry.generation = 1;
+ out->body.entry.attr_valid = UINT64_MAX;
+ out->body.entry.entry_valid = 0;
+ })));
+
+ expect_opendir(ino);
+
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in->header.opcode == FUSE_READDIR &&
+ in->header.nodeid == ino &&
+ in->body.readdir.size == sizeof(buf));
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+ out->header.error = 0;
+ out->header.len = sizeof(out->header);
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ fd = fhopen(&fhp, O_DIRECTORY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ r = getdirentries(fd, buf, sizeof(buf), 0);
+ ASSERT_EQ(0, r) << strerror(errno);
+
+ /* Deliberately leak fd. RELEASEDIR will be tested separately */
+}
Modified: projects/fuse2/tests/sys/fs/fusefs/readdir.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/readdir.cc Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/tests/sys/fs/fusefs/readdir.cc Thu May 23 00:44:01 2019 (r348135)
@@ -45,56 +45,6 @@ void expect_lookup(const char *relpath, uint64_t ino)
{
FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1);
}
-
-void expect_readdir(uint64_t ino, uint64_t off, vector<struct dirent> &ents)
-{
- EXPECT_CALL(*m_mock, process(
- ResultOf([=](auto in) {
- return (in->header.opcode == FUSE_READDIR &&
- in->header.nodeid == ino &&
- in->body.readdir.fh == FH &&
- in->body.readdir.offset == off);
- }, Eq(true)),
- _)
- ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
- struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
- int i = 0;
-
- out->header.error = 0;
- out->header.len = 0;
-
- for (const auto& it: ents) {
- size_t entlen, entsize;
-
- fde->ino = it.d_fileno;
- fde->off = it.d_off;
- fde->type = it.d_type;
- fde->namelen = it.d_namlen;
- strncpy(fde->name, it.d_name, it.d_namlen);
- entlen = FUSE_NAME_OFFSET + fde->namelen;
- entsize = FUSE_DIRENT_SIZE(fde);
- /*
- * The FUSE protocol does not require zeroing out the
- * unused portion of the name. But it's a good
- * practice to prevent information disclosure to the
- * FUSE client, even though the client is usually the
- * kernel
- */
- memset(fde->name + fde->namelen, 0, entsize - entlen);
- if (out->header.len + entsize > in->body.read.size) {
- printf("Overflow in readdir expectation: i=%d\n"
- , i);
- break;
- }
- out->header.len += entsize;
- fde = (struct fuse_dirent*)
- ((long*)fde + entsize / sizeof(long));
- i++;
- }
- out->header.len += sizeof(out->header);
- })));
-
-}
};
class Readdir_7_8: public Readdir {
Modified: projects/fuse2/tests/sys/fs/fusefs/utils.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.cc Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.cc Thu May 23 00:44:01 2019 (r348135)
@@ -35,6 +35,7 @@ extern "C" {
#include <sys/sysctl.h>
#include <sys/wait.h>
+#include <dirent.h>
#include <grp.h>
#include <pwd.h>
#include <semaphore.h>
@@ -285,6 +286,56 @@ void FuseTest::expect_read(uint64_t ino, uint64_t offs
}))).RetiresOnSaturation();
}
+void FuseTest::expect_readdir(uint64_t ino, uint64_t off,
+ std::vector<struct dirent> &ents)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in->header.opcode == FUSE_READDIR &&
+ in->header.nodeid == ino &&
+ in->body.readdir.fh == FH &&
+ in->body.readdir.offset == off);
+ }, Eq(true)),
+ _)
+ ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
+ struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
+ int i = 0;
+
+ out->header.error = 0;
+ out->header.len = 0;
+
+ for (const auto& it: ents) {
+ size_t entlen, entsize;
+
+ fde->ino = it.d_fileno;
+ fde->off = it.d_off;
+ fde->type = it.d_type;
+ fde->namelen = it.d_namlen;
+ strncpy(fde->name, it.d_name, it.d_namlen);
+ entlen = FUSE_NAME_OFFSET + fde->namelen;
+ entsize = FUSE_DIRENT_SIZE(fde);
+ /*
+ * The FUSE protocol does not require zeroing out the
+ * unused portion of the name. But it's a good
+ * practice to prevent information disclosure to the
+ * FUSE client, even though the client is usually the
+ * kernel
+ */
+ memset(fde->name + fde->namelen, 0, entsize - entlen);
+ if (out->header.len + entsize > in->body.read.size) {
+ printf("Overflow in readdir expectation: i=%d\n"
+ , i);
+ break;
+ }
+ out->header.len += entsize;
+ fde = (struct fuse_dirent*)
+ ((long*)fde + entsize / sizeof(long));
+ i++;
+ }
+ out->header.len += sizeof(out->header);
+ })));
+
+}
void FuseTest::expect_release(uint64_t ino, uint64_t fh)
{
EXPECT_CALL(*m_mock, process(
Modified: projects/fuse2/tests/sys/fs/fusefs/utils.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.hh Thu May 23 00:22:03 2019 (r348134)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.hh Thu May 23 00:44:01 2019 (r348135)
@@ -150,6 +150,14 @@ class FuseTest : public ::testing::Test {
void expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, const void *contents);
+ /*
+ * Create an expectation that FUSE_READIR will be called any number of
+ * times on the given ino with the given offset, returning (by copy)
+ * the provided entries
+ */
+ void expect_readdir(uint64_t ino, uint64_t off,
+ std::vector<struct dirent> &ents);
+
/*
* Create an expectation that FUSE_RELEASE will be called exactly once
* for the given inode and filehandle, returning success
More information about the svn-src-projects
mailing list