Index: projects/fuse2/tests/sys/fs/fuse/flush.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/flush.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/flush.cc (revision 345188) @@ -1,150 +1,203 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Flush: public FuseTest { public: -void expect_flush(uint64_t ino, int times, ProcessMockerT r) +void expect_flush(uint64_t ino, int times, pid_t lo, ProcessMockerT r) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FLUSH && in->header.nodeid == ino && + in->body.flush.lock_owner == (uint64_t)lo && in->body.flush.fh == FH); }, Eq(true)), _) ).Times(times) .WillRepeatedly(Invoke(r)); } void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } /* * When testing FUSE_FLUSH, the FUSE_RELEASE calls are uninteresting. This * expectation will silence googlemock warnings */ void expect_release() { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RELEASE); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnErrno(0))); } }; -// TODO: lock_owner stuff +class FlushWithLocks: public Flush { + virtual void SetUp() { + m_init_flags = FUSE_POSIX_LOCKS; + Flush::SetUp(); + } +}; /* If a file descriptor is duplicated, every close causes FLUSH */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ TEST_F(Flush, DISABLED_dup) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd, fd2; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_flush(ino, 2, ReturnErrno(0)); + expect_flush(ino, 2, 0, ReturnErrno(0)); expect_release(); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); fd2 = dup(fd); ASSERT_EQ(0, close(fd2)) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } /* * Some FUSE filesystem cache data internally and flush it on release. Such * filesystems may generate errors during release. On Linux, these get * returned by close(2). However, POSIX does not require close(2) to return * this error. FreeBSD's fuse(4) should return EIO if it returns an error at * all. */ /* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ TEST_F(Flush, DISABLED_eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_flush(ino, 1, ReturnErrno(EIO)); + expect_flush(ino, 1, 0, ReturnErrno(EIO)); expect_release(); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); } /* A FUSE_FLUSH should be sent on close(2) */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ TEST_F(Flush, DISABLED_flush) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_flush(ino, 1, ReturnErrno(0)); + expect_flush(ino, 1, 0, ReturnErrno(0)); expect_release(); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_TRUE(0 == close(fd)) << strerror(errno); +} + +/* + * When closing a file with a POSIX file lock, flush should release the lock, + * _even_if_ it's not the process's last file descriptor for this file. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(FlushWithLocks, DISABLED_unlock_on_close) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + struct flock fl; + pid_t pid = getpid(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.setlk.fh == FH); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; + }))); + expect_flush(ino, 1, pid, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); + + fd2 = dup(fd); + ASSERT_EQ(0, close(fd2)) << strerror(errno); + /* Deliberately leak fd */ } Index: projects/fuse2/tests/sys/fs/fuse/fsync.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/fsync.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/fsync.cc (revision 345188) @@ -1,283 +1,283 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; /* * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28. * This bit was actually part of kernel protocol version 5.2, but never * documented until after 7.28 */ #ifndef FUSE_FSYNC_FDATASYNC #define FUSE_FSYNC_FDATASYNC 1 #endif class Fsync: public FuseTest { public: void expect_fsync(uint64_t ino, uint32_t flags, int error) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC && in->header.nodeid == ino && //(pid_t)in->header.pid == getpid() && in->body.fsync.fh == FH && in->body.fsync.fsync_flags == flags); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(error))); } void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } void expect_write(uint64_t ino, uint64_t size, const void *contents) { FuseTest::expect_write(ino, 0, size, size, 0, contents); } }; /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_aio_fsync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; struct aiocb iocb, *piocb; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); bzero(&iocb, sizeof(iocb)); iocb.aio_fildes = fd; ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno); ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * fuse(4) should NOT fsync during VOP_RELEASE or VOP_INACTIVE * * This test only really make sense in writeback caching mode, but it should * still pass in any cache mode. */ TEST_F(Fsync, close) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC); }, Eq(true)), _) ).Times(0); - expect_release(ino, 1, 0); + expect_release(ino, 1, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); close(fd); } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); ASSERT_NE(0, fdatasync(fd)); ASSERT_EQ(EIO, errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fdatasync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); ASSERT_EQ(0, fdatasync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fsync) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefgh"; ssize_t bufsize = strlen(CONTENTS); uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Fsync should sync a file with dirty metadata but clean data */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fsync_metadata_only) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; mode_t mode = 0755; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | mode; }))); expect_fsync(ino, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fchmod(fd, mode)) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } // fsync()ing a file that isn't dirty should be a no-op TEST_F(Fsync, nop) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); fd = open(FULLPATH, O_WRONLY); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fsync(fd)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } // TODO: ENOSYS test Index: projects/fuse2/tests/sys/fs/fuse/locks.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/locks.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/locks.cc (revision 345188) @@ -1,434 +1,431 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" /* This flag value should probably be defined in fuse_kernel.h */ #define OFFSET_MAX 0x7fffffffffffffffLL using namespace testing; /* For testing filesystems without posix locking support */ class Fallback: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } }; /* For testing filesystems with posix locking support */ class Locks: public Fallback { virtual void SetUp() { m_init_flags = FUSE_POSIX_LOCKS; Fallback::SetUp(); } }; class GetlkFallback: public Fallback {}; class Getlk: public Locks {}; class SetlkFallback: public Fallback {}; class Setlk: public Locks {}; class SetlkwFallback: public Fallback {}; class Setlkw: public Locks {}; /* * If the fuse filesystem does not support posix file locks, then the kernel * should fall back to local locks. */ TEST_F(GetlkFallback, local) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = getpid(); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the filesystem has no locks that fit the description, the filesystem * should return F_UNLCK */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Getlk, DISABLED_no_locks) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.getlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); ASSERT_EQ(F_UNLCK, fl.l_type); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* A different pid does have a lock */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Getlk, DISABLED_lock_exists) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; - pid_t pid2 = 1234; + pid_t pid2 = 1235; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETLK && in->header.nodeid == ino && in->body.getlk.fh == FH && in->body.getlk.owner == (uint32_t)pid && in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.getlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk.start = 100; out->body.getlk.lk.end = 199; out->body.getlk.lk.type = F_WRLCK; out->body.getlk.lk.pid = (uint32_t)pid2;; }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); EXPECT_EQ(100, fl.l_start); EXPECT_EQ(100, fl.l_len); EXPECT_EQ(pid2, fl.l_pid); EXPECT_EQ(F_WRLCK, fl.l_type); EXPECT_EQ(SEEK_SET, fl.l_whence); EXPECT_EQ(0, fl.l_sysid); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the fuse filesystem does not support posix file locks, then the kernel * should fall back to local locks. */ TEST_F(SetlkFallback, local) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = getpid(); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Set a new lock with FUSE_SETLK */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlk, DISABLED_set) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == 1009 && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlk.fh == FH && + in->body.setlk.owner == (uint32_t)pid && + in->body.setlk.lk.start == 10 && + in->body.setlk.lk.end == 1009 && + in->body.setlk.lk.type == F_RDLCK && + in->body.setlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getlk); - out->body.getlk.lk = in->body.getlk.lk; - out->body.getlk.lk.type = F_UNLCK; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* l_len = 0 is a flag value that means to lock until EOF */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlk, DISABLED_set_eof) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == OFFSET_MAX && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlk.fh == FH && + in->body.setlk.owner == (uint32_t)pid && + in->body.setlk.lk.start == 10 && + in->body.setlk.lk.end == OFFSET_MAX && + in->body.setlk.lk.type == F_RDLCK && + in->body.setlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getlk); - out->body.getlk.lk = in->body.getlk.lk; - out->body.getlk.lk.type = F_UNLCK; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 0; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Fail to set a new lock with FUSE_SETLK due to a conflict */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlk, DISABLED_eagain) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == 1009 && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlk.fh == FH && + in->body.setlk.owner == (uint32_t)pid && + in->body.setlk.lk.start == 10 && + in->body.setlk.lk.end == 1009 && + in->body.setlk.lk.type == F_RDLCK && + in->body.setlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EAGAIN))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl)); ASSERT_EQ(EAGAIN, errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * If the fuse filesystem does not support posix file locks, then the kernel * should fall back to local locks. */ TEST_F(SetlkwFallback, local) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = getpid(); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Set a new lock with FUSE_SETLK. If the lock is not available, then the * command should block. But to the kernel, that's the same as just being * slow, so we don't need a separate test method */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ TEST_F(Setlkw, DISABLED_set) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; struct flock fl; int fd; pid_t pid = 1234; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == 1009 && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlkw.fh == FH && + in->body.setlkw.owner == (uint32_t)pid && + in->body.setlkw.lk.start == 10 && + in->body.setlkw.lk.end == 1009 && + in->body.setlkw.lk.type == F_RDLCK && + in->body.setlkw.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getlk); - out->body.getlk.lk = in->body.getlk.lk; - out->body.getlk.lk.type = F_UNLCK; + SET_OUT_HEADER_LEN(out, setlkw); + out->body.setlkw.lk = in->body.setlkw.lk; }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); fl.l_start = 10; fl.l_len = 1000; fl.l_pid = pid; fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_sysid = 0; ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } Index: projects/fuse2/tests/sys/fs/fuse/mockfs.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc (revision 345188) @@ -1,424 +1,432 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include #include #include #include #include #include #include #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)", "LOOKUP", "FORGET", "GETATTR", "SETATTR", "READLINK", "SYMLINK", "Unknown (opcode 7)", "MKNOD", "MKDIR", "UNLINK", "RMDIR", "RENAME", "LINK", "OPEN", "READ", "WRITE", "STATFS", "RELEASE", "Unknown (opcode 19)", "FSYNC", "SETXATTR", "GETXATTR", "LISTXATTR", "REMOVEXATTR", "FLUSH", "INIT", "OPENDIR", "READDIR", "RELEASEDIR", "FSYNCDIR", "GETLK", "SETLK", "SETLKW", "ACCESS", "CREATE", "INTERRUPT", "BMAP", "DESTROY" }; if (opcode >= NUM_OPS) return ("Unknown (opcode > max)"); else return (table[opcode]); } ProcessMockerT ReturnErrno(int error) { return([=](auto in, auto &out) { auto out0 = new mockfs_buf_out; out0->header.unique = in->header.unique; out0->header.error = -error; out0->header.len = sizeof(out0->header); out.push_back(out0); }); } /* Helper function used for returning negative cache entries for LOOKUP */ ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid) { return([=](auto in, auto &out) { /* nodeid means ENOENT and cache it */ auto out0 = new mockfs_buf_out; out0->body.entry.nodeid = 0; out0->header.unique = in->header.unique; out0->header.error = 0; out0->body.entry.entry_valid = entry_valid->tv_sec; out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec; SET_OUT_HEADER_LEN(out0, entry); out.push_back(out0); }); } ProcessMockerT ReturnImmediate(std::function f) { return([=](auto in, auto &out) { auto out0 = new mockfs_buf_out; f(in, out0); out.push_back(out0); }); } void sigint_handler(int __unused sig) { quit = 1; } void debug_fuseop(const mockfs_buf_in *in) { printf("%-11s ino=%2lu", opcode2opname(in->header.opcode), in->header.nodeid); if (verbosity > 1) { printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u", in->header.uid, in->header.gid, in->header.pid, in->header.unique, in->header.len); } switch (in->header.opcode) { + case FUSE_FLUSH: + printf(" lock_owner=%lu", in->body.flush.lock_owner); + break; case FUSE_FORGET: printf(" nlookup=%lu", in->body.forget.nlookup); break; case FUSE_FSYNC: printf(" flags=%#x", in->body.fsync.fsync_flags); break; case FUSE_FSYNCDIR: printf(" flags=%#x", in->body.fsyncdir.fsync_flags); break; case FUSE_LOOKUP: printf(" %s", in->body.lookup); break; case FUSE_OPEN: printf(" flags=%#x mode=%#o", in->body.open.flags, in->body.open.mode); break; case FUSE_OPENDIR: printf(" flags=%#x mode=%#o", in->body.opendir.flags, in->body.opendir.mode); break; case FUSE_READ: printf(" offset=%lu size=%u", in->body.read.offset, in->body.read.size); break; case FUSE_READDIR: printf(" offset=%lu size=%u", in->body.readdir.offset, in->body.readdir.size); + break; + case FUSE_RELEASE: + printf(" flags=%#x lock_owner=%lu", + in->body.release.flags, + in->body.release.lock_owner); break; case FUSE_SETATTR: if (verbosity <= 1) { printf(" valid=%#x", in->body.setattr.valid); break; } if (in->body.setattr.valid & FATTR_MODE) printf(" mode=%#o", in->body.setattr.mode); if (in->body.setattr.valid & FATTR_UID) printf(" uid=%u", in->body.setattr.uid); if (in->body.setattr.valid & FATTR_GID) printf(" gid=%u", in->body.setattr.gid); if (in->body.setattr.valid & FATTR_SIZE) printf(" size=%zu", in->body.setattr.size); if (in->body.setattr.valid & FATTR_ATIME) printf(" atime=%zu.%u", in->body.setattr.atime, in->body.setattr.atimensec); if (in->body.setattr.valid & FATTR_MTIME) printf(" mtime=%zu.%u", in->body.setattr.mtime, in->body.setattr.mtimensec); if (in->body.setattr.valid & FATTR_FH) printf(" fh=%zu", in->body.setattr.fh); break; case FUSE_SETXATTR: /* * In theory neither the xattr name and value need be * ASCII, but in this test suite they always are. */ { const char *attr = (const char*)in->body.bytes + sizeof(fuse_setxattr_in); const char *v = attr + strlen(attr) + 1; printf(" %s=%s", attr, v); } break; case FUSE_WRITE: printf(" offset=%lu size=%u flags=%u", in->body.write.offset, in->body.write.size, in->body.write.write_flags); break; default: break; } printf("\n"); } MockFS::MockFS(int max_readahead, uint32_t flags) { struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; m_daemon_id = NULL; m_maxreadahead = max_readahead; quit = 0; /* * 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(flags); signal(SIGUSR1, sigint_handler); if (pthread_create(&m_daemon_id, NULL, service, (void*)this)) throw(std::system_error(errno, std::system_category(), "Couldn't Couldn't start fuse thread")); } MockFS::~MockFS() { kill_daemon(); ::unmount("mountpoint", MNT_FORCE); rmdir("mountpoint"); } void MockFS::init(uint32_t flags) { mockfs_buf_in *in; mockfs_buf_out *out; in = (mockfs_buf_in*) malloc(sizeof(*in)); ASSERT_TRUE(in != NULL); out = (mockfs_buf_out*) malloc(sizeof(*out)); ASSERT_TRUE(out != 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; out->body.init.flags = in->body.init.flags & flags; /* * The default max_write is set to this formula in libfuse, though * individual filesystems can lower it. The "- 4096" was added in * commit 154ffe2, with the commit message "fix". */ uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096; /* For testing purposes, it should be distinct from MAXPHYS */ m_max_write = MIN(default_max_write, MAXPHYS / 2); out->body.init.max_write = m_max_write; out->body.init.max_readahead = m_maxreadahead; SET_OUT_HEADER_LEN(out, init); write(m_fuse_fd, out, out->header.len); free(in); } void MockFS::kill_daemon() { if (m_daemon_id != NULL) { 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); m_daemon_id = NULL; } } void MockFS::loop() { mockfs_buf_in *in; std::vector out; in = (mockfs_buf_in*) malloc(sizeof(*in)); ASSERT_TRUE(in != NULL); while (!quit) { bzero(in, sizeof(*in)); read_request(in); if (quit) break; if (verbosity > 0) debug_fuseop(in); if (pid_ok((pid_t)in->header.pid)) { process(in, out); } else { /* * 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); } for (auto &it: out) { ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 || errno == EAGAIN) << strerror(errno); delete it; } out.clear(); } free(in); } bool MockFS::pid_ok(pid_t pid) { if (pid == m_pid) { return (true); } else { struct kinfo_proc *ki; bool ok = false; ki = kinfo_getproc(pid); if (ki == NULL) return (false); /* * Allow access by the aio daemon processes so that our tests * can use aio functions */ if (0 == strncmp("aiod", ki->ki_comm, 4)) ok = true; free(ki); return (ok); } } void MockFS::process_default(const mockfs_buf_in *in, std::vector &out) { auto out0 = new mockfs_buf_out; out0->header.unique = in->header.unique; out0->header.error = -EOPNOTSUPP; out0->header.len = sizeof(out0->header); out.push_back(out0); } 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->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 345187) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 345188) @@ -1,236 +1,238 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include "fuse_kernel.h" } #include #define TIME_T_MAX (std::numeric_limits::max()) /* * A pseudo-fuse errno used indicate that a fuse operation should have no * response, at least not immediately */ #define FUSE_NORESPONSE 9999 #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ } /* * Create an expectation on FUSE_LOOKUP and return it so the caller can set * actions. * * This must be a macro instead of a method because EXPECT_CALL returns a type * with a deleted constructor. */ #define EXPECT_LOOKUP(parent, path) \ EXPECT_CALL(*m_mock, process( \ ResultOf([=](auto in) { \ return (in->header.opcode == FUSE_LOOKUP && \ in->header.nodeid == (parent) && \ strcmp(in->body.lookup, (path)) == 0); \ }, Eq(true)), \ _) \ ) extern int verbosity; /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ struct fuse_create_out { struct fuse_entry_out entry; struct fuse_open_out open; }; union fuse_payloads_in { fuse_access_in access; /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_flush_in flush; fuse_fsync_in fsync; fuse_fsync_in fsyncdir; fuse_forget_in forget; fuse_interrupt_in interrupt; fuse_lk_in getlk; fuse_getxattr_in getxattr; fuse_init_in init; fuse_link_in link; fuse_listxattr_in listxattr; char lookup[0]; fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; fuse_open_in opendir; fuse_read_in read; fuse_read_in readdir; fuse_release_in release; fuse_release_in releasedir; fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; fuse_setxattr_in setxattr; fuse_lk_in setlk; + fuse_lk_in setlkw; char unlink[0]; fuse_write_in write; }; struct mockfs_buf_in { fuse_in_header header; union fuse_payloads_in body; }; union fuse_payloads_out { fuse_attr_out attr; fuse_create_out create; /* The protocol places no limits on the size of bytes */ uint8_t bytes[0x20000]; fuse_entry_out entry; fuse_lk_out getlk; fuse_getxattr_out getxattr; fuse_init_out init; fuse_listxattr_out listxattr; fuse_open_out open; fuse_lk_out setlk; + fuse_lk_out setlkw; fuse_statfs_out statfs; /* * The protocol places no limits on the length of the string. This is * merely convenient for testing. */ char str[80]; fuse_write_out write; }; struct mockfs_buf_out { fuse_out_header header; union fuse_payloads_out body; /* Default constructor: zero everything */ mockfs_buf_out() { memset(this, 0, sizeof(*this)); } }; /* A function that can be invoked in place of MockFS::process */ typedef std::function &out)> ProcessMockerT; /* * Helper function used for setting an error expectation for any fuse operation. * The operation will return the supplied error */ ProcessMockerT ReturnErrno(int error); /* Helper function used for returning negative cache entries for LOOKUP */ ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid); /* Helper function used for returning a single immediate response */ ProcessMockerT ReturnImmediate( std::function f); /* * Fake FUSE filesystem * * "Mounts" a filesystem to a temporary directory and services requests * according to the programmed expectations. * * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api. */ class MockFS { /* * thread id of the fuse daemon thread * * It must run in a separate thread so it doesn't deadlock with the * client test code. */ pthread_t m_daemon_id; /* file descriptor of /dev/fuse control device */ int m_fuse_fd; /* The max_readahead filesystem option */ uint32_t m_maxreadahead; /* pid of the test process */ pid_t m_pid; /* Initialize a session after mounting */ void init(uint32_t flags); /* Is pid from a process that might be involved in the test? */ bool pid_ok(pid_t pid); /* Default request handler */ void process_default(const mockfs_buf_in*, std::vector&); /* Entry point for the daemon thread */ static void* service(void*); /* Read, but do not process, a single request from the kernel */ void read_request(mockfs_buf_in*); public: /* Maximum size of a FUSE_WRITE write */ uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ MockFS(int max_readahead, uint32_t flags); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ void kill_daemon(); /* Process FUSE requests endlessly */ void loop(); /* * Request handler * * This method is expected to provide the responses to each FUSE * operation. For an immediate response, push one buffer into out. * For a delayed response, push nothing. For an immediate response * plus a delayed response to an earlier operation, push two bufs. * Test cases must define each response using Googlemock expectations */ MOCK_METHOD2(process, void(const mockfs_buf_in*, std::vector&)); /* Gracefully unmount */ void unmount(); }; Index: projects/fuse2/tests/sys/fs/fuse/release.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/release.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/release.cc (revision 345188) @@ -1,143 +1,190 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Release: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino, int times) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, times); } }; -// TODO: lock owner stuff +class ReleaseWithLocks: public Release { + virtual void SetUp() { + m_init_flags = FUSE_POSIX_LOCKS; + Release::SetUp(); + } +}; + /* If a file descriptor is duplicated, only the last close causes RELEASE */ TEST_F(Release, dup) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd, fd2; expect_lookup(RELPATH, ino, 1); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_release(ino, 1, 0); + expect_release(ino, 1, 0, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); fd2 = dup(fd); ASSERT_EQ(0, close(fd2)) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } /* * Some FUSE filesystem cache data internally and flush it on release. Such * filesystems may generate errors during release. On Linux, these get * returned by close(2). However, POSIX does not require close(2) to return * this error. FreeBSD's fuse(4) should return EIO if it returns an error at * all. */ /* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */ TEST_F(Release, eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino, 1); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_release(ino, 1, EIO); + expect_release(ino, 1, 0, EIO); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); } /* * fuse(4) will issue multiple FUSE_OPEN operations for the same file if it's * opened with different modes. Each FUSE_OPEN should get its own * FUSE_RELEASE. */ TEST_F(Release, multiple_opens) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd, fd2; expect_lookup(RELPATH, ino, 2); expect_open(ino, 0, 2); expect_getattr(ino, 0); - expect_release(ino, 2, 0); + expect_release(ino, 2, 0, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); fd2 = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd2) << strerror(errno); ASSERT_EQ(0, close(fd2)) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } TEST_F(Release, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; int fd; expect_lookup(RELPATH, ino, 1); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_release(ino, 1, 0); + expect_release(ino, 1, 0, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(0, close(fd)) << strerror(errno); +} + +/* When closing a file with a POSIX file lock, release should release the lock*/ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(ReleaseWithLocks, DISABLED_unlock_on_close) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + struct flock fl; + pid_t pid = getpid(); + + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.setlk.fh == FH); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; + }))); + expect_release(ino, 1, (uint64_t)pid, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } Index: projects/fuse2/tests/sys/fs/fuse/setattr.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/setattr.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/setattr.cc (revision 345188) @@ -1,489 +1,535 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Setattr : public FuseTest {}; +/* + * If setattr 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(Setattr, DISABLED_attr_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + struct stat sb; + const mode_t newmode = 0644; + + EXPECT_LOOKUP(1, RELPATH) + .WillRepeatedly(Invoke(ReturnImmediate([=](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) { + /* In protocol 7.23, ctime will be changed too */ + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([](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_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR); + }, Eq(true)), + _) + ).Times(0); + + /* Set an attribute with SETATTR */ + ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); + + /* The stat(2) should use cached attributes */ + ASSERT_EQ(0, stat(FULLPATH, &sb)); + EXPECT_EQ(S_IFREG | newmode, sb.st_mode); +} + /* 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(1, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([](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(1, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([](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(1, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](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(1, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([=](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(ReturnImmediate([=](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(ReturnImmediate([=](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(1, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([=](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(ReturnImmediate([=](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(ReturnImmediate([=](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(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([](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(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([=](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(ReturnImmediate([=](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(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](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(ReturnImmediate([=](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(ReturnImmediate([=](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/utils.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/utils.cc (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/utils.cc (revision 345188) @@ -1,251 +1,253 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "mockfs.hh" #include "utils.hh" using namespace testing; class FuseEnv: public Environment { virtual void SetUp() { const char *mod_name = "fuse"; const char *devnode = "/dev/fuse"; const char *usermount_node = "vfs.usermount"; int usermount_val = 0; size_t usermount_size = sizeof(usermount_val); if (modfind(mod_name) == -1) { FAIL() << "Module " << mod_name << " could not be resolved"; } if (eaccess(devnode, R_OK | W_OK)) { if (errno == ENOENT) { FAIL() << devnode << " does not exist"; } else if (errno == EACCES) { FAIL() << devnode << " is not accessible by the current user"; } else { FAIL() << strerror(errno); } } sysctlbyname(usermount_node, &usermount_val, &usermount_size, NULL, 0); if (geteuid() != 0 && !usermount_val) FAIL() << "current user is not allowed to mount"; } }; void FuseTest::SetUp() { const char *node = "vfs.maxbcachebuf"; int val = 0; size_t size = sizeof(val); ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) << strerror(errno); m_maxbcachebuf = val; try { m_mock = new MockFS(m_maxreadahead, m_init_flags); } catch (std::system_error err) { FAIL() << err.what(); } } void FuseTest::expect_getattr(uint64_t ino, uint64_t size) { /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.size = size; out->body.attr.attr_valid = UINT64_MAX; }))); } void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, int times) { EXPECT_LOOKUP(1, relpath) .Times(times) .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = mode; out->body.entry.nodeid = ino; out->body.entry.attr.nlink = 1; out->body.entry.attr_valid = UINT64_MAX; }))); } void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && in->header.nodeid == ino); }, Eq(true)), _) ).Times(times) .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = FH; out->body.open.open_flags = flags; }))); } void FuseTest::expect_opendir(uint64_t ino) { EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, statfs); }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPENDIR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = FH; }))); } void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, const void *contents) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == FH && in->body.read.offset == offset && in->body.read.size == isize); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(struct fuse_out_header) + osize; memmove(out->body.bytes, contents, osize); }))).RetiresOnSaturation(); } -void FuseTest::expect_release(uint64_t ino, int times, int error) +void FuseTest::expect_release(uint64_t ino, int times, uint64_t lock_owner, + int error) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RELEASE && in->header.nodeid == ino && + in->body.release.lock_owner == lock_owner && in->body.release.fh == FH); }, Eq(true)), _) ).Times(times) .WillRepeatedly(Invoke(ReturnErrno(error))); } void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, uint32_t flags, const void *contents) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *buf = (const char*)in->body.bytes + sizeof(struct fuse_write_in); bool pid_ok; if (in->body.write.write_flags & FUSE_WRITE_CACHE) pid_ok = true; else pid_ok = (pid_t)in->header.pid == getpid(); return (in->header.opcode == FUSE_WRITE && in->header.nodeid == ino && in->body.write.fh == FH && in->body.write.offset == offset && in->body.write.size == isize && pid_ok && in->body.write.write_flags == flags && 0 == bcmp(buf, contents, isize)); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, write); out->body.write.size = osize; }))); } static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); exit(2); } int main(int argc, char **argv) { int ch; FuseEnv *fuse_env = new FuseEnv; InitGoogleTest(&argc, argv); AddGlobalTestEnvironment(fuse_env); while ((ch = getopt(argc, argv, "v")) != -1) { switch (ch) { case 'v': verbosity++; break; default: usage(argv[0]); break; } } return (RUN_ALL_TESTS()); } Index: projects/fuse2/tests/sys/fs/fuse/utils.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 345187) +++ projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 345188) @@ -1,115 +1,116 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * TODO: remove FUSE_WRITE_CACHE definition when upgrading to protocol 7.9. * This bit was actually part of kernel protocol version 7.2, but never * documented until 7.9 */ #ifndef FUSE_WRITE_CACHE #define FUSE_WRITE_CACHE 1 #endif class FuseTest : public ::testing::Test { protected: uint32_t m_maxreadahead; uint32_t m_init_flags; MockFS *m_mock = NULL; const static uint64_t FH = 0xdeadbeef1a7ebabe; public: int m_maxbcachebuf; /* * libfuse's default max_readahead is UINT_MAX, though it can be * lowered */ FuseTest(): FuseTest(UINT_MAX, 0) {} FuseTest(uint32_t maxreadahead): m_maxreadahead(maxreadahead) {} FuseTest(uint32_t maxreadahead, uint32_t init_flags): m_maxreadahead(maxreadahead), m_init_flags(init_flags) {} virtual void SetUp(); virtual void TearDown() { if (m_mock) delete m_mock; } /* * Create an expectation that FUSE_GETATTR will be called for the given * inode any number of times. It will respond with a few basic * attributes, like the given size and the mode S_IFREG | 0644 */ void expect_getattr(uint64_t ino, uint64_t size); /* * Create an expectation that FUSE_LOOKUP will be called for the given * path exactly times times. It will respond with inode ino, mode * mode, and cache validity forever. */ void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, int times); /* * Create an expectation that FUSE_GETATTR will be called for the given * inode exactly times times. It will return with open_flags flags and * file handle FH. */ void expect_open(uint64_t ino, uint32_t flags, int times); /* * Create an expectation that FUSE_OPENDIR will be called exactly once * for inode ino. */ void expect_opendir(uint64_t ino); /* * Create an expectation that FUSE_READ will be called exactly once for * the given inode, at offset offset and with size isize. It will * return the first osize bytes from contents */ void expect_read(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, const void *contents); /* * Create an expectation that FUSE_RELEASE will be called times times * for the given inode, returning error error */ - void expect_release(uint64_t ino, int times, int error); + void expect_release(uint64_t ino, int times, uint64_t lock_owner, + int error); /* * Create an expectation that FUSE_WRITE will be called exactly once * for the given inode, at offset offset, with write_flags flags, * size isize and buffer contents. It will return osize */ void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, uint32_t flags, const void *contents); };