Index: projects/fuse2/tests/sys/fs/fuse/Makefile =================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344824) @@ -1,95 +1,107 @@ # $FreeBSD$ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr +ATF_TESTS_CXX+= link ATF_TESTS_CXX+= lookup ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink +ATF_TESTS_CXX+= rename ATF_TESTS_CXX+= setattr ATF_TESTS_CXX+= symlink SRCS.access+= access.cc SRCS.access+= getmntopts.c SRCS.access+= mockfs.cc SRCS.access+= utils.cc SRCS.create+= create.cc SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc +SRCS.link+= getmntopts.c +SRCS.link+= link.cc +SRCS.link+= mockfs.cc +SRCS.link+= utils.cc + SRCS.lookup+= getmntopts.c SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc SRCS.mkdir+= getmntopts.c SRCS.mkdir+= mockfs.cc SRCS.mkdir+= mkdir.cc SRCS.mkdir+= utils.cc SRCS.mknod+= getmntopts.c SRCS.mknod+= mockfs.cc SRCS.mknod+= mknod.cc SRCS.mknod+= utils.cc SRCS.open+= getmntopts.c SRCS.open+= mockfs.cc SRCS.open+= open.cc SRCS.open+= utils.cc SRCS.readlink+= getmntopts.c SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc SRCS.readlink+= utils.cc + +SRCS.rename+= getmntopts.c +SRCS.rename+= mockfs.cc +SRCS.rename+= rename.cc +SRCS.rename+= utils.cc SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc SRCS.setattr+= setattr.cc SRCS.setattr+= utils.cc SRCS.symlink+= getmntopts.c SRCS.symlink+= mockfs.cc SRCS.symlink+= symlink.cc SRCS.symlink+= utils.cc # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 FUSEFS= ${.CURDIR:H:H:H:H}/sys/fs/fuse MOUNT= ${.CURDIR:H:H:H:H}/sbin/mount CFLAGS+= -I${.CURDIR:H:H:H} CFLAGS+= -I${FUSEFS} CFLAGS+= -I${MOUNT} .PATH: ${MOUNT} LIBADD+= pthread WARNS?= 6 NO_WTHREAD_SAFETY= # GoogleTest fails Clang's thread safety check # Use googlemock from ports until after the import-googletest-1.8.1 branch # merges to head. CXXFLAGS+= -I/usr/local/include CXXFLAGS+= -DGTEST_HAS_POSIX_RE=1 CXXFLAGS+= -DGTEST_HAS_PTHREAD=1 CXXFLAGS+= -DGTEST_HAS_STREAM_REDIRECTION=1 CXXFLAGS+= -frtti CXXFLAGS+= -std=c++14 LDADD+= ${LOCALBASE}/lib/libgmock.a LDADD+= ${LOCALBASE}/lib/libgtest.a # Without -lpthread, gtest fails at _runtime_ with the error pthread_key_create(&key, &DeleteThreadLocalValue)failed with error 78 LIBADD+= pthread .include Index: projects/fuse2/tests/sys/fs/fuse/access.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/access.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/access.cc (revision 344824) @@ -1,99 +1,99 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { //#include #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Access: public FuseTest {}; /* 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(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_IFREG | 0644; out->body.entry.nodeid = 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(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_IFREG | 0644; out->body.entry.nodeid = 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 344823) +++ projects/fuse2/tests/sys/fs/fuse/create.cc (revision 344824) @@ -1,390 +1,390 @@ /*- * 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 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; out->header.error = 0; /* * 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. */ out->body.entry.entry_valid = 0; SET_OUT_HEADER_LEN(out, 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.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; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).Times(1) + EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.entry_valid = UINT64_MAX; SET_OUT_HEADER_LEN(out, entry); })).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(RELPATH).Times(1) + 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); })); 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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/getattr.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/getattr.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/getattr.cc (revision 344824) @@ -1,213 +1,213 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "mockfs.hh" #include "utils.hh" using namespace testing; class Getattr : public FuseTest {}; /* * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs * should use the cached attributes, rather than query the daemon */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ TEST_F(Getattr, DISABLED_attr_cache) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const uint64_t generation = 13; struct stat sb; - EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + 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(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + 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_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(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 = 0100644; out->body.entry.nodeid = ino; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(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(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_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.generation = generation; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.size = 1; out->body.attr.attr.blocks = 2; out->body.attr.attr.atime = 3; out->body.attr.attr.mtime = 4; out->body.attr.attr.ctime = 5; out->body.attr.attr.atimensec = 6; out->body.attr.attr.mtimensec = 7; out->body.attr.attr.ctimensec = 8; out->body.attr.attr.nlink = 9; out->body.attr.attr.uid = 10; out->body.attr.attr.gid = 11; out->body.attr.attr.rdev = 12; })); ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(1, sb.st_size); EXPECT_EQ(2, sb.st_blocks); EXPECT_EQ(3, sb.st_atim.tv_sec); EXPECT_EQ(6, sb.st_atim.tv_nsec); EXPECT_EQ(4, sb.st_mtim.tv_sec); EXPECT_EQ(7, sb.st_mtim.tv_nsec); EXPECT_EQ(5, sb.st_ctim.tv_sec); EXPECT_EQ(8, sb.st_ctim.tv_nsec); EXPECT_EQ(9ull, sb.st_nlink); EXPECT_EQ(10ul, sb.st_uid); EXPECT_EQ(11ul, sb.st_gid); EXPECT_EQ(12ul, sb.st_rdev); EXPECT_EQ(ino, sb.st_ino); EXPECT_EQ(S_IFREG | 0644, sb.st_mode); // fuse(4) does not _yet_ support inode generations //EXPECT_EQ(generation, sb.st_gen); //st_birthtim and st_flags are not supported by protocol 7.8. They're //only supported as OS-specific extensions to OSX. //EXPECT_EQ(, sb.st_birthtim); //EXPECT_EQ(, sb.st_flags); //FUSE can't set st_blksize until protocol 7.9 } Index: projects/fuse2/tests/sys/fs/fuse/link.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/link.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/link.cc (revision 344824) @@ -0,0 +1,106 @@ +/*- + * 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 Link: public FuseTest {}; + +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_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_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); +} Property changes on: projects/fuse2/tests/sys/fs/fuse/link.cc ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: projects/fuse2/tests/sys/fs/fuse/lookup.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/lookup.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/lookup.cc (revision 344824) @@ -1,272 +1,302 @@ /*- * 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 Lookup: public FuseTest {}; /* * If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs * should use the cached attributes, rather than query the daemon */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ TEST_F(Lookup, DISABLED_attr_cache) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; struct stat sb; - EXPECT_LOOKUP(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.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.ino = ino; // Must match nodeid out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.attr.size = 1; out->body.entry.attr.blocks = 2; out->body.entry.attr.atime = 3; out->body.entry.attr.mtime = 4; out->body.entry.attr.ctime = 5; out->body.entry.attr.atimensec = 6; out->body.entry.attr.mtimensec = 7; out->body.entry.attr.ctimensec = 8; out->body.entry.attr.nlink = 9; out->body.entry.attr.uid = 10; out->body.entry.attr.gid = 11; out->body.entry.attr.rdev = 12; })); /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */ ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(1, sb.st_size); EXPECT_EQ(2, sb.st_blocks); EXPECT_EQ(3, sb.st_atim.tv_sec); EXPECT_EQ(6, sb.st_atim.tv_nsec); EXPECT_EQ(4, sb.st_mtim.tv_sec); EXPECT_EQ(7, sb.st_mtim.tv_nsec); EXPECT_EQ(5, sb.st_ctim.tv_sec); EXPECT_EQ(8, sb.st_ctim.tv_nsec); EXPECT_EQ(9ull, sb.st_nlink); EXPECT_EQ(10ul, sb.st_uid); EXPECT_EQ(11ul, sb.st_gid); EXPECT_EQ(12ul, sb.st_rdev); EXPECT_EQ(ino, sb.st_ino); EXPECT_EQ(S_IFREG | 0644, sb.st_mode); // fuse(4) does not _yet_ support inode generations //EXPECT_EQ(generation, sb.st_gen); //st_birthtim and st_flags are not supported by protocol 7.8. They're //only supported as OS-specific extensions to OSX. //EXPECT_EQ(, sb.st_birthtim); //EXPECT_EQ(, sb.st_flags); //FUSE can't set st_blksize until protocol 7.9 } /* * If lookup returns a finite but non-zero cache timeout, then we should discard * the cached attributes and requery the daemon. */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */ TEST_F(Lookup, attr_cache_timeout) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; struct stat sb; /* * The timeout should be longer than the longest plausible time the * daemon would take to complete a write(2) to /dev/fuse, but no longer. */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + 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; })); /* 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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(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.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) { - EXPECT_LOOKUP("does_not_exist").Times(1) + EXPECT_LOOKUP(1, "does_not_exist").Times(1) .WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.nodeid = 0; out->body.entry.entry_valid = UINT64_MAX; SET_OUT_HEADER_LEN(out, entry); })); 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. */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(RELPATH).Times(2) + EXPECT_LOOKUP(1, RELPATH).Times(2) .WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.nodeid = 0; out->body.entry.entry_valid_nsec = timeout_ns; SET_OUT_HEADER_LEN(out, entry); })); EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); usleep(2 * timeout_ns / 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(RELPATH).Times(2) + 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); } TEST_F(Lookup, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(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_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 344823) +++ projects/fuse2/tests/sys/fs/fuse/mkdir.cc (revision 344824) @@ -1,198 +1,198 @@ /*- * 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 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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; /* mkdir will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; out->header.error = 0; /* * 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. */ out->body.entry.entry_valid = 0; SET_OUT_HEADER_LEN(out, 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.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; /* mkdir will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).Times(1) + EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.entry_valid = UINT64_MAX; SET_OUT_HEADER_LEN(out, entry); })).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(RELPATH).Times(1) + 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); })); 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(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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/mknod.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mknod.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/mknod.cc (revision 344824) @@ -1,138 +1,138 @@ /*- * 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 Mknod: public FuseTest { public: virtual void SetUp() { if (geteuid() != 0) { // TODO: With GoogleTest 1.8.2, use SKIP instead FAIL() << "Only root may use most mknod(2) variations"; } FuseTest::SetUp(); } /* Test an OK creation of a file with the given mode and device number */ void test_ok(mode_t mode, dev_t dev) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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_mknod_in); return (in->header.opcode == FUSE_MKNOD && in->body.mknod.mode == mode && in->body.mknod.rdev == dev && (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 = mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; out->body.create.entry.attr.rdev = dev; })); EXPECT_EQ(0, mknod(FULLPATH, mode, dev)) << strerror(errno); } }; /* * mknod(2) should be able to create block devices on a FUSE filesystem. Even * though FreeBSD doesn't use block devices, this is useful when copying media * from or preparing media for other operating systems. */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ TEST_F(Mknod, DISABLED_blk) { test_ok(S_IFBLK | 0755, 0xfe00); /* /dev/vda's device number on Linux */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ TEST_F(Mknod, DISABLED_chr) { test_ok(S_IFCHR | 0755, 0x64); /* /dev/fuse's device number */ } /* * The daemon is responsible for checking file permissions (unless the * default_permissions mount option was used) */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ TEST_F(Mknod, DISABLED_eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = S_IFIFO | 0755; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + 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_mknod_in); return (in->header.opcode == FUSE_MKNOD && in->body.mknod.mode == mode && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, mknod(FULLPATH, mode, 0)); EXPECT_EQ(EPERM, errno); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ TEST_F(Mknod, DISABLED_fifo) { test_ok(S_IFIFO | 0755, 0); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ TEST_F(Mknod, DISABLED_whiteout) { test_ok(S_IFWHT | 0755, 0); } Index: projects/fuse2/tests/sys/fs/fuse/mockfs.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc (revision 344824) @@ -1,261 +1,273 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include #include #include #include #include #include #include #include "mntopts.h" // for build_iovec } #include #include "mockfs.hh" using namespace testing; int verbosity = 0; static sig_atomic_t quit = 0; const char* opcode2opname(uint32_t opcode) { const int NUM_OPS = 39; const char* table[NUM_OPS] = { "Unknown (opcode 0)", "FUSE_LOOKUP", "FUSE_FORGET", "FUSE_GETATTR", "FUSE_SETATTR", "FUSE_READLINK", "FUSE_SYMLINK", "Unknown (opcode 7)", "FUSE_MKNOD", "FUSE_MKDIR", "FUSE_UNLINK", "FUSE_RMDIR", "FUSE_RENAME", "FUSE_LINK", "FUSE_OPEN", "FUSE_READ", "FUSE_WRITE", "FUSE_STATFS", "FUSE_RELEASE", "Unknown (opcode 19)", "FUSE_FSYNC", "FUSE_SETXATTR", "FUSE_GETXATTR", "FUSE_LISTXATTR", "FUSE_REMOVEXATTR", "FUSE_FLUSH", "FUSE_INIT", "FUSE_OPENDIR", "FUSE_READDIR", "FUSE_RELEASEDIR", "FUSE_FSYNCDIR", "FUSE_GETLK", "FUSE_SETLK", "FUSE_SETLKW", "FUSE_ACCESS", "FUSE_CREATE", "FUSE_INTERRUPT", "FUSE_BMAP", "FUSE_DESTROY" }; if (opcode >= NUM_OPS) return ("Unknown (opcode > max)"); else return (table[opcode]); } std::function ReturnErrno(int error) { return([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -error; out->header.len = sizeof(out->header); }); } void sigint_handler(int __unused sig) { quit = 1; } +void debug_fuseop(const mockfs_buf_in *in) +{ + printf("%s ino=%lu", opcode2opname(in->header.opcode), + in->header.nodeid); + switch (in->header.opcode) { + case FUSE_LOOKUP: + printf(" %s", in->body.lookup); + break; + default: + break; + } + printf("\n"); +} + MockFS::MockFS() { struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; /* * Kyua sets pwd to a testcase-unique tempdir; no need to use * mkdtemp */ /* * googletest doesn't allow ASSERT_ in constructors, so we must throw * instead. */ if (mkdir("mountpoint" , 0644) && errno != EEXIST) throw(std::system_error(errno, std::system_category(), "Couldn't make mountpoint directory")); m_fuse_fd = open("/dev/fuse", O_RDWR); if (m_fuse_fd < 0) throw(std::system_error(errno, std::system_category(), "Couldn't open /dev/fuse")); sprintf(fdstr, "%d", m_fuse_fd); m_pid = getpid(); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, "mountpoint"), -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); build_iovec(&iov, &iovlen, "fd", fdstr, -1); if (nmount(iov, iovlen, 0)) throw(std::system_error(errno, std::system_category(), "Couldn't mount filesystem")); // Setup default handler ON_CALL(*this, process(_, _)) .WillByDefault(Invoke(this, &MockFS::process_default)); init(); if (pthread_create(&m_thr, NULL, service, (void*)this)) throw(std::system_error(errno, std::system_category(), "Couldn't Couldn't start fuse thread")); } MockFS::~MockFS() { pthread_kill(m_daemon_id, SIGUSR1); // Closing the /dev/fuse file descriptor first allows unmount to // succeed even if the daemon doesn't correctly respond to commands // during the unmount sequence. close(m_fuse_fd); pthread_join(m_daemon_id, NULL); ::unmount("mountpoint", MNT_FORCE); rmdir("mountpoint"); } void MockFS::init() { mockfs_buf_in *in; mockfs_buf_out out; in = (mockfs_buf_in*) malloc(sizeof(*in)); ASSERT_TRUE(in != NULL); read_request(in); ASSERT_EQ(FUSE_INIT, in->header.opcode); memset(&out, 0, sizeof(out)); out.header.unique = in->header.unique; out.header.error = 0; out.body.init.major = FUSE_KERNEL_VERSION; out.body.init.minor = FUSE_KERNEL_MINOR_VERSION; SET_OUT_HEADER_LEN(&out, init); write(m_fuse_fd, &out, out.header.len); free(in); } void MockFS::loop() { mockfs_buf_in *in; mockfs_buf_out out; in = (mockfs_buf_in*) malloc(sizeof(*in)); ASSERT_TRUE(in != NULL); while (!quit) { bzero(in, sizeof(*in)); bzero(&out, sizeof(out)); read_request(in); if (quit) break; - if (verbosity > 0) { - printf("Got request %s\n", - opcode2opname(in->header.opcode)); - } + if (verbosity > 0) + debug_fuseop(in); if ((pid_t)in->header.pid != m_pid) { /* * Reject any requests from unknown processes. Because * we actually do mount a filesystem, plenty of * unrelated system daemons may try to access it. */ process_default(in, &out); } else { process(in, &out); } if (in->header.opcode == FUSE_FORGET) { /*Alone among the opcodes, FORGET expects no response*/ continue; } ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 || errno == EAGAIN) << strerror(errno); } free(in); } void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) { out->header.unique = in->header.unique; out->header.error = -EOPNOTSUPP; out->header.len = sizeof(out->header); } void MockFS::read_request(mockfs_buf_in *in) { ssize_t res; res = read(m_fuse_fd, in, sizeof(*in)); if (res < 0 && !quit) perror("read"); ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit); } void* MockFS::service(void *pthr_data) { MockFS *mock_fs = (MockFS*)pthr_data; mock_fs->m_daemon_id = pthread_self(); quit = 0; signal(SIGUSR1, sigint_handler); mock_fs->loop(); return (NULL); } void MockFS::unmount() { ::unmount("mountpoint", 0); } Index: projects/fuse2/tests/sys/fs/fuse/mockfs.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344824) @@ -1,172 +1,175 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include "fuse_kernel.h" } #include #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ } /* * Create an expectation on FUSE_LOOKUP and return it so the caller can set * actions. * * This must be a macro instead of a method because EXPECT_CALL returns a type * with a deleted constructor. */ -#define EXPECT_LOOKUP(path) \ +#define EXPECT_LOOKUP(parent, path) \ EXPECT_CALL(*m_mock, process( \ ResultOf([=](auto in) { \ return (in->header.opcode == FUSE_LOOKUP && \ + in->header.nodeid == (parent) && \ strcmp(in->body.lookup, (path)) == 0); \ }, Eq(true)), \ _) \ ) extern int verbosity; /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ struct fuse_create_out { struct fuse_entry_out entry; struct fuse_open_out open; }; union fuse_payloads_in { fuse_access_in access; /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_forget_in forget; fuse_init_in init; + fuse_link_in link; char lookup[0]; fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; + fuse_rename_in rename; fuse_setattr_in setattr; }; struct mockfs_buf_in { fuse_in_header header; union fuse_payloads_in body; }; union fuse_payloads_out { fuse_attr_out attr; fuse_create_out create; fuse_entry_out entry; fuse_init_out init; fuse_open_out open; /* * The protocol places no limits on the length of the string. This is * merely convenient for testing. */ char str[80]; }; struct mockfs_buf_out { fuse_out_header header; union fuse_payloads_out body; }; /* * Helper function used for setting an error expectation for any fuse operation. * The operation will return the supplied error */ std::function ReturnErrno(int error); /* * Fake FUSE filesystem * * "Mounts" a filesystem to a temporary directory and services requests * according to the programmed expectations. * * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api. */ class MockFS { public: /* thread id of the fuse daemon thread */ pthread_t m_daemon_id; private: /* file descriptor of /dev/fuse control device */ int m_fuse_fd; /* pid of the test process */ pid_t m_pid; /* * Thread that's running the mockfs daemon. * * It must run in a separate thread so it doesn't deadlock with the * client test code. */ pthread_t m_thr; /* Initialize a session after mounting */ void init(); /* Default request handler */ void process_default(const mockfs_buf_in*, mockfs_buf_out*); /* Entry point for the daemon thread */ static void* service(void*); /* Read, but do not process, a single request from the kernel */ void read_request(mockfs_buf_in*); public: /* Create a new mockfs and mount it to a tempdir */ MockFS(); virtual ~MockFS(); /* Process FUSE requests endlessly */ void loop(); /* * Request handler * * This method is expected to provide the response to each FUSE * operation. Responses must be immediate (so this method can't be used * for testing a daemon with queue depth > 1). Test cases must define * each response using Googlemock expectations */ MOCK_METHOD2(process, void(const mockfs_buf_in*, mockfs_buf_out*)); /* Gracefully unmount */ void unmount(); }; Index: projects/fuse2/tests/sys/fs/fuse/open.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/open.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/open.cc (revision 344824) @@ -1,142 +1,142 @@ /*- * 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 Open: public FuseTest {}; /* * 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(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_IFREG | 0644; out->body.entry.nodeid = ino; })); 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(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_IFREG | 0644; out->body.entry.nodeid = ino; })); 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); } TEST_F(Open, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; - EXPECT_LOOKUP(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_IFREG | 0644; out->body.entry.nodeid = ino; out->body.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_RDONLY); EXPECT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } Index: projects/fuse2/tests/sys/fs/fuse/readlink.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/readlink.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/readlink.cc (revision 344824) @@ -1,98 +1,98 @@ /*- * 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 Readlink: public FuseTest {}; TEST_F(Readlink, eloop) { const char FULLPATH[] = "mountpoint/src"; const char RELPATH[] = "src"; const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(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_IFLNK | 0777; out->body.entry.nodeid = 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(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_IFLNK | 0777; out->body.entry.nodeid = 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/rename.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/rename.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/rename.cc (revision 344824) @@ -0,0 +1,188 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class 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(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 + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke(ReturnErrno(ENOENT))); + + ASSERT_NE(0, rename(FULLSRC, FULLDST)); + ASSERT_EQ(ENOENT, errno); +} + +TEST_F(Rename, exdev) +{ + const char FULLB[] = "mountpoint/src"; + const char RELB[] = "src"; + // FUSE hardcodes the mountpoint to inocde 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); + })); + + 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 + 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(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 + 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_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); +} Property changes on: projects/fuse2/tests/sys/fs/fuse/rename.cc ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: projects/fuse2/tests/sys/fs/fuse/setattr.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/setattr.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/setattr.cc (revision 344824) @@ -1,483 +1,483 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Setattr : public FuseTest {}; /* Change the mode of a file */ TEST_F(Setattr, chmod) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_LOOKUP(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_IFREG | oldmode; out->body.entry.nodeid = ino; out->body.entry.attr.mode = S_IFREG | oldmode; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_MODE; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.mode == newmode); }, 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 | newmode; })); EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); } /* Change the owner and group of a file */ TEST_F(Setattr, chown) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const gid_t oldgroup = 66; const gid_t newgroup = 99; const uid_t olduser = 33; const uid_t newuser = 44; - EXPECT_LOOKUP(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_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr.gid = oldgroup; out->body.entry.attr.uid = olduser; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_GID | FATTR_UID; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.uid == newuser && in->body.setattr.gid == newgroup); }, 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.uid = newuser; out->body.attr.attr.gid = newgroup; })); EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno); } /* * FUSE daemons are allowed to check permissions however they like. If the * daemon returns EPERM, even if the file permissions "should" grant access, * then fuse(4) should return EPERM too. */ TEST_F(Setattr, eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - EXPECT_LOOKUP(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_IFREG | 0777; out->body.entry.nodeid = ino; out->body.entry.attr.uid = in->header.uid; out->body.entry.attr.gid = in->header.gid; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, truncate(FULLPATH, 10)); EXPECT_EQ(EPERM, errno); } /* Change the mode of an open file, by its file descriptor */ TEST_F(Setattr, fchmod) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_LOOKUP(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_IFREG | oldmode; out->body.entry.nodeid = ino; out->body.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 | oldmode; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_MODE; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.mode == newmode); }, 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 | newmode; })); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Change the size of an open file, by its file descriptor */ TEST_F(Setattr, ftruncate) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; uint64_t fh = 0xdeadbeef1a7ebabe; const off_t oldsize = 99; const off_t newsize = 12345; - EXPECT_LOOKUP(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_IFREG | 0755; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.size = oldsize; })); 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; })); /* 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 | 0755; out->body.attr.attr.size = oldsize; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_SIZE | FATTR_FH; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.fh == fh); }, 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 | 0755; out->body.attr.attr.size = newsize; })); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Change the size of the file */ TEST_F(Setattr, truncate) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const uint64_t oldsize = 100'000'000; const uint64_t newsize = 20'000'000; - EXPECT_LOOKUP(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_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr.size = oldsize; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_SIZE; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.size == newsize); }, 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 = newsize; })); EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno); } /* Change a file's timestamps */ TEST_F(Setattr, utimensat) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const timespec oldtimes[2] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, }; const timespec newtimes[2] = { {.tv_sec = 5, .tv_nsec = 6}, {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(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_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.atime = oldtimes[0].tv_sec; out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; out->body.entry.attr.mtime = oldtimes[1].tv_sec; out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; })); /* * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR * call */ 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.atime = oldtimes[0].tv_sec; out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = oldtimes[1].tv_sec; out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_ATIME | FATTR_MTIME; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.atime == newtimes[0].tv_sec && in->body.setattr.atimensec == newtimes[0].tv_nsec && in->body.setattr.mtime == newtimes[1].tv_sec && in->body.setattr.mtimensec == newtimes[1].tv_nsec); }, 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.atime = newtimes[0].tv_sec; out->body.attr.attr.atimensec = newtimes[0].tv_nsec; out->body.attr.attr.mtime = newtimes[1].tv_sec; out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; })); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } /* Change a file mtime but not its atime */ TEST_F(Setattr, utimensat_mtime_only) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const timespec oldtimes[2] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, }; const timespec newtimes[2] = { {.tv_sec = 5, .tv_nsec = UTIME_OMIT}, {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(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_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.atime = oldtimes[0].tv_sec; out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; out->body.entry.attr.mtime = oldtimes[1].tv_sec; out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; })); /* * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR * call */ 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.atime = oldtimes[0].tv_sec; out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = oldtimes[1].tv_sec; out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_MTIME; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.mtime == newtimes[1].tv_sec && in->body.setattr.mtimensec == newtimes[1].tv_nsec); }, 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.atime = oldtimes[0].tv_sec; out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = newtimes[1].tv_sec; out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; })); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } /* * Writethrough cache: newly changed attributes should be automatically cached, * if the filesystem allows it */ //TODO TEST_F(Setattr, writethrough_cache){} Index: projects/fuse2/tests/sys/fs/fuse/symlink.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/symlink.cc (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/symlink.cc (revision 344824) @@ -1,95 +1,90 @@ /*- * 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 Symlink: public FuseTest {}; TEST_F(Symlink, enospc) { const char FULLPATH[] = "mountpoint/lnk"; const char RELPATH[] = "lnk"; const char dst[] = "dst"; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes; const char *linkname = name + strlen(name) + 1; return (in->header.opcode == FUSE_SYMLINK && (0 == strcmp(linkname, dst)) && (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOSPC; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(ENOSPC))); - EXPECT_EQ(-1, symlink(dst, FULLPATH)); EXPECT_EQ(ENOSPC, errno); } TEST_F(Symlink, ok) { const char FULLPATH[] = "mountpoint/src"; const char RELPATH[] = "src"; const char dst[] = "dst"; const uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in->body.bytes; const char *linkname = name + strlen(name) + 1; return (in->header.opcode == FUSE_SYMLINK && (0 == strcmp(linkname, dst)) && (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_IFLNK | 0777; out->body.entry.nodeid = ino; })); EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/utils.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 344823) +++ projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 344824) @@ -1,47 +1,47 @@ /*- * 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. */ class FuseTest : public ::testing::Test { protected: MockFS *m_mock = NULL; public: virtual void SetUp() { try { m_mock = new MockFS{}; } catch (std::system_error err) { FAIL() << err.what(); } } - void TearDown() { + virtual void TearDown() { if (m_mock) delete m_mock; } };