Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 1 Mar 2019 23:53:05 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r344715 - in projects/fuse2: etc/mtree tests/sys/fs tests/sys/fs/fuse
Message-ID:  <201903012353.x21Nr5do051752@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Fri Mar  1 23:53:05 2019
New Revision: 344715
URL: https://svnweb.freebsd.org/changeset/base/344715

Log:
  Begin a fuse(4) test suite
  
  It only tests the kernel portion of fuse, not the userspace portion (which
  comes from sysutils/fusefs-libs).  The kernel-userspace interface is
  de-facto standardized, and this test suite seeks to validate FreeBSD's
  implementation.
  
  It uses GoogleMock to substitute for a userspace daemon and validate the
  kernel's behavior in response to filesystem access.  GoogleMock is
  convenient because it can validate the order, number, and arguments of each
  operation, and return canned responses.
  
  But that also means that the test suite must use GoogleTest, since
  GoogleMock is incompatible with atf-c++ and atf.test.mk does not allow C++
  programs to use atf-c.
  
  This commit adds the first 10 test cases out of an estimated 130 total.
  
  PR:		235775, 235773
  Sponsored by:	The FreeBSD Foundation

Added:
  projects/fuse2/tests/sys/fs/fuse/
  projects/fuse2/tests/sys/fs/fuse/Makefile   (contents, props changed)
  projects/fuse2/tests/sys/fs/fuse/getattr.cc   (contents, props changed)
  projects/fuse2/tests/sys/fs/fuse/lookup.cc   (contents, props changed)
  projects/fuse2/tests/sys/fs/fuse/mockfs.cc   (contents, props changed)
  projects/fuse2/tests/sys/fs/fuse/mockfs.hh
  projects/fuse2/tests/sys/fs/fuse/utils.cc   (contents, props changed)
  projects/fuse2/tests/sys/fs/fuse/utils.hh
Modified:
  projects/fuse2/etc/mtree/BSD.tests.dist
  projects/fuse2/tests/sys/fs/Makefile

Modified: projects/fuse2/etc/mtree/BSD.tests.dist
==============================================================================
--- projects/fuse2/etc/mtree/BSD.tests.dist	Fri Mar  1 23:30:23 2019	(r344714)
+++ projects/fuse2/etc/mtree/BSD.tests.dist	Fri Mar  1 23:53:05 2019	(r344715)
@@ -701,6 +701,8 @@
         file
         ..
         fs
+            fuse
+            ..
             tmpfs
             ..
         ..

Modified: projects/fuse2/tests/sys/fs/Makefile
==============================================================================
--- projects/fuse2/tests/sys/fs/Makefile	Fri Mar  1 23:30:23 2019	(r344714)
+++ projects/fuse2/tests/sys/fs/Makefile	Fri Mar  1 23:53:05 2019	(r344715)
@@ -7,6 +7,7 @@ TESTSDIR=		${TESTSBASE}/sys/fs
 TESTSRC=		${SRCTOP}/contrib/netbsd-tests/fs
 
 #TESTS_SUBDIRS+=	nullfs	# XXX: needs rump
+TESTS_SUBDIRS+=		fuse
 TESTS_SUBDIRS+=		tmpfs
 
 ${PACKAGE}FILES+=	h_funcs.subr

