Index: projects/fuse2/tests/sys/fs/fuse/Makefile =================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344949) +++ projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344950) @@ -1,137 +1,143 @@ # $FreeBSD$ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create +ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= link ATF_TESTS_CXX+= lookup ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= release ATF_TESTS_CXX+= rename ATF_TESTS_CXX+= rmdir ATF_TESTS_CXX+= setattr ATF_TESTS_CXX+= statfs ATF_TESTS_CXX+= symlink ATF_TESTS_CXX+= unlink ATF_TESTS_CXX+= write SRCS.access+= access.cc SRCS.access+= getmntopts.c SRCS.access+= mockfs.cc SRCS.access+= utils.cc SRCS.create+= create.cc SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc + +SRCS.flush+= flush.cc +SRCS.flush+= getmntopts.c +SRCS.flush+= mockfs.cc +SRCS.flush+= utils.cc SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc SRCS.link+= getmntopts.c SRCS.link+= link.cc SRCS.link+= mockfs.cc SRCS.link+= utils.cc SRCS.lookup+= getmntopts.c SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc SRCS.mkdir+= getmntopts.c SRCS.mkdir+= mockfs.cc SRCS.mkdir+= mkdir.cc SRCS.mkdir+= utils.cc SRCS.mknod+= getmntopts.c SRCS.mknod+= mockfs.cc SRCS.mknod+= mknod.cc SRCS.mknod+= utils.cc SRCS.open+= getmntopts.c SRCS.open+= mockfs.cc SRCS.open+= open.cc SRCS.open+= utils.cc SRCS.readlink+= getmntopts.c SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc SRCS.readlink+= utils.cc SRCS.release+= getmntopts.c SRCS.release+= mockfs.cc SRCS.release+= release.cc SRCS.release+= utils.cc SRCS.rename+= getmntopts.c SRCS.rename+= mockfs.cc SRCS.rename+= rename.cc SRCS.rename+= utils.cc SRCS.rmdir+= getmntopts.c SRCS.rmdir+= mockfs.cc SRCS.rmdir+= rmdir.cc SRCS.rmdir+= utils.cc SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc SRCS.setattr+= setattr.cc SRCS.setattr+= utils.cc SRCS.statfs+= getmntopts.c SRCS.statfs+= mockfs.cc SRCS.statfs+= statfs.cc SRCS.statfs+= utils.cc SRCS.symlink+= getmntopts.c SRCS.symlink+= mockfs.cc SRCS.symlink+= symlink.cc SRCS.symlink+= utils.cc SRCS.unlink+= getmntopts.c SRCS.unlink+= mockfs.cc SRCS.unlink+= unlink.cc SRCS.unlink+= utils.cc SRCS.write+= getmntopts.c SRCS.write+= mockfs.cc SRCS.write+= write.cc SRCS.write+= utils.cc # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 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+= util 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 Index: projects/fuse2/tests/sys/fs/fuse/flush.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/flush.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/flush.cc (revision 344950) @@ -0,0 +1,193 @@ +/*- + * 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 +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Flush: public FuseTest { + +const static uint64_t FH = 0xdeadbeef1a7ebabe; + +public: +void expect_flush(uint64_t ino, int times, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_FLUSH && + in->header.nodeid == ino && + in->body.flush.fh == Flush::FH); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke(r)); +} + +void expect_getattr(uint64_t ino) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(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; + })); + +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).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.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = Flush::FH; + })); + +} + +/* + * When testing FUSE_FLUSH, the FUSE_RELEASE calls are uninteresting. This + * expectation will silence googlemock warnings + */ +void expect_release() +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnErrno(0))); +} +}; + +// TODO: lock_owner stuff + +/* If a file descriptor is duplicated, every close causes FLUSH */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +TEST_F(Flush, DISABLED_dup) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_flush(ino, 2, ReturnErrno(0)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + fd2 = dup(fd); + + ASSERT_EQ(0, close(fd2)) << strerror(errno); + ASSERT_EQ(0, close(fd)) << strerror(errno); +} + +/* + * Some FUSE filesystem cache data internally and flush it on release. Such + * filesystems may generate errors during release. On Linux, these get + * returned by close(2). However, POSIX does not require close(2) to return + * this error. FreeBSD's fuse(4) should return EIO if it returns an error at + * all. + */ +/* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +TEST_F(Flush, DISABLED_eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_flush(ino, 1, ReturnErrno(EIO)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); +} + +/* A FUSE_FLUSH should be sent on close(2) */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +TEST_F(Flush, DISABLED_flush) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_flush(ino, 1, ReturnErrno(0)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_TRUE(0 == close(fd)) << strerror(errno); +} Property changes on: projects/fuse2/tests/sys/fs/fuse/flush.cc ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: projects/fuse2/tests/sys/fs/fuse/mockfs.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344949) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344950) @@ -1,198 +1,199 @@ /*- * 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 #include #include "fuse_kernel.h" } #include #define TIME_T_MAX (std::numeric_limits::max()) #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ } /* * Create an expectation on FUSE_LOOKUP and return it so the caller can set * actions. * * This must be a macro instead of a method because EXPECT_CALL returns a type * with a deleted constructor. */ #define EXPECT_LOOKUP(parent, path) \ EXPECT_CALL(*m_mock, process( \ ResultOf([=](auto in) { \ return (in->header.opcode == FUSE_LOOKUP && \ in->header.nodeid == (parent) && \ strcmp(in->body.lookup, (path)) == 0); \ }, Eq(true)), \ _) \ ) extern int verbosity; /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ struct fuse_create_out { struct fuse_entry_out entry; struct fuse_open_out open; }; union fuse_payloads_in { fuse_access_in access; /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; + fuse_flush_in flush; fuse_forget_in forget; fuse_init_in init; fuse_link_in link; char lookup[0]; fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; fuse_read_in read; fuse_release_in release; fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; char unlink[0]; fuse_write_in write; }; struct mockfs_buf_in { fuse_in_header header; union fuse_payloads_in body; }; union fuse_payloads_out { fuse_attr_out attr; fuse_create_out create; /* The protocol places no limits on the size of bytes */ uint8_t bytes[0x2000]; fuse_entry_out entry; fuse_init_out init; fuse_open_out open; fuse_statfs_out statfs; /* * The protocol places no limits on the length of the string. This is * merely convenient for testing. */ char str[80]; fuse_write_out write; }; struct mockfs_buf_out { fuse_out_header header; union fuse_payloads_out body; }; /* A function that can be invoked in place of MockFS::process */ typedef std::function ProcessMockerT; /* * Helper function used for setting an error expectation for any fuse operation. * The operation will return the supplied error */ ProcessMockerT ReturnErrno(int error); /* Helper function used for returning negative cache entries for LOOKUP */ std::function ReturnNegativeCache(const struct timespec *entry_valid); /* * 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 { /* * thread id of the fuse daemon thread * * It must run in a separate thread so it doesn't deadlock with the * client test code. */ pthread_t m_daemon_id; /* file descriptor of /dev/fuse control device */ int m_fuse_fd; /* pid of the test process */ pid_t m_pid; /* Initialize a session after mounting */ void init(); /* Is pid from a process that might be involved in the test? */ bool pid_ok(pid_t pid); /* 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: /* Maximum size of a FUSE_WRITE write */ uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ MockFS(); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ void kill_daemon(); /* 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(); };