Index: projects/fuse2/tests/sys/fs/fuse/access.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/access.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/access.cc (revision 345119) @@ -1,100 +1,96 @@ /*- * 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. */ extern "C" { //#include #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; -class Access: public FuseTest {}; +class Access: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} +}; /* TODO: test methods for the default_permissions mount option */ /* The error case of FUSE_ACCESS. */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ TEST_F(Access, DISABLED_eaccess) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; mode_t access_mode = X_OK; - EXPECT_LOOKUP(1, RELPATH).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; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_ACCESS && in->header.nodeid == ino && in->body.access.mask == access_mode); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EACCES))); ASSERT_NE(0, access(FULLPATH, access_mode)); ASSERT_EQ(EACCES, errno); } /* The successful case of FUSE_ACCESS. */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ TEST_F(Access, DISABLED_ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; mode_t access_mode = R_OK; - EXPECT_LOOKUP(1, RELPATH).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; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_ACCESS && in->header.nodeid == ino && in->body.access.mask == access_mode); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/create.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/create.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/create.cc (revision 345119) @@ -1,380 +1,373 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Create: public FuseTest {}; /* * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the * attribute cache */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ TEST_F(Create, DISABLED_attr_cache) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).Times(0); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * The fuse daemon fails the request with EEXIST. This usually indicates a * race condition: some other FUSE client created the file in between when the * kernel checked for it with lookup and tried to create it with create */ TEST_F(Create, eexist) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EEXIST))); EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); EXPECT_EQ(EEXIST, errno); } /* * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback * to FUSE_MKNOD/FUSE_OPEN */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ TEST_F(Create, DISABLED_Enosys) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(ENOSYS))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_mknod_in); return (in->header.opcode == FUSE_MKNOD && in->body.mknod.mode == (S_IFREG | mode) && in->body.mknod.rdev == 0 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); })); /* 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; })); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Creating a new file after FUSE_LOOKUP returned a negative cache entry */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Create, DISABLED_entry_cache_negative) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; /* * Set entry_valid = 0 because this test isn't concerned with whether * or not we actually cache negative entries, only with whether we * interpret negative cache responses correctly. */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; /* create will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); /* 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; })); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Creating a new file should purge any negative namecache entries */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Create, DISABLED_entry_cache_negative_purge) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; /* create will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) .RetiresOnSaturation(); /* Then the CREATE should purge the negative cache entry */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.attr_valid = UINT64_MAX; })); /* 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; })); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(1, RELPATH).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = ino; - out->body.entry.attr.mode = S_IFREG | mode; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELPATH, ino, S_IFREG | mode, 1); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * The daemon is responsible for checking file permissions (unless the * default_permissions mount option was used) */ TEST_F(Create, eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); EXPECT_EQ(EPERM, errno); } TEST_F(Create, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_CREATE && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); /* 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; })); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } Index: projects/fuse2/tests/sys/fs/fuse/flush.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/flush.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/flush.cc (revision 345119) @@ -1,194 +1,150 @@ /*- * 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. */ 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); + in->body.flush.fh == 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; - })); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } -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_open(ino, 0, 1); + expect_getattr(ino, 0); 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_open(ino, 0, 1); + expect_getattr(ino, 0); 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_open(ino, 0, 1); + expect_getattr(ino, 0); 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); } Index: projects/fuse2/tests/sys/fs/fuse/fsync.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/fsync.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/fsync.cc (revision 345119) @@ -1,349 +1,283 @@ /*- * 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. */ extern "C" { #include #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; /* * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28. * This bit was actually part of kernel protocol version 5.2, but never * documented until after 7.28 */ #ifndef FUSE_FSYNC_FDATASYNC #define FUSE_FSYNC_FDATASYNC 1 #endif class Fsync: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; void expect_fsync(uint64_t ino, uint32_t flags, int error) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC && in->header.nodeid == ino && //(pid_t)in->header.pid == getpid() && in->body.fsync.fh == FH && in->body.fsync.fsync_flags == flags); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(error))); } -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; - out->body.attr.attr_valid = UINT64_MAX; - })); -} - 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; - })); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } -void expect_open(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(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 = FH; - })); -} - -void expect_release(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_RELEASE && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(0))); -} - void expect_write(uint64_t ino, uint64_t size, const void *contents) { - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - const char *buf = (const char*)in->body.bytes + - sizeof(struct fuse_write_in); - - return (in->header.opcode == FUSE_WRITE && - in->header.nodeid == ino && - 0 == bcmp(buf, contents, size)); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, write); - out->body.write.size = size; - })); + FuseTest::expect_write(ino, 0, size, size, 0, contents); } }; /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_aio_fsync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; struct aiocb iocb, *piocb; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); bzero(&iocb, sizeof(iocb)); iocb.aio_fildes = fd; ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno); ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * fuse(4) should NOT fsync during VOP_RELEASE or VOP_INACTIVE * * This test only really make sense in writeback caching mode, but it should * still pass in any cache mode. */ TEST_F(Fsync, close) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETATTR); }, 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 })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC); }, Eq(true)), _) ).Times(0); - expect_release(ino); + expect_release(ino, 1, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); close(fd); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); ASSERT_NE(0, fdatasync(fd)); ASSERT_EQ(EIO, errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fdatasync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); ASSERT_EQ(0, fdatasync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fsync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Fsync should sync a file with dirty metadata but clean data */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fsync_metadata_only) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; mode_t mode = 0755; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETATTR); }, 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 | mode; })); expect_fsync(ino, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fchmod(fd, mode)) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } // fsync()ing a file that isn't dirty should be a no-op TEST_F(Fsync, nop) { 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); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_WRONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } - +// TODO: ENOSYS test Index: projects/fuse2/tests/sys/fs/fuse/fsyncdir.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/fsyncdir.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/fsyncdir.cc (revision 345119) @@ -1,186 +1,163 @@ /*- * 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. */ extern "C" { #include #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; /* * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28. * This bit was actually part of kernel protocol version 5.2, but never * documented until after 7.28 */ #ifndef FUSE_FSYNC_FDATASYNC #define FUSE_FSYNC_FDATASYNC 1 #endif class FsyncDir: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; void expect_fsyncdir(uint64_t ino, uint32_t flags, int error) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNCDIR && in->header.nodeid == ino && //(pid_t)in->header.pid == getpid() && in->body.fsyncdir.fh == FH && in->body.fsyncdir.fsync_flags == flags); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(error))); } 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_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_opendir(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPENDIR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(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 = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } }; /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(FsyncDir, DISABLED_aio_fsync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct aiocb iocb, *piocb; int fd; expect_lookup(RELPATH, ino); expect_opendir(ino); expect_fsyncdir(ino, 0, 0); fd = open(FULLPATH, O_DIRECTORY); ASSERT_LE(0, fd) << strerror(errno); bzero(&iocb, sizeof(iocb)); iocb.aio_fildes = fd; ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno); ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(FsyncDir, DISABLED_eio) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_opendir(ino); expect_fsyncdir(ino, 0, EIO); fd = open(FULLPATH, O_DIRECTORY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_NE(0, fsync(fd)); ASSERT_EQ(EIO, errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(FsyncDir, DISABLED_fsyncdata) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_opendir(ino); expect_fsyncdir(ino, FUSE_FSYNC_FDATASYNC, 0); fd = open(FULLPATH, O_DIRECTORY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fdatasync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Unlike regular files, the kernel doesn't know whether a directory is or * isn't dirty, so fuse(4) should always send FUSE_FSYNCDIR on fsync(2) */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(FsyncDir, DISABLED_fsync) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_opendir(ino); expect_fsyncdir(ino, 0, 0); fd = open(FULLPATH, O_DIRECTORY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } Index: projects/fuse2/tests/sys/fs/fuse/getattr.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/getattr.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/getattr.cc (revision 345119) @@ -1,214 +1,188 @@ /*- * 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. */ #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_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.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_LOOKUP(1, RELPATH).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_lookup(RELPATH, ino, S_IFREG | 0644, 2); 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_LOOKUP(1, RELPATH).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_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(ENOENT))); 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_LOOKUP(1, RELPATH).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_lookup(RELPATH, ino, S_IFREG | 0644, 1); 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 } Index: projects/fuse2/tests/sys/fs/fuse/link.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/link.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/link.cc (revision 345119) @@ -1,107 +1,103 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; -class Link: public FuseTest {}; +class Link: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} +}; TEST_F(Link, emlink) { const char FULLPATH[] = "mountpoint/lnk"; const char RELPATH[] = "lnk"; const char FULLDST[] = "mountpoint/dst"; const char RELDST[] = "dst"; uint64_t dst_ino = 42; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); - EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = dst_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELDST, dst_ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(struct fuse_link_in); return (in->header.opcode == FUSE_LINK && in->body.link.oldnodeid == dst_ino && (0 == strcmp(name, RELPATH))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EMLINK))); EXPECT_EQ(-1, link(FULLDST, FULLPATH)); EXPECT_EQ(EMLINK, errno); } TEST_F(Link, ok) { const char FULLPATH[] = "mountpoint/src"; const char RELPATH[] = "src"; const char FULLDST[] = "mountpoint/dst"; const char RELDST[] = "dst"; uint64_t dst_ino = 42; const uint64_t ino = 42; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); - EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = dst_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELDST, dst_ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(struct fuse_link_in); return (in->header.opcode == FUSE_LINK && in->body.link.oldnodeid == dst_ino && (0 == strcmp(name, RELPATH))); }, 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; })); EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/locks.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/locks.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/locks.cc (revision 345119) @@ -1,475 +1,434 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" /* This flag value should probably be defined in fuse_kernel.h */ #define OFFSET_MAX 0x7fffffffffffffffLL using namespace testing; /* For testing filesystems without posix locking support */ class Fallback: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; -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; - out->body.attr.attr_valid = UINT64_MAX; - })); -} - 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; - })); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } -void expect_open(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(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 = FH; - })); -} - }; /* For testing filesystems with posix locking support */ class Locks: public Fallback { virtual void SetUp() { m_init_flags = FUSE_POSIX_LOCKS; Fallback::SetUp(); } }; class GetlkFallback: public Fallback {}; class Getlk: public Locks {}; class SetlkFallback: public Fallback {}; class Setlk: public Locks {}; class SetlkwFallback: public Fallback {}; class Setlkw: public Locks {}; /* * If the fuse filesystem does not support posix file locks, then the kernel * should fall back to local locks. */ TEST_F(GetlkFallback, local) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = getpid(); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the filesystem has no locks that fit the description, the filesystem * should return F_UNLCK */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Getlk, DISABLED_no_locks) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && in->body.getlk.lk.pid == 10); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; })); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); ASSERT_EQ(F_UNLCK, fl.l_type); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* A different pid does have a lock */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Getlk, DISABLED_lock_exists) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; pid_t pid2 = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && in->body.getlk.lk.pid == 10); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk.start = 100; out->body.getlk.lk.end = 199; out->body.getlk.lk.type = F_WRLCK; out->body.getlk.lk.pid = (uint32_t)pid2;; })); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); EXPECT_EQ(100, fl.l_start); EXPECT_EQ(100, fl.l_len); EXPECT_EQ(pid2, fl.l_pid); EXPECT_EQ(F_WRLCK, fl.l_type); EXPECT_EQ(SEEK_SET, fl.l_whence); EXPECT_EQ(0, fl.l_sysid); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the fuse filesystem does not support posix file locks, then the kernel * should fall back to local locks. */ TEST_F(SetlkFallback, local) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = getpid(); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Set a new lock with FUSE_SETLK */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlk, DISABLED_set) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && in->body.getlk.lk.pid == 10); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; })); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* l_len = 0 is a flag value that means to lock until EOF */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlk, DISABLED_set_eof) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == OFFSET_MAX && in->body.getlk.lk.type == F_RDLCK && in->body.getlk.lk.pid == 10); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; })); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 0; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Fail to set a new lock with FUSE_SETLK due to a conflict */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlk, DISABLED_eagain) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && in->body.getlk.lk.pid == 10); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EAGAIN))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl)); ASSERT_EQ(EAGAIN, errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the fuse filesystem does not support posix file locks, then the kernel * should fall back to local locks. */ TEST_F(SetlkwFallback, local) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = getpid(); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Set a new lock with FUSE_SETLK. If the lock is not available, then the * command should block. But to the kernel, that's the same as just being * slow, so we don't need a separate test method */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlkw, DISABLED_set) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && in->body.getlk.lk.pid == 10); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; })); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } Index: projects/fuse2/tests/sys/fs/fuse/lookup.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/lookup.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/lookup.cc (revision 345119) @@ -1,295 +1,292 @@ /*- * 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. */ extern "C" { #include } #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; + const uint64_t generation = 13; struct stat sb; EXPECT_LOOKUP(1, RELPATH).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; + out->body.entry.generation = generation; })); /* 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_LOOKUP(1, RELPATH).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; - })); + expect_getattr(ino, 0); /* 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) { const char FULLPATH[] = "mountpoint/does_not_exist"; const char RELPATH[] = "does_not_exist"; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_NE(0, access(FULLPATH, 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_LOOKUP(1, RELPATH).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 the daemon returns an error of 0 and an inode of 0, that's a flag for * "ENOENT and cache it" with the given entry_timeout */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ TEST_F(Lookup, DISABLED_entry_cache_negative) { struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; EXPECT_LOOKUP(1, "does_not_exist").Times(1) .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))); EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); EXPECT_EQ(ENOENT, errno); EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); EXPECT_EQ(ENOENT, errno); } /* Negative entry caches should timeout, too */ TEST_F(Lookup, entry_cache_negative_timeout) { const char *RELPATH = "does_not_exist"; const char *FULLPATH = "mountpoint/does_not_exist"; /* * 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. */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 250'000'000}; EXPECT_LOOKUP(1, RELPATH).Times(2) .WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid))); EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); usleep(2 * entry_valid.tv_nsec / 1000); /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, 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_LOOKUP(1, RELPATH).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); } + +// TODO: export_support +// After upgrading the protocol to 7.10, check that the kernel will only +// attempt to lookup "." and ".." if the filesystem sets FUSE_EXPORT_SUPPORT in +// the init flags. If not, then all lookups for those entries will return +// ESTALE. TEST_F(Lookup, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; EXPECT_LOOKUP(1, RELPATH).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); } // Lookup in a subdirectory of the fuse mount TEST_F(Lookup, subdir) { const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; const char DIRPATH[] = "some_dir"; const char RELPATH[] = "some_file.txt"; uint64_t dir_ino = 2; uint64_t file_ino = 3; EXPECT_LOOKUP(1, DIRPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = dir_ino; })); EXPECT_LOOKUP(dir_ino, RELPATH).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 = file_ino; })); /* * 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); } Index: projects/fuse2/tests/sys/fs/fuse/mkdir.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mkdir.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/mkdir.cc (revision 345119) @@ -1,188 +1,181 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Mkdir: public FuseTest {}; /* * EMLINK is possible on filesystems that limit the number of hard links to a * single file, like early versions of BtrFS */ TEST_F(Mkdir, emlink) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; mode_t mode = 0755; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_mkdir_in); return (in->header.opcode == FUSE_MKDIR && in->body.mkdir.mode == (S_IFDIR | mode) && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EMLINK))); ASSERT_NE(1, mkdir(FULLPATH, mode)); ASSERT_EQ(EMLINK, errno); } /* * Creating a new directory after FUSE_LOOKUP returned a negative cache entry */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Mkdir, DISABLED_entry_cache_negative) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; /* * Set entry_valid = 0 because this test isn't concerned with whether * or not we actually cache negative entries, only with whether we * interpret negative cache responses correctly. */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; /* mkdir will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_MKDIR && in->body.mkdir.mode == (S_IFDIR | mode) && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); } /* * Creating a new directory should purge any negative namecache entries */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; /* mkdir will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) .RetiresOnSaturation(); /* Then the MKDIR should purge the negative cache entry */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_open_in); return (in->header.opcode == FUSE_MKDIR && in->body.mkdir.mode == (S_IFDIR | mode) && (0 == strcmp(RELPATH, name))); }, 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_IFDIR | mode; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; })); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(1, RELPATH).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = ino; - out->body.entry.attr.mode = S_IFDIR | mode; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELPATH, ino, S_IFDIR | mode, 1); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); } TEST_F(Mkdir, ok) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; mode_t mode = 0755; uint64_t ino = 42; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes + sizeof(fuse_mkdir_in); return (in->header.opcode == FUSE_MKDIR && in->body.mkdir.mode == (S_IFDIR | mode) && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/open.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/open.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/open.cc (revision 345119) @@ -1,206 +1,187 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Open: public FuseTest { public: /* Test an OK open of a file with the given flags */ void test_ok(int os_flags, int fuse_flags) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; - EXPECT_LOOKUP(1, RELPATH).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.attr_valid = UINT64_MAX; - })); - + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && in->body.open.flags == (uint32_t)fuse_flags && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); })); /* 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; })); fd = open(FULLPATH, os_flags); EXPECT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } }; /* * The fuse daemon fails the request with enoent. This usually indicates a * race condition: some other FUSE client removed the file in between when the * kernel checked for it with lookup and tried to open it */ TEST_F(Open, enoent) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).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; - })); - + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_NE(0, open(FULLPATH, O_RDONLY)); EXPECT_EQ(ENOENT, errno); } /* * The daemon is responsible for checking file permissions (unless the * default_permissions mount option was used) */ TEST_F(Open, eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).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; - })); - + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, open(FULLPATH, O_RDONLY)); EXPECT_EQ(EPERM, errno); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ TEST_F(Open, DISABLED_o_append) { test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND); } /* The kernel is supposed to filter out this flag */ TEST_F(Open, o_creat) { test_ok(O_WRONLY | O_CREAT, O_WRONLY); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ TEST_F(Open, DISABLED_o_direct) { test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT); } /* The kernel is supposed to filter out this flag */ TEST_F(Open, o_excl) { test_ok(O_WRONLY | O_EXCL, O_WRONLY); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236329 */ TEST_F(Open, DISABLED_o_exec) { test_ok(O_EXEC, O_EXEC); } /* The kernel is supposed to filter out this flag */ TEST_F(Open, o_noctty) { test_ok(O_WRONLY | O_NOCTTY, O_WRONLY); } TEST_F(Open, o_rdonly) { test_ok(O_RDONLY, O_RDONLY); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ TEST_F(Open, DISABLED_o_trunc) { test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC); } TEST_F(Open, o_wronly) { test_ok(O_WRONLY, O_WRONLY); } TEST_F(Open, o_rdwr) { test_ok(O_RDWR, O_RDWR); } Index: projects/fuse2/tests/sys/fs/fuse/opendir.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/opendir.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/opendir.cc (revision 345119) @@ -1,156 +1,150 @@ /*- * 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. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Opendir: public FuseTest { public: 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_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } }; /* * The fuse daemon fails the request with enoent. This usually indicates a * race condition: some other FUSE client removed the file in between when the * kernel checked for it with lookup and tried to open it */ TEST_F(Opendir, enoent) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPENDIR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_NE(0, open(FULLPATH, O_DIRECTORY)); EXPECT_EQ(ENOENT, errno); } /* * The daemon is responsible for checking file permissions (unless the * default_permissions mount option was used) */ TEST_F(Opendir, eperm) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPENDIR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, open(FULLPATH, O_DIRECTORY)); EXPECT_EQ(EPERM, errno); } TEST_F(Opendir, open) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPENDIR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, open); })); EXPECT_LE(0, open(FULLPATH, O_DIRECTORY)) << strerror(errno); } TEST_F(Opendir, opendir) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, statfs); })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPENDIR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, open); })); errno = 0; EXPECT_NE(NULL, opendir(FULLPATH)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/read.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/read.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/read.cc (revision 345119) @@ -1,568 +1,505 @@ /*- * 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. */ extern "C" { #include #include #include #include #include #include #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Read: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; -void expect_getattr(uint64_t ino, uint64_t size) -{ - /* 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; - out->body.attr.attr.size = size; - })); - -} - 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, uint32_t flags, 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 = Read::FH; - out->body.open.open_flags = flags; - })); -} - -void expect_read(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, - const void *contents) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READ && - in->header.nodeid == ino && - in->body.read.fh == Read::FH && - in->body.read.offset == offset && - in->body.read.size == isize); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(struct fuse_out_header) + osize; - memmove(out->body.bytes, contents, osize); - })).RetiresOnSaturation(); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } }; class AioRead: public Read { virtual void SetUp() { const char *node = "vfs.aio.enable_unsafe"; int val = 0; size_t size = sizeof(val); ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) << strerror(errno); // TODO: With GoogleTest 1.8.2, use SKIP instead if (!val) FAIL() << "vfs.aio.enable_unsafe must be set for this test"; FuseTest::SetUp(); } }; class ReadAhead: public Read, public WithParamInterface { virtual void SetUp() { m_maxreadahead = GetParam(); Read::SetUp(); } }; /* AIO reads need to set the header's pid field correctly */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ TEST_F(AioRead, aio_read) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; struct aiocb iocb, *piocb; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); iocb.aio_nbytes = bufsize; iocb.aio_fildes = fd; iocb.aio_buf = buf; iocb.aio_offset = 0; iocb.aio_sigevent.sigev_notify = SIGEV_NONE; ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno); ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* 0-length reads shouldn't cause any confusion */ TEST_F(Read, direct_io_read_nothing) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; uint64_t offset = 100; char buf[80]; expect_lookup(RELPATH, ino); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, offset + 1000); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * With direct_io, reads should not fill the cache. They should go straight to * the daemon */ TEST_F(Read, direct_io_pread) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; uint64_t offset = 100; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, offset + bufsize); expect_read(ino, offset, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * With direct_io, filesystems are allowed to return less data than is * requested. fuse(4) should return a short read to userland. */ TEST_F(Read, direct_io_short_read) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefghijklmnop"; uint64_t ino = 42; int fd; uint64_t offset = 100; ssize_t bufsize = strlen(CONTENTS); ssize_t halfbufsize = bufsize / 2; char buf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, offset + bufsize); expect_read(ino, offset, bufsize, halfbufsize, CONTENTS); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } TEST_F(Read, eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EIO))); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(EIO, errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } TEST_F(Read, mmap) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t len; ssize_t bufsize = strlen(CONTENTS); void *p; //char buf[bufsize]; len = getpagesize(); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); /* mmap may legitimately try to read more data than is available */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == Read::FH && in->body.read.offset == 0 && in->body.read.size >= bufsize); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(struct fuse_out_header) + bufsize; memmove(out->body.bytes, CONTENTS, bufsize); })); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); ASSERT_NE(MAP_FAILED, p) << strerror(errno); ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize)); ASSERT_EQ(0, munmap(p, len)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass * cache and to straight to the daemon */ TEST_F(Read, o_direct) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); // Fill the cache ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); // Reads with o_direct should bypass the cache expect_read(ino, 0, bufsize, bufsize, CONTENTS); ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno); ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } TEST_F(Read, pread) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; /* * Set offset to a maxbcachebuf boundary so we'll be sure what offset * to read from. Without this, the read might start at a lower offset. */ uint64_t offset = m_maxbcachebuf; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, offset + bufsize); expect_read(ino, offset, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } TEST_F(Read, read) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* If the filesystem allows it, the kernel should try to readahead */ TEST_F(Read, default_readahead) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS0 = "abcdefghijklmnop"; uint64_t ino = 42; int fd; ssize_t bufsize = 8; /* hard-coded in fuse_internal.c */ size_t default_maxreadahead = 65536; ssize_t filesize = default_maxreadahead * 2; char *contents; char buf[bufsize]; const char *contents1 = CONTENTS0 + bufsize; contents = (char*)calloc(1, filesize); ASSERT_NE(NULL, contents); memmove(contents, CONTENTS0, strlen(CONTENTS0)); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, filesize); expect_read(ino, 0, default_maxreadahead, default_maxreadahead, contents); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize)); /* A subsequent read should be serviced by cache */ ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, contents1, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Reading with sendfile should work (though it obviously won't be 0-copy) */ TEST_F(Read, sendfile) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; int sp[2]; off_t sbytes; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); /* Like mmap, sendfile may request more data than is available */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == Read::FH && in->body.read.offset == 0 && in->body.read.size >= bufsize); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(struct fuse_out_header) + bufsize; memmove(out->body.bytes, CONTENTS, bufsize); })); ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) << strerror(errno); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)) << strerror(errno); ASSERT_EQ(bufsize, read(sp[0], buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); close(sp[1]); close(sp[0]); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* sendfile should fail gracefully if fuse declines the read */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */ TEST_F(Read, DISABLED_sendfile_eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); int sp[2]; off_t sbytes; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EIO))); ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) << strerror(errno); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)); close(sp[1]); close(sp[0]); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* fuse(4) should honor the filesystem's requested m_readahead parameter */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236472 */ TEST_P(ReadAhead, DISABLED_readahead) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS0 = "abcdefghijklmnop"; uint64_t ino = 42; int fd; ssize_t bufsize = 8; ssize_t filesize = m_maxbcachebuf * 2; char *contents; char buf[bufsize]; ASSERT_TRUE(GetParam() < (uint32_t)m_maxbcachebuf) << "Test assumes that max_readahead < maxbcachebuf"; contents = (char*)calloc(1, filesize); ASSERT_NE(NULL, contents); memmove(contents, CONTENTS0, strlen(CONTENTS0)); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, filesize); /* fuse(4) should only read ahead the allowed amount */ expect_read(ino, 0, GetParam(), GetParam(), contents); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize)); /* Deliberately leak fd. close(2) will be tested in release.cc */ } INSTANTIATE_TEST_CASE_P(RA, ReadAhead, ::testing::Values(0u, 2048u)); Index: projects/fuse2/tests/sys/fs/fuse/readdir.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/readdir.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/readdir.cc (revision 345119) @@ -1,334 +1,301 @@ /*- * 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. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; using namespace std; class Readdir: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; public: 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_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_opendir(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_STATFS); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, statfs); - })); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPENDIR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(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 = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } void expect_readdir(uint64_t ino, uint64_t off, vector &ents) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READDIR && in->header.nodeid == ino && in->body.readdir.offset == off); }, Eq(true)), _) ).WillRepeatedly(Invoke([=](auto in, auto out) { struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes; int i = 0; out->header.unique = in->header.unique; 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); })); } }; /* FUSE_READDIR returns nothing but "." and ".." */ TEST_F(Readdir, dots) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; DIR *dir; struct dirent *de; vector ents(2); vector empty_ents(0); const char *dot = "."; const char *dotdot = ".."; expect_lookup(RELPATH, ino); expect_opendir(ino); ents[0].d_fileno = 2; ents[0].d_off = 2000; ents[0].d_namlen = strlen(dotdot); ents[0].d_type = DT_DIR; strncpy(ents[0].d_name, dotdot, ents[0].d_namlen); ents[1].d_fileno = 3; ents[1].d_off = 3000; ents[1].d_namlen = strlen(dot); ents[1].d_type = DT_DIR; strncpy(ents[1].d_name, dot, ents[1].d_namlen); expect_readdir(ino, 0, ents); expect_readdir(ino, 3000, empty_ents); errno = 0; dir = opendir(FULLPATH); ASSERT_NE(NULL, dir) << strerror(errno); errno = 0; de = readdir(dir); ASSERT_NE(NULL, de) << strerror(errno); EXPECT_EQ(2ul, de->d_fileno); /* * fuse(4) doesn't actually set d_off, which is ok for now because * nothing uses it. */ //EXPECT_EQ(2000, de->d_off); EXPECT_EQ(DT_DIR, de->d_type); EXPECT_EQ(2, de->d_namlen); EXPECT_EQ(0, strcmp("..", de->d_name)); errno = 0; de = readdir(dir); ASSERT_NE(NULL, de) << strerror(errno); EXPECT_EQ(3ul, de->d_fileno); //EXPECT_EQ(3000, de->d_off); EXPECT_EQ(DT_DIR, de->d_type); EXPECT_EQ(1, de->d_namlen); EXPECT_EQ(0, strcmp(".", de->d_name)); ASSERT_EQ(NULL, readdir(dir)); ASSERT_EQ(0, errno); /* Deliberately leak dir. RELEASEDIR will be tested separately */ } TEST_F(Readdir, eio) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; DIR *dir; struct dirent *de; expect_lookup(RELPATH, ino); expect_opendir(ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READDIR && in->header.nodeid == ino && in->body.readdir.offset == 0); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EIO))); errno = 0; dir = opendir(FULLPATH); ASSERT_NE(NULL, dir) << strerror(errno); errno = 0; de = readdir(dir); ASSERT_EQ(NULL, de); ASSERT_EQ(EIO, errno); /* Deliberately leak dir. RELEASEDIR will be tested separately */ } /* * FUSE_READDIR returns nothing, not even "." and "..". This is legal, though * the filesystem obviously won't be fully functional. */ TEST_F(Readdir, nodots) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; DIR *dir; expect_lookup(RELPATH, ino); expect_opendir(ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READDIR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->header.len = sizeof(out->header); })); errno = 0; dir = opendir(FULLPATH); ASSERT_NE(NULL, dir) << strerror(errno); errno = 0; ASSERT_EQ(NULL, readdir(dir)); ASSERT_EQ(0, errno); /* Deliberately leak dir. RELEASEDIR will be tested separately */ } /* telldir(3) and seekdir(3) should work with fuse */ TEST_F(Readdir, seekdir) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; DIR *dir; struct dirent *de; /* * use enough entries to be > 4096 bytes, so getdirentries must be * called * multiple times. */ vector ents0(122), ents1(102), ents2(30); long bookmark; int i = 0; for (auto& it: ents0) { snprintf(it.d_name, MAXNAMLEN, "file.%d", i); it.d_fileno = 2 + i; it.d_off = (2 + i) * 1000; it.d_namlen = strlen(it.d_name); it.d_type = DT_REG; i++; } for (auto& it: ents1) { snprintf(it.d_name, MAXNAMLEN, "file.%d", i); it.d_fileno = 2 + i; it.d_off = (2 + i) * 1000; it.d_namlen = strlen(it.d_name); it.d_type = DT_REG; i++; } for (auto& it: ents2) { snprintf(it.d_name, MAXNAMLEN, "file.%d", i); it.d_fileno = 2 + i; it.d_off = (2 + i) * 1000; it.d_namlen = strlen(it.d_name); it.d_type = DT_REG; i++; } expect_lookup(RELPATH, ino); expect_opendir(ino); expect_readdir(ino, 0, ents0); expect_readdir(ino, 123000, ents1); expect_readdir(ino, 225000, ents2); errno = 0; dir = opendir(FULLPATH); ASSERT_NE(NULL, dir) << strerror(errno); for (i=0; i < 128; i++) { errno = 0; de = readdir(dir); ASSERT_NE(NULL, de) << strerror(errno); EXPECT_EQ(2 + (ino_t)i, de->d_fileno); } bookmark = telldir(dir); for (; i < 232; i++) { errno = 0; de = readdir(dir); ASSERT_NE(NULL, de) << strerror(errno); EXPECT_EQ(2 + (ino_t)i, de->d_fileno); } seekdir(dir, bookmark); de = readdir(dir); ASSERT_NE(NULL, de) << strerror(errno); EXPECT_EQ(130ul, de->d_fileno); /* Deliberately leak dir. RELEASEDIR will be tested separately */ } Index: projects/fuse2/tests/sys/fs/fuse/readlink.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/readlink.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/readlink.cc (revision 345119) @@ -1,99 +1,95 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; -class Readlink: public FuseTest {}; +class Readlink: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFLNK | 0777, 1); +} +}; TEST_F(Readlink, eloop) { const char FULLPATH[] = "mountpoint/src"; const char RELPATH[] = "src"; const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFLNK | 0777; - out->body.entry.nodeid = ino; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READLINK && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(ELOOP))); EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf))); EXPECT_EQ(ELOOP, errno); } TEST_F(Readlink, ok) { const char FULLPATH[] = "mountpoint/src"; const char RELPATH[] = "src"; const char dst[] = "dst"; const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFLNK | 0777; - out->body.entry.nodeid = ino; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READLINK && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; strlcpy(out->body.str, dst, sizeof(out->body.str)); out->header.len = sizeof(out->header) + strlen(dst) + 1; })); EXPECT_EQ((ssize_t)strlen(dst) + 1, readlink(FULLPATH, buf, sizeof(buf))); EXPECT_STREQ(dst, buf); } Index: projects/fuse2/tests/sys/fs/fuse/release.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/release.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/release.cc (revision 345119) @@ -1,200 +1,143 @@ /*- * 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. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Release: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; - public: -void expect_getattr(uint64_t ino) +void expect_lookup(const char *relpath, uint64_t ino, int times) { - /* 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; - })); - + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, times); } - -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 = Release::FH; - })); - -} - -void expect_release(uint64_t ino, int times, ProcessMockerT r) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_RELEASE && - in->header.nodeid == ino && - in->body.release.fh == Release::FH); - }, Eq(true)), - _) - ).Times(times) - .WillRepeatedly(Invoke(r)); -} }; // TODO: lock owner stuff /* If a file descriptor is duplicated, only the last close causes RELEASE */ TEST_F(Release, 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_release(ino, 1, ReturnErrno(0)); + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_release(ino, 1, 0); fd = open(FULLPATH, O_RDONLY); 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 */ TEST_F(Release, 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_release(ino, 1, ReturnErrno(EIO)); + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_release(ino, 1, EIO); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); } /* * fuse(4) will issue multiple FUSE_OPEN operations for the same file if it's * opened with different modes. Each FUSE_OPEN should get its own * FUSE_RELEASE. */ TEST_F(Release, multiple_opens) { 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, 2); - expect_getattr(ino); - expect_release(ino, 2, ReturnErrno(0)); + expect_lookup(RELPATH, ino, 2); + expect_open(ino, 0, 2); + expect_getattr(ino, 0); + expect_release(ino, 2, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); fd2 = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd2) << strerror(errno); ASSERT_EQ(0, close(fd2)) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } TEST_F(Release, ok) { 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_release(ino, 1, ReturnErrno(0)); + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_release(ino, 1, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/releasedir.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/releasedir.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/releasedir.cc (revision 345119) @@ -1,144 +1,110 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class ReleaseDir: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; - public: 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_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_opendir(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_STATFS); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, statfs); - })); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPENDIR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(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 = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } void expect_releasedir(uint64_t ino, ProcessMockerT r) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RELEASEDIR && in->header.nodeid == ino && in->body.release.fh == FH); }, Eq(true)), _) ).WillOnce(Invoke(r)); } }; /* If a file descriptor is duplicated, only the last close causes RELEASE */ TEST_F(ReleaseDir, dup) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; DIR *dir, *dir2; expect_lookup(RELPATH, ino); expect_opendir(ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READDIR && in->header.nodeid == ino && in->body.readdir.offset == 0); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->header.len = sizeof(out->header); })); expect_releasedir(ino, ReturnErrno(0)); dir = opendir(FULLPATH); ASSERT_NE(NULL, dir) << strerror(errno); dir2 = fdopendir(dup(dirfd(dir))); ASSERT_NE(NULL, dir2) << strerror(errno); ASSERT_EQ(0, closedir(dir)) << strerror(errno); ASSERT_EQ(0, closedir(dir2)) << strerror(errno); } TEST_F(ReleaseDir, ok) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; DIR *dir; expect_lookup(RELPATH, ino); expect_opendir(ino); expect_releasedir(ino, ReturnErrno(0)); dir = opendir(FULLPATH); ASSERT_NE(NULL, dir) << strerror(errno); ASSERT_EQ(0, closedir(dir)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/rename.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/rename.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/rename.cc (revision 345119) @@ -1,294 +1,249 @@ /*- * 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. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Rename: public FuseTest { public: int tmpfd = -1; char tmpfile[80] = "/tmp/fuse.rename.XXXXXX"; virtual void TearDown() { if (tmpfd >= 0) { close(tmpfd); unlink(tmpfile); } FuseTest::TearDown(); } }; // EINVAL, dst is subdir of src TEST_F(Rename, einval) { const char FULLDST[] = "mountpoint/src/dst"; const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; uint64_t src_ino = 42; - EXPECT_LOOKUP(1, RELSRC).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = src_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 2); EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); ASSERT_NE(0, rename(FULLSRC, FULLDST)); ASSERT_EQ(EINVAL, errno); } // source does not exist TEST_F(Rename, enoent) { const char FULLDST[] = "mountpoint/dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke(ReturnErrno(ENOENT))); ASSERT_NE(0, rename(FULLSRC, FULLDST)); ASSERT_EQ(ENOENT, errno); } /* * Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Rename, DISABLED_entry_cache_negative) { const char FULLDST[] = "mountpoint/dst"; const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; /* * Set entry_valid = 0 because this test isn't concerned with whether * or not we actually cache negative entries, only with whether we * interpret negative cache responses correctly. */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); - + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *src = (const char*)in->body.bytes + sizeof(fuse_rename_in); const char *dst = src + strlen(src) + 1; return (in->header.opcode == FUSE_RENAME && in->body.rename.newdir == dst_dir_ino && (0 == strcmp(RELDST, dst)) && (0 == strcmp(RELSRC, src))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); } /* * Renaming a file should purge any negative namecache entries for the dst */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Rename, DISABLED_entry_cache_negative_purge) { const char FULLDST[] = "mountpoint/dst"; const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; /* * Set entry_valid = 0 because this test isn't concerned with whether * or not we actually cache negative entries, only with whether we * interpret negative cache responses correctly. */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); - + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)) .RetiresOnSaturation(); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *src = (const char*)in->body.bytes + sizeof(fuse_rename_in); const char *dst = src + strlen(src) + 1; return (in->header.opcode == FUSE_RENAME && in->body.rename.newdir == dst_dir_ino && (0 == strcmp(RELDST, dst)) && (0 == strcmp(RELSRC, src))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(1, RELDST).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = ino; - out->body.entry.attr.mode = S_IFREG | 0644; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno); } TEST_F(Rename, exdev) { const char FULLB[] = "mountpoint/src"; const char RELB[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t b_ino = 42; tmpfd = mkstemp(tmpfile); ASSERT_LE(0, tmpfd) << strerror(errno); - EXPECT_LOOKUP(1, RELB).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = b_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELB, b_ino, S_IFREG | 0644, 2); ASSERT_NE(0, rename(tmpfile, FULLB)); ASSERT_EQ(EXDEV, errno); ASSERT_NE(0, rename(FULLB, tmpfile)); ASSERT_EQ(EXDEV, errno); } TEST_F(Rename, ok) { const char FULLDST[] = "mountpoint/dst"; const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *src = (const char*)in->body.bytes + sizeof(fuse_rename_in); const char *dst = src + strlen(src) + 1; return (in->header.opcode == FUSE_RENAME && in->body.rename.newdir == dst_dir_ino && (0 == strcmp(RELDST, dst)) && (0 == strcmp(RELSRC, src))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); } // Rename overwrites an existing destination file TEST_F(Rename, overwrite) { const char FULLDST[] = "mountpoint/dst"; const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; // The inode of the already-existing destination file uint64_t dst_ino = 2; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); - EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = dst_ino; - SET_OUT_HEADER_LEN(out, entry); - })); - + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); + expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *src = (const char*)in->body.bytes + sizeof(fuse_rename_in); const char *dst = src + strlen(src) + 1; return (in->header.opcode == FUSE_RENAME && in->body.rename.newdir == dst_dir_ino && (0 == strcmp(RELDST, dst)) && (0 == strcmp(RELSRC, src))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/rmdir.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/rmdir.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/rmdir.cc (revision 345119) @@ -1,91 +1,91 @@ /*- * 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. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; -class Rmdir: public FuseTest {}; - -TEST_F(Rmdir, enotempty) +class Rmdir: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) { - const char FULLPATH[] = "mountpoint/some_dir"; - const char RELPATH[] = "some_dir"; - uint64_t ino = 42; - - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, relpath).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = ino; out->body.entry.attr.nlink = 2; })); +} +}; + +TEST_F(Rmdir, enotempty) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RMDIR && 0 == strcmp(RELPATH, in->body.rmdir) && in->header.nodeid == 1); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(ENOTEMPTY))); ASSERT_NE(0, rmdir(FULLPATH)); ASSERT_EQ(ENOTEMPTY, errno); } TEST_F(Rmdir, ok) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr.nlink = 2; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RMDIR && 0 == strcmp(RELPATH, in->body.rmdir) && in->header.nodeid == 1); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/unlink.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/unlink.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/unlink.cc (revision 345119) @@ -1,90 +1,84 @@ /*- * 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 "mockfs.hh" #include "utils.hh" using namespace testing; -class Unlink: public FuseTest {}; +class Unlink: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} +}; TEST_F(Unlink, eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).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.attr.nlink = 1; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_UNLINK && 0 == strcmp(RELPATH, in->body.unlink) && in->header.nodeid == 1); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EPERM))); ASSERT_NE(0, unlink(FULLPATH)); ASSERT_EQ(EPERM, errno); } TEST_F(Unlink, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).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.attr.nlink = 1; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_UNLINK && 0 == strcmp(RELPATH, in->body.unlink) && in->header.nodeid == 1); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(0))); ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/utils.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/utils.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/utils.cc (revision 345119) @@ -1,109 +1,251 @@ /*- * 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. */ #include #include #include #include #include #include "mockfs.hh" #include "utils.hh" -class FuseEnv: public ::testing::Environment { +using namespace testing; + +class FuseEnv: public Environment { virtual void SetUp() { const char *mod_name = "fuse"; const char *devnode = "/dev/fuse"; const char *usermount_node = "vfs.usermount"; int usermount_val = 0; size_t usermount_size = sizeof(usermount_val); if (modfind(mod_name) == -1) { FAIL() << "Module " << mod_name << " could not be resolved"; } if (eaccess(devnode, R_OK | W_OK)) { if (errno == ENOENT) { FAIL() << devnode << " does not exist"; } else if (errno == EACCES) { FAIL() << devnode << " is not accessible by the current user"; } else { FAIL() << strerror(errno); } } sysctlbyname(usermount_node, &usermount_val, &usermount_size, NULL, 0); if (geteuid() != 0 && !usermount_val) FAIL() << "current user is not allowed to mount"; } }; void FuseTest::SetUp() { const char *node = "vfs.maxbcachebuf"; int val = 0; size_t size = sizeof(val); ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) << strerror(errno); m_maxbcachebuf = val; try { m_mock = new MockFS(m_maxreadahead, m_init_flags); } catch (std::system_error err) { FAIL() << err.what(); } } +void FuseTest::expect_getattr(uint64_t ino, uint64_t size) +{ + /* 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; + out->body.attr.attr.size = size; + out->body.attr.attr_valid = UINT64_MAX; + })); +} + +void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, + int times) +{ + EXPECT_LOOKUP(1, relpath) + .Times(times) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = mode; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 1; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void FuseTest::expect_open(uint64_t ino, uint32_t flags, 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 = FH; + out->body.open.open_flags = flags; + })); +} + +void FuseTest::expect_opendir(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(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 = FH; + })); +} + +void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == offset && + in->body.read.size == isize); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(struct fuse_out_header) + osize; + memmove(out->body.bytes, contents, osize); + })).RetiresOnSaturation(); +} + +void FuseTest::expect_release(uint64_t ino, int times, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE && + in->header.nodeid == ino && + in->body.release.fh == FH); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke(ReturnErrno(error))); +} +void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, uint32_t flags, const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *buf = (const char*)in->body.bytes + + sizeof(struct fuse_write_in); + bool pid_ok; + + if (in->body.write.write_flags & FUSE_WRITE_CACHE) + pid_ok = true; + else + pid_ok = (pid_t)in->header.pid == getpid(); + + return (in->header.opcode == FUSE_WRITE && + in->header.nodeid == ino && + in->body.write.fh == FH && + in->body.write.offset == offset && + in->body.write.size == isize && + pid_ok && + in->body.write.write_flags == flags && + 0 == bcmp(buf, contents, isize)); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, write); + out->body.write.size = osize; + })); +} + static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); exit(2); } int main(int argc, char **argv) { int ch; FuseEnv *fuse_env = new FuseEnv; - ::testing::InitGoogleTest(&argc, argv); - ::testing::AddGlobalTestEnvironment(fuse_env); + InitGoogleTest(&argc, argv); + AddGlobalTestEnvironment(fuse_env); while ((ch = getopt(argc, argv, "v")) != -1) { switch (ch) { case 'v': verbosity++; break; default: usage(argv[0]); break; } } return (RUN_ALL_TESTS()); } Index: projects/fuse2/tests/sys/fs/fuse/utils.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 345119) @@ -1,55 +1,115 @@ /*- * 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. */ +/* + * TODO: remove FUSE_WRITE_CACHE definition when upgrading to protocol 7.9. + * This bit was actually part of kernel protocol version 7.2, but never + * documented until 7.9 + */ +#ifndef FUSE_WRITE_CACHE +#define FUSE_WRITE_CACHE 1 +#endif + class FuseTest : public ::testing::Test { protected: uint32_t m_maxreadahead; uint32_t m_init_flags; MockFS *m_mock = NULL; + const static uint64_t FH = 0xdeadbeef1a7ebabe; public: int m_maxbcachebuf; /* * libfuse's default max_readahead is UINT_MAX, though it can be * lowered */ FuseTest(): FuseTest(UINT_MAX, 0) {} FuseTest(uint32_t maxreadahead): m_maxreadahead(maxreadahead) {} FuseTest(uint32_t maxreadahead, uint32_t init_flags): m_maxreadahead(maxreadahead), m_init_flags(init_flags) {} virtual void SetUp(); virtual void TearDown() { if (m_mock) delete m_mock; } + + /* + * Create an expectation that FUSE_GETATTR will be called for the given + * inode any number of times. It will respond with a few basic + * attributes, like the given size and the mode S_IFREG | 0644 + */ + void expect_getattr(uint64_t ino, uint64_t size); + + /* + * Create an expectation that FUSE_LOOKUP will be called for the given + * path exactly times times. It will respond with inode ino, mode + * mode, and cache validity forever. + */ + void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, + int times); + + /* + * Create an expectation that FUSE_GETATTR will be called for the given + * inode exactly times times. It will return with open_flags flags and + * file handle FH. + */ + void expect_open(uint64_t ino, uint32_t flags, int times); + + /* + * Create an expectation that FUSE_OPENDIR will be called exactly once + * for inode ino. + */ + void expect_opendir(uint64_t ino); + + /* + * Create an expectation that FUSE_READ will be called exactly once for + * the given inode, at offset offset and with size isize. It will + * return the first osize bytes from contents + */ + void expect_read(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, const void *contents); + + /* + * Create an expectation that FUSE_RELEASE will be called times times + * for the given inode, returning error error + */ + void expect_release(uint64_t ino, int times, int error); + + /* + * Create an expectation that FUSE_WRITE will be called exactly once + * for the given inode, at offset offset, with write_flags flags, + * size isize and buffer contents. It will return osize + */ + void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, uint32_t flags, const void *contents); }; Index: projects/fuse2/tests/sys/fs/fuse/write.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/write.cc (revision 345118) +++ projects/fuse2/tests/sys/fs/fuse/write.cc (revision 345119) @@ -1,752 +1,649 @@ /*- * 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. */ extern "C" { #include #include #include #include #include #include #include #include } #include "mockfs.hh" #include "utils.hh" -/* - * TODO: remove FUSE_WRITE_CACHE definition when upgrading to protocol 7.9. - * This bit was actually part of kernel protocol version 7.2, but never - * documented until 7.9 - */ -#ifndef FUSE_WRITE_CACHE -#define FUSE_WRITE_CACHE 1 -#endif - using namespace testing; class Write: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; -void expect_getattr(uint64_t ino, uint64_t size) -{ - /* 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; - out->body.attr.attr.size = size; - out->body.attr.attr_valid = UINT64_MAX; - })); -} - 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; - })); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } -void expect_open(uint64_t ino, uint32_t flags, 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 = Write::FH; - out->body.open.open_flags = flags; - })); -} - -void expect_read(uint64_t ino, uint64_t offset, uint64_t size, - const void *contents) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READ && - in->header.nodeid == ino && - in->body.read.fh == Write::FH && - in->body.read.offset == offset && - in->body.read.size == size); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(struct fuse_out_header) + size; - memmove(out->body.bytes, contents, size); - })).RetiresOnSaturation(); -} - void expect_release(uint64_t ino, ProcessMockerT r) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RELEASE && in->header.nodeid == ino); }, Eq(true)), _) ).WillRepeatedly(Invoke(r)); } -void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, - uint32_t flags, const void *contents) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - const char *buf = (const char*)in->body.bytes + - sizeof(struct fuse_write_in); - bool pid_ok; - - if (in->body.write.write_flags & FUSE_WRITE_CACHE) - pid_ok = true; - else - pid_ok = (pid_t)in->header.pid == getpid(); - - return (in->header.opcode == FUSE_WRITE && - in->header.nodeid == ino && - in->body.write.fh == Write::FH && - in->body.write.offset == offset && - in->body.write.size == isize && - pid_ok && - in->body.write.write_flags == flags && - 0 == bcmp(buf, contents, isize)); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, write); - out->body.write.size = osize; - })); -} - }; class AioWrite: public Write { virtual void SetUp() { const char *node = "vfs.aio.enable_unsafe"; int val = 0; size_t size = sizeof(val); ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) << strerror(errno); // TODO: With GoogleTest 1.8.2, use SKIP instead if (!val) FAIL() << "vfs.aio.enable_unsafe must be set for this test"; FuseTest::SetUp(); } }; /* Tests for the write-through cache mode */ class WriteThrough: public Write { virtual void SetUp() { const char *cache_mode_node = "vfs.fuse.data_cache_mode"; const char *sync_resize_node = "vfs.fuse.sync_resize"; int val = 0; size_t size = sizeof(val); ASSERT_EQ(0, sysctlbyname(cache_mode_node, &val, &size, NULL, 0)) << strerror(errno); // TODO: With GoogleTest 1.8.2, use SKIP instead if (val != 1) FAIL() << "vfs.fuse.data_cache_mode must be set to 1 " "(writethrough) for this test"; ASSERT_EQ(0, sysctlbyname(sync_resize_node, &val, &size, NULL, 0)) << strerror(errno); if (val != 0) FAIL() << "vfs.fuse.sync_resize must be set to 0 for this test." " That sysctl will probably be removed soon."; FuseTest::SetUp(); } }; /* Tests for the writeback cache mode */ class WriteBack: public Write { virtual void SetUp() { const char *node = "vfs.fuse.data_cache_mode"; int val = 0; size_t size = sizeof(val); ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) << strerror(errno); // TODO: With GoogleTest 1.8.2, use SKIP instead if (val != 2) FAIL() << "vfs.fuse.data_cache_mode must be set to 2 " "(writeback) for this test"; FuseTest::SetUp(); } }; /* AIO writes need to set the header's pid field correctly */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ TEST_F(AioWrite, DISABLED_aio_write) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; uint64_t offset = 4096; int fd; ssize_t bufsize = strlen(CONTENTS); struct aiocb iocb, *piocb; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); iocb.aio_nbytes = bufsize; iocb.aio_fildes = fd; iocb.aio_buf = (void *)CONTENTS; iocb.aio_offset = offset; iocb.aio_sigevent.sigev_notify = SIGEV_NONE; ASSERT_EQ(0, aio_write(&iocb)) << strerror(errno); ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * When a file is opened with O_APPEND, we should forward that flag to * FUSE_OPEN (tested by Open.o_append) but still attempt to calculate the * offset internally. That way we'll work both with filesystems that * understand O_APPEND (and ignore the offset) and filesystems that don't (and * simply use the offset). * * Note that verifying the O_APPEND flag in FUSE_OPEN is done in the * Open.o_append test. */ TEST_F(Write, append) { const ssize_t BUFSIZE = 9; const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char CONTENTS[BUFSIZE] = "abcdefgh"; uint64_t ino = 42; /* * Set offset to a maxbcachebuf boundary so we don't need to RMW when * using writeback caching */ uint64_t initial_offset = m_maxbcachebuf; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, initial_offset); expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS); /* Must open O_RDWR or fuse(4) implicitly sets direct_io */ fd = open(FULLPATH, O_RDWR | O_APPEND); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } TEST_F(Write, append_direct_io) { const ssize_t BUFSIZE = 9; const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char CONTENTS[BUFSIZE] = "abcdefgh"; uint64_t ino = 42; uint64_t initial_offset = 4096; int fd; expect_lookup(RELPATH, ino); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, initial_offset); expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS); fd = open(FULLPATH, O_WRONLY | O_APPEND); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* A direct write should evict any overlapping cached data */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235774 */ TEST_F(Write, DISABLED_direct_io_evicts_cache) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char CONTENTS0[] = "abcdefgh"; const char CONTENTS1[] = "ijklmnop"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS0) + 1; char readbuf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); - expect_read(ino, 0, bufsize, CONTENTS0); + expect_read(ino, 0, bufsize, bufsize, CONTENTS0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS1); fd = open(FULLPATH, O_RDWR); EXPECT_LE(0, fd) << strerror(errno); // Prime cache ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); // Write directly, evicting cache ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno); ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno); // Read again. Cache should be bypassed - expect_read(ino, 0, bufsize, CONTENTS1); + expect_read(ino, 0, bufsize, bufsize, CONTENTS1); ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno); ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); ASSERT_STREQ(readbuf, CONTENTS1); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * When the direct_io option is used, filesystems are allowed to write less * data than requested */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236381 */ TEST_F(Write, DISABLED_direct_io_short_write) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefghijklmnop"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); ssize_t halfbufsize = bufsize / 2; const char *halfcontents = CONTENTS + halfbufsize; expect_lookup(RELPATH, ino); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, halfbufsize, 0, CONTENTS); expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0, halfcontents); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * An insidious edge case: the filesystem returns a short write, and the * difference between what we requested and what it actually wrote crosses an * iov element boundary */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236381 */ TEST_F(Write, DISABLED_direct_io_short_write_iov) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS0 = "abcdefgh"; const char *CONTENTS1 = "ijklmnop"; const char *EXPECTED0 = "abcdefghijklmnop"; const char *EXPECTED1 = "hijklmnop"; uint64_t ino = 42; int fd; ssize_t size0 = strlen(CONTENTS0) - 1; ssize_t size1 = strlen(CONTENTS1) + 1; ssize_t totalsize = size0 + size1; struct iovec iov[2]; expect_lookup(RELPATH, ino); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, 0); expect_write(ino, 0, totalsize, size0, 0, EXPECTED0); expect_write(ino, size0, size1, size1, 0, EXPECTED1); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); iov[0].iov_base = (void*)CONTENTS0; iov[0].iov_len = strlen(CONTENTS0); iov[1].iov_base = (void*)CONTENTS1; iov[1].iov_len = strlen(CONTENTS1); ASSERT_EQ(totalsize, writev(fd, iov, 2)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the kernel cannot be sure which uid, gid, or pid was responsible for a * write, then it must set the FUSE_WRITE_CACHE bit */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236378 */ // TODO: check vfs.fuse.mmap_enable TEST_F(Write, DISABLED_mmap) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); void *p; uint64_t offset = 10; size_t len; void *zeros, *expected; len = getpagesize(); zeros = calloc(1, len); ASSERT_NE(NULL, zeros); expected = calloc(1, len); ASSERT_NE(NULL, expected); memmove((uint8_t*)expected + offset, CONTENTS, bufsize); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, len); - expect_read(ino, 0, len, zeros); + expect_read(ino, 0, len, len, zeros); /* * Writes from the pager may or may not be associated with the correct * pid, so they must set FUSE_WRITE_CACHE */ expect_write(ino, 0, len, len, FUSE_WRITE_CACHE, expected); expect_release(ino, ReturnErrno(0)); fd = open(FULLPATH, O_RDWR); EXPECT_LE(0, fd) << strerror(errno); p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); ASSERT_NE(MAP_FAILED, p) << strerror(errno); memmove((uint8_t*)p + offset, CONTENTS, bufsize); ASSERT_EQ(0, munmap(p, len)) << strerror(errno); close(fd); // Write mmap'd data on close free(expected); free(zeros); } TEST_F(Write, pwrite) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; uint64_t offset = 4096; int fd; ssize_t bufsize = strlen(CONTENTS); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } TEST_F(Write, write) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* fuse(4) should not issue writes of greater size than the daemon requests */ TEST_F(Write, write_large) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; int *contents; uint64_t ino = 42; int fd; ssize_t halfbufsize, bufsize; halfbufsize = m_mock->m_max_write; bufsize = halfbufsize * 2; contents = (int*)malloc(bufsize); ASSERT_NE(NULL, contents); for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) { contents[i] = i; } expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, halfbufsize, halfbufsize, 0, contents); expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0, &contents[halfbufsize / sizeof(int)]); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ free(contents); } TEST_F(Write, write_nothing) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = ""; uint64_t ino = 42; int fd; ssize_t bufsize = 0; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* In writeback mode, dirty data should be written on close */ TEST_F(WriteBack, close) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETATTR); }, 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 })); expect_release(ino, ReturnErrno(0)); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); close(fd); } /* * Without direct_io, writes should be committed to cache */ TEST_F(WriteBack, writeback) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char readbuf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_RDWR); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* * A subsequent read should be serviced by cache, without querying the * filesystem daemon */ ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * With O_DIRECT, writes should be not committed to cache. Admittedly this is * an odd test, because it would be unusual to use O_DIRECT for writes but not * reads. */ TEST_F(WriteBack, o_direct) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char readbuf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); - expect_read(ino, 0, bufsize, CONTENTS); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDWR | O_DIRECT); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* A subsequent read must query the daemon because cache is empty */ ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno); ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Without direct_io, writes should be committed to cache */ /* * Disabled because we don't yet implement write-through caching. No bugzilla * entry, because that's a feature request, not a bug. */ TEST_F(WriteThrough, DISABLED_writethrough) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); char readbuf[bufsize]; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_RDWR); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* * A subsequent read should be serviced by cache, without querying the * filesystem daemon */ ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* With writethrough caching, writes update the cached file size */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ TEST_F(WriteThrough, DISABLED_update_file_size) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; struct stat sb; uint64_t ino = 42; int fd; ssize_t bufsize = strlen(CONTENTS); expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); 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.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.size = 0; out->body.attr.attr_valid = UINT64_MAX; })); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_RDWR); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); /* Get cached attributes */ ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); ASSERT_EQ(bufsize, sb.st_size); /* Deliberately leak fd. close(2) will be tested in release.cc */ }