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