Added: projects/fuse2/tests/sys/fs/fuse/Makefile
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/Makefile	Fri Mar  1 23:53:05 2019	(r344715)
@@ -0,0 +1,47 @@
+# $FreeBSD$
+
+PACKAGE=	tests
+
+TESTSDIR=	${TESTSBASE}/sys/fs/fuse
+
+ATF_TESTS_CXX+=	getattr
+ATF_TESTS_CXX+=	lookup
+
+SRCS.getattr+=	getattr.cc
+SRCS.getattr+=	getmntopts.c
+SRCS.getattr+=	mockfs.cc
+SRCS.getattr+=	utils.cc
+
+SRCS.lookup+=	lookup.cc
+SRCS.lookup+=	getmntopts.c
+SRCS.lookup+=	mockfs.cc
+SRCS.lookup+=	utils.cc
+
+TEST_METADATA+= timeout=10
+TEST_METADATA+= required_user=root
+
+FUSEFS=		${.CURDIR:H:H:H:H}/sys/fs/fuse
+MOUNT=		${.CURDIR:H:H:H:H}/sbin/mount
+CFLAGS+=	-I${.CURDIR:H:H:H}
+CFLAGS+=	-I${FUSEFS}
+CFLAGS+=	-I${MOUNT}
+.PATH:		${MOUNT}
+
+LIBADD+=	pthread
+WARNS?=	6
+NO_WTHREAD_SAFETY=	# GoogleTest fails Clang's thread safety check
+
+# Use googlemock from ports until after the import-googletest-1.8.1 branch
+# merges to head.
+CXXFLAGS+=	-I/usr/local/include
+CXXFLAGS+=	-DGTEST_HAS_POSIX_RE=1
+CXXFLAGS+=	-DGTEST_HAS_PTHREAD=1
+CXXFLAGS+=	-DGTEST_HAS_STREAM_REDIRECTION=1
+CXXFLAGS+=	-frtti
+CXXFLAGS+=	-std=c++14
+LDADD+=		${LOCALBASE}/lib/libgmock.a
+LDADD+=		${LOCALBASE}/lib/libgtest.a
+# Without -lpthread, gtest fails at _runtime_ with the error pthread_key_create(&key, &DeleteThreadLocalValue)failed with error 78
+LIBADD+=	pthread
+
+.include <bsd.test.mk>

Added: projects/fuse2/tests/sys/fs/fuse/getattr.cc
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/getattr.cc	Fri Mar  1 23:53:05 2019	(r344715)
@@ -0,0 +1,241 @@
+/*-
+ * Copyright (c) 2019 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+class Getattr : public FuseTest {};
+
+/*
+ * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
+ * should use the cached attributes, rather than query the daemon
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
+TEST_F(Getattr, DISABLED_attr_cache)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	const uint64_t generation = 13;
+	struct stat sb;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = ino;
+		out->body.entry.generation = generation;
+	}));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr_valid = UINT64_MAX;
+		out->body.attr.attr.ino = ino;	// Must match nodeid
+		out->body.attr.attr.mode = S_IFREG | 0644;
+	}));
+	EXPECT_EQ(0, stat(FULLPATH, &sb));
+	/* The second stat(2) should use cached attributes */
+	EXPECT_EQ(0, stat(FULLPATH, &sb));
+}
+
+/*
+ * If getattr returns a finite but non-zero cache timeout, then we should
+ * discard the cached attributes and requery the daemon after the timeout
+ * period passes.
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */
+TEST_F(Getattr, attr_cache_timeout)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	const uint64_t generation = 13;
+	struct stat sb;
+	/* 
+	 * The timeout should be longer than the longest plausible time the
+	 * daemon would take to complete a write(2) to /dev/fuse, but no longer.
+	 */
+	long timeout_ns = 250'000'000;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.entry_valid = UINT64_MAX;
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = ino;
+		out->body.entry.generation = generation;
+	}));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).Times(2)
+	.WillRepeatedly(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr_valid_nsec = timeout_ns;
+		out->body.attr.attr_valid = UINT64_MAX;
+		out->body.attr.attr.ino = ino;	// Must match nodeid
+		out->body.attr.attr.mode = S_IFREG | 0644;
+	}));
+	EXPECT_EQ(0, stat(FULLPATH, &sb));
+	usleep(2 * timeout_ns / 1000);
+	/* Timeout has expire. stat(2) should requery the daemon */
+	EXPECT_EQ(0, stat(FULLPATH, &sb));
+}
+
+TEST_F(Getattr, enoent)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	struct stat sb;
+	const uint64_t ino = 42;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.attr.mode = 0100644;
+		out->body.entry.nodeid = ino;
+	}));
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		out->header.error = -ENOENT;
+		out->header.len = sizeof(out->header);
+	}));
+	EXPECT_NE(0, stat(FULLPATH, &sb));
+	EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(Getattr, ok)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	const uint64_t generation = 13;
+	struct stat sb;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = ino;
+		out->body.entry.generation = generation;
+	}));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr.ino = ino;	// Must match nodeid
+		out->body.attr.attr.mode = S_IFREG | 0644;
+		out->body.attr.attr.size = 1;
+		out->body.attr.attr.blocks = 2;
+		out->body.attr.attr.atime = 3;
+		out->body.attr.attr.mtime = 4;
+		out->body.attr.attr.ctime = 5;
+		out->body.attr.attr.atimensec = 6;
+		out->body.attr.attr.mtimensec = 7;
+		out->body.attr.attr.ctimensec = 8;
+		out->body.attr.attr.nlink = 9;
+		out->body.attr.attr.uid = 10;
+		out->body.attr.attr.gid = 11;
+		out->body.attr.attr.rdev = 12;
+	}));
+
+	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
+	EXPECT_EQ(1, sb.st_size);
+	EXPECT_EQ(2, sb.st_blocks);
+	EXPECT_EQ(3, sb.st_atim.tv_sec);
+	EXPECT_EQ(6, sb.st_atim.tv_nsec);
+	EXPECT_EQ(4, sb.st_mtim.tv_sec);
+	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
+	EXPECT_EQ(5, sb.st_ctim.tv_sec);
+	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
+	EXPECT_EQ(9ull, sb.st_nlink);
+	EXPECT_EQ(10ul, sb.st_uid);
+	EXPECT_EQ(11ul, sb.st_gid);
+	EXPECT_EQ(12ul, sb.st_rdev);
+	EXPECT_EQ(ino, sb.st_ino);
+	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
+
+	// fuse(4) does not _yet_ support inode generations
+	//EXPECT_EQ(generation, sb.st_gen);
+
+	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
+	//only supported as OS-specific extensions to OSX.
+	//EXPECT_EQ(, sb.st_birthtim);
+	//EXPECT_EQ(, sb.st_flags);
+	
+	//FUSE can't set st_blksize until protocol 7.9
+}

Added: projects/fuse2/tests/sys/fs/fuse/lookup.cc
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/lookup.cc	Fri Mar  1 23:53:05 2019	(r344715)
@@ -0,0 +1,258 @@
+/*-
+ * Copyright (c) 2019 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+class Lookup: public FuseTest {};
+
+/*
+ * If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs
+ * should use the cached attributes, rather than query the daemon
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
+TEST_F(Lookup, DISABLED_attr_cache)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	struct stat sb;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.nodeid = ino;
+		out->body.entry.attr_valid = UINT64_MAX;
+		out->body.entry.attr.ino = ino;	// Must match nodeid
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.attr.size = 1;
+		out->body.entry.attr.blocks = 2;
+		out->body.entry.attr.atime = 3;
+		out->body.entry.attr.mtime = 4;
+		out->body.entry.attr.ctime = 5;
+		out->body.entry.attr.atimensec = 6;
+		out->body.entry.attr.mtimensec = 7;
+		out->body.entry.attr.ctimensec = 8;
+		out->body.entry.attr.nlink = 9;
+		out->body.entry.attr.uid = 10;
+		out->body.entry.attr.gid = 11;
+		out->body.entry.attr.rdev = 12;
+	}));
+	/* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */
+	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
+	EXPECT_EQ(1, sb.st_size);
+	EXPECT_EQ(2, sb.st_blocks);
+	EXPECT_EQ(3, sb.st_atim.tv_sec);
+	EXPECT_EQ(6, sb.st_atim.tv_nsec);
+	EXPECT_EQ(4, sb.st_mtim.tv_sec);
+	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
+	EXPECT_EQ(5, sb.st_ctim.tv_sec);
+	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
+	EXPECT_EQ(9ull, sb.st_nlink);
+	EXPECT_EQ(10ul, sb.st_uid);
+	EXPECT_EQ(11ul, sb.st_gid);
+	EXPECT_EQ(12ul, sb.st_rdev);
+	EXPECT_EQ(ino, sb.st_ino);
+	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
+
+	// fuse(4) does not _yet_ support inode generations
+	//EXPECT_EQ(generation, sb.st_gen);
+
+	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
+	//only supported as OS-specific extensions to OSX.
+	//EXPECT_EQ(, sb.st_birthtim);
+	//EXPECT_EQ(, sb.st_flags);
+	
+	//FUSE can't set st_blksize until protocol 7.9
+}
+
+/*
+ * If lookup returns a finite but non-zero cache timeout, then we should discard
+ * the cached attributes and requery the daemon.
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */
+TEST_F(Lookup, attr_cache_timeout)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	struct stat sb;
+	/* 
+	 * The timeout should be longer than the longest plausible time the
+	 * daemon would take to complete a write(2) to /dev/fuse, but no longer.
+	 */
+	long timeout_ns = 250'000'000;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.nodeid = ino;
+		out->body.entry.attr_valid_nsec = timeout_ns;
+		out->body.entry.attr.ino = ino;	// Must match nodeid
+		out->body.entry.attr.mode = S_IFREG | 0644;
+	}));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr.ino = ino;	// Must match nodeid
+		out->body.attr.attr.mode = S_IFREG | 0644;
+	}));
+
+	/* access(2) will issue a VOP_LOOKUP but not a VOP_GETATTR */
+	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
+	usleep(2 * timeout_ns / 1000);
+	/* The cache has timed out; VOP_GETATTR should query the daemon*/
+	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
+}
+
+TEST_F(Lookup, enoent)
+{
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		out->header.error = -ENOENT;
+		out->header.len = sizeof(out->header);
+	}));
+	EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
+	EXPECT_EQ(ENOENT, errno);
+}
+
+/*
+ * If lookup returns a non-zero entry timeout, then subsequent VOP_LOOKUPs
+ * should use the cached inode rather than requery the daemon
+ */
+TEST_F(Lookup, entry_cache)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.entry_valid = UINT64_MAX;
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = 14;
+	}));
+	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
+	/* The second access(2) should use the cache */
+	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
+}
+
+/*
+ * If lookup returns a finite but non-zero entry cache timeout, then we should
+ * discard the cached inode and requery the daemon
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */
+TEST_F(Lookup, DISABLED_entry_cache_timeout)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	/* 
+	 * The timeout should be longer than the longest plausible time the
+	 * daemon would take to complete a write(2) to /dev/fuse, but no longer.
+	 */
+	long timeout_ns = 250'000'000;
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).Times(2)
+	.WillRepeatedly(Invoke([=](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.entry_valid_nsec = timeout_ns;
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = 14;
+	}));
+	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
+	usleep(2 * timeout_ns / 1000);
+	/* The cache has timed out; VOP_LOOKUP should query the daemon*/
+	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
+}
+
+TEST_F(Lookup, ok)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_LOOKUP &&
+				strcmp(in->body.lookup, RELPATH) == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke([](auto in, auto out) {
+		out->header.unique = in->header.unique;
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = 14;
+	}));
+	/*
+	 * access(2) is one of the few syscalls that will not (always) follow
+	 * up a successful VOP_LOOKUP with another VOP.
+	 */
+	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
+}

Added: projects/fuse2/tests/sys/fs/fuse/mockfs.cc
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc	Fri Mar  1 23:53:05 2019	(r344715)
@@ -0,0 +1,251 @@
+/*-
+ * Copyright (c) 2019 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+extern "C" {
+#include <sys/param.h>
+
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "mntopts.h"	// for build_iovec
+}
+
+#include <gtest/gtest.h>
+
+#include "mockfs.hh"
+
+using namespace testing;
+
+int verbosity = 0;
+static sig_atomic_t quit = 0;
+
+const char* opcode2opname(uint32_t opcode)
+{
+	const int NUM_OPS = 39;
+	const char* table[NUM_OPS] = {
+		"Unknown (opcode 0)",
+		"FUSE_LOOKUP",
+		"FUSE_FORGET",
+		"FUSE_GETATTR",
+		"FUSE_SETATTR",
+		"FUSE_READLINK",
+		"FUSE_SYMLINK",
+		"Unknown (opcode 7)",
+		"FUSE_MKNOD",
+		"FUSE_MKDIR",
+		"FUSE_UNLINK",
+		"FUSE_RMDIR",
+		"FUSE_RENAME",
+		"FUSE_LINK",
+		"FUSE_OPEN",
+		"FUSE_READ",
+		"FUSE_WRITE",
+		"FUSE_STATFS",
+		"FUSE_RELEASE",
+		"Unknown (opcode 19)",
+		"FUSE_FSYNC",
+		"FUSE_SETXATTR",
+		"FUSE_GETXATTR",
+		"FUSE_LISTXATTR",
+		"FUSE_REMOVEXATTR",
+		"FUSE_FLUSH",
+		"FUSE_INIT",
+		"FUSE_OPENDIR",
+		"FUSE_READDIR",
+		"FUSE_RELEASEDIR",
+		"FUSE_FSYNCDIR",
+		"FUSE_GETLK",
+		"FUSE_SETLK",
+		"FUSE_SETLKW",
+		"FUSE_ACCESS",
+		"FUSE_CREATE",
+		"FUSE_INTERRUPT",
+		"FUSE_BMAP",
+		"FUSE_DESTROY"
+	};
+	if (opcode >= NUM_OPS)
+		return ("Unknown (opcode > max)");
+	else
+		return (table[opcode]);
+}
+
+void sigint_handler(int __unused sig) {
+	quit = 1;
+}
+
+MockFS::MockFS() {
+	struct iovec *iov = NULL;
+	int iovlen = 0;
+	char fdstr[15];
+
+	/*
+	 * Kyua sets pwd to a testcase-unique tempdir; no need to use
+	 * mkdtemp
+	 */
+	/*
+	 * googletest doesn't allow ASSERT_ in constructors, so we must throw
+	 * instead.
+	 */
+	if (mkdir("mountpoint" , 0644) && errno != EEXIST)
+		throw(std::system_error(errno, std::system_category(),
+			"Couldn't make mountpoint directory"));
+
+	m_fuse_fd = open("/dev/fuse", O_RDWR);
+	if (m_fuse_fd < 0)
+		throw(std::system_error(errno, std::system_category(),
+			"Couldn't open /dev/fuse"));
+	sprintf(fdstr, "%d", m_fuse_fd);
+
+	m_pid = getpid();
+
+	build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
+	build_iovec(&iov, &iovlen, "fspath",
+		    __DECONST(void *, "mountpoint"), -1);
+	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
+	build_iovec(&iov, &iovlen, "fd", fdstr, -1);
+	if (nmount(iov, iovlen, 0))
+		throw(std::system_error(errno, std::system_category(),
+			"Couldn't mount filesystem"));
+
+	// Setup default handler
+	ON_CALL(*this, process(_, _))
+		.WillByDefault(Invoke(this, &MockFS::process_default));
+
+	init();
+	if (pthread_create(&m_thr, NULL, service, (void*)this))
+		throw(std::system_error(errno, std::system_category(),
+			"Couldn't Couldn't start fuse thread"));
+}
+
+MockFS::~MockFS() {
+	pthread_kill(m_daemon_id, SIGUSR1);
+	// Closing the /dev/fuse file descriptor first allows unmount to
+	// succeed even if the daemon doesn't correctly respond to commands
+	// during the unmount sequence.
+	close(m_fuse_fd);
+	pthread_join(m_daemon_id, NULL);
+	::unmount("mountpoint", MNT_FORCE);
+	rmdir("mountpoint");
+}
+
+void MockFS::init() {
+	mockfs_buf_in *in;
+	mockfs_buf_out out;
+
+	in = (mockfs_buf_in*) malloc(sizeof(*in));
+	ASSERT_TRUE(in != NULL);
+
+	read_request(in);
+	ASSERT_EQ(FUSE_INIT, in->header.opcode);
+
+	memset(&out, 0, sizeof(out));
+	out.header.unique = in->header.unique;
+	out.header.error = 0;
+	out.body.init.major = FUSE_KERNEL_VERSION;
+	out.body.init.minor = FUSE_KERNEL_MINOR_VERSION;
+	SET_OUT_HEADER_LEN(&out, init);
+	write(m_fuse_fd, &out, out.header.len);
+
+	free(in);
+}
+
+void MockFS::loop() {
+	mockfs_buf_in *in;
+	mockfs_buf_out out;
+
+	in = (mockfs_buf_in*) malloc(sizeof(*in));
+	ASSERT_TRUE(in != NULL);
+	while (!quit) {
+		bzero(in, sizeof(*in));
+		bzero(&out, sizeof(out));
+		read_request(in);
+		if (quit)
+			break;
+		if (verbosity > 0) {
+			printf("Got request %s\n",
+				opcode2opname(in->header.opcode));
+		}
+		if ((pid_t)in->header.pid != m_pid) {
+			/* 
+			 * Reject any requests from unknown processes.  Because
+			 * we actually do mount a filesystem, plenty of
+			 * unrelated system daemons may try to access it.
+			 */
+			process_default(in, &out);
+		} else {
+			process(in, &out);
+		}
+		if (in->header.opcode == FUSE_FORGET) {
+			/*Alone among the opcodes, FORGET expects no response*/
+			continue;
+		}
+		ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 ||
+			    errno == EAGAIN)
+			<< strerror(errno);
+	}
+	free(in);
+}
+
+void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) {
+	out->header.unique = in->header.unique;
+	out->header.error = -EOPNOTSUPP;
+	out->header.len = sizeof(out->header);
+}
+
+void MockFS::read_request(mockfs_buf_in *in) {
+	ssize_t res;
+
+	res = read(m_fuse_fd, in, sizeof(*in));
+	if (res < 0 && !quit)
+		perror("read");
+	ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
+}
+
+void* MockFS::service(void *pthr_data) {
+	MockFS *mock_fs = (MockFS*)pthr_data;
+	mock_fs->m_daemon_id = pthread_self();
+
+	quit = 0;
+	signal(SIGUSR1, sigint_handler);
+
+	mock_fs->loop();
+
+	return (NULL);
+}
+
+void MockFS::unmount() {
+	::unmount("mountpoint", 0);
+}

Added: projects/fuse2/tests/sys/fs/fuse/mockfs.hh
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh	Fri Mar  1 23:53:05 2019	(r344715)
@@ -0,0 +1,131 @@
+/*-
+ * Copyright (c) 2019 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+extern "C" {
+#include <sys/types.h>
+
+#include <pthread.h>
+
+#include "fuse_kernel.h"
+}
+
+#include <gmock/gmock.h>
+
+#define SET_OUT_HEADER_LEN(out, variant) { \
+	(out)->header.len = (sizeof((out)->header) + \
+			     sizeof((out)->body.variant)); \
+}
+
+extern int verbosity;
+
+union fuse_payloads_in {
+	fuse_forget_in	forget;
+	fuse_init_in	init;
+	char		lookup[0];
+	/* value is from fuse_kern_chan.c in fusefs-libs */
+	uint8_t		bytes[0x21000 - sizeof(struct fuse_in_header)];
+};
+
+struct mockfs_buf_in {
+	fuse_in_header		header;
+	union fuse_payloads_in	body;
+};
+
+union fuse_payloads_out {
+	fuse_init_out		init;
+	fuse_entry_out		entry;
+	fuse_attr_out		attr;
+};
+
+struct mockfs_buf_out {
+	fuse_out_header		header;
+	union fuse_payloads_out	body;
+};
+
+/*
+ * Fake FUSE filesystem
+ *
+ * "Mounts" a filesystem to a temporary directory and services requests
+ * according to the programmed expectations.
+ *
+ * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api.
+ */
+class MockFS {
+	public:
+	/* thread id of the fuse daemon thread */
+	pthread_t m_daemon_id;
+
+	private:
+	/* file descriptor of /dev/fuse control device */
+	int m_fuse_fd;
+	
+	/* pid of the test process */
+	pid_t m_pid;
+
+	/* 
+	 * Thread that's running the mockfs daemon.
+	 *
+	 * It must run in a separate thread so it doesn't deadlock with the
+	 * client test code.
+	 */
+	pthread_t m_thr;
+
+	/* Initialize a session after mounting */
+	void init();
+
+	/* Default request handler */
+	void process_default(const mockfs_buf_in*, mockfs_buf_out*);
+
+	/* Entry point for the daemon thread */
+	static void* service(void*);
+
+	/* Read, but do not process, a single request from the kernel */
+	void read_request(mockfs_buf_in*);
+
+	public:
+	/* Create a new mockfs and mount it to a tempdir */
+	MockFS();
+	virtual ~MockFS();
+
+	/* Process FUSE requests endlessly */
+	void loop();
+
+	/* 
+	 * Request handler
+	 *
+	 * This method is expected to provide the response to each FUSE
+	 * operation.  Responses must be immediate (so this method can't be used
+	 * for testing a daemon with queue depth > 1).  Test cases must define
+	 * each response using Googlemock expectations
+	 */
+	MOCK_METHOD2(process, void(const mockfs_buf_in*, mockfs_buf_out*));
+
+	/* Gracefully unmount */
+	void unmount();
+};

Added: projects/fuse2/tests/sys/fs/fuse/utils.cc
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fuse/utils.cc	Fri Mar  1 23:53:05 2019	(r344715)
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (c) 2019 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include "mockfs.hh"

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201903012353.x21Nr5do051752>