From be65471ae9e2d8a80182a66192a0f274515c43fa Mon Sep 17 00:00:00 2001 From: asomers Date: Mon, 18 Mar 2019 18:05:19 +0000 Subject: [PATCH] fuse(4): add tests for some mount options. This commit adds tests for the default_permissions and push_symlinks_in mount options. It doesn't add tests for allow_other, because I'm not sure how that will interact with Kyua (the test will need to drop privileges). All of the other mount options are undocumented. PR: 216391 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 + tests/sys/fs/fuse/access.cc | 3 - tests/sys/fs/fuse/default_permissions.cc | 136 +++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.cc | 14 ++- tests/sys/fs/fuse/mockfs.hh | 3 +- tests/sys/fs/fuse/readlink.cc | 64 ++++++++--- tests/sys/fs/fuse/utils.cc | 3 +- tests/sys/fs/fuse/utils.hh | 20 ++-- 8 files changed, 217 insertions(+), 32 deletions(-) create mode 100644 tests/sys/fs/fuse/default_permissions.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index ec01b0c34762..64e606cc0053 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -9,6 +9,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse # out, so we get more granular reporting. ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create +ATF_TESTS_CXX+= default_permissions ATF_TESTS_CXX+= destroy ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= fsync @@ -46,6 +47,11 @@ SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc +SRCS.default_permissions+= default_permissions.cc +SRCS.default_permissions+= getmntopts.c +SRCS.default_permissions+= mockfs.cc +SRCS.default_permissions+= utils.cc + SRCS.destroy+= destroy.cc SRCS.destroy+= getmntopts.c SRCS.destroy+= mockfs.cc diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index 5ceaf567a678..094809be6462 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -29,7 +29,6 @@ */ extern "C" { -//#include #include #include } @@ -59,8 +58,6 @@ void expect_lookup(const char *relpath, uint64_t ino) } }; -/* 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) diff --git a/tests/sys/fs/fuse/default_permissions.cc b/tests/sys/fs/fuse/default_permissions.cc new file mode 100644 index 000000000000..ffc56bb6cdc3 --- /dev/null +++ b/tests/sys/fs/fuse/default_permissions.cc @@ -0,0 +1,136 @@ +/*- + * 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. + */ + +/* + * Tests for the "default_permissions" mount option. They must be in their own + * file so they can be run as an unprivileged user + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class DefaultPermissions: public FuseTest { + +virtual void SetUp() { + if (geteuid() == 0) { + FAIL() << "This test requires an unprivileged user"; + } + m_default_permissions = true; + FuseTest::SetUp(); +} + +public: +void expect_lookup(const char *relpath, uint64_t ino, mode_t mode) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | mode, 0, 1); +} + +}; + +class Access: public DefaultPermissions {}; +class Open: public DefaultPermissions {}; + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=216391 */ +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, ino, S_IFREG | 0644); + /* + * Once default_permissions is properly implemented, there might be + * another FUSE_GETATTR or something in here. But there should not be + * a FUSE_ACCESS + */ + + ASSERT_NE(0, access(FULLPATH, access_mode)); + ASSERT_EQ(EACCES, errno); +} + +TEST_F(Access, 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, ino, S_IFREG | 0644); + /* + * Once default_permissions is properly implemented, there might be + * another FUSE_GETATTR or something in here. But there should not be + * a FUSE_ACCESS + */ + + ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(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, ino, S_IFREG | 0644); + expect_open(ino, 0, 1); + /* Until the attr cache is working, we may send an additional GETATTR */ + expect_getattr(ino, 0); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=216391 */ +TEST_F(Open, DISABLED_eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino, S_IFREG | 0644); + /* + * Once default_permissions is properly implemented, there might be + * another FUSE_GETATTR or something in here. But there should not be + * a FUSE_ACCESS + */ + + EXPECT_NE(0, open(FULLPATH, O_RDWR)); + EXPECT_EQ(EPERM, errno); +} diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index bb5e7f6b24e0..a5a7e9130e4b 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -243,7 +243,9 @@ void debug_fuseop(const mockfs_buf_in *in) printf("\n"); } -MockFS::MockFS(int max_readahead, uint32_t flags) { +MockFS::MockFS(int max_readahead, bool push_symlinks_in, + bool default_permissions, uint32_t flags) +{ struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; @@ -277,6 +279,16 @@ MockFS::MockFS(int max_readahead, uint32_t flags) { __DECONST(void *, "mountpoint"), -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); build_iovec(&iov, &iovlen, "fd", fdstr, -1); + if (push_symlinks_in) { + const bool trueval = true; + build_iovec(&iov, &iovlen, "push_symlinks_in", + __DECONST(void*, &trueval), sizeof(bool)); + } + if (default_permissions) { + const bool trueval = true; + build_iovec(&iov, &iovlen, "default_permissions", + __DECONST(void*, &trueval), sizeof(bool)); + } if (nmount(iov, iovlen, 0)) throw(std::system_error(errno, std::system_category(), "Couldn't mount filesystem")); diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index b836d18d3088..5061fd51524e 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -212,7 +212,8 @@ class MockFS { uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ - MockFS(int max_readahead, uint32_t flags); + MockFS(int max_readahead, bool push_symlinks_in, + bool default_permissions, uint32_t flags); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 918bd885660e..de2828dd8774 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -29,6 +29,8 @@ */ extern "C" { +#include + #include } @@ -43,6 +45,24 @@ void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFLNK | 0777, 0, 1); } +void expect_readlink(uint64_t ino, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READLINK && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +}; + +class PushSymlinksIn: public Readlink { + virtual void SetUp() { + m_push_symlinks_in = true; + Readlink::SetUp(); + } }; TEST_F(Readlink, eloop) @@ -53,14 +73,7 @@ TEST_F(Readlink, eloop) char buf[80]; expect_lookup(RELPATH, ino); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READLINK && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(ELOOP))); + expect_readlink(ino, ReturnErrno(ELOOP)); EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf))); EXPECT_EQ(ELOOP, errno); @@ -75,20 +88,35 @@ TEST_F(Readlink, ok) char buf[80]; expect_lookup(RELPATH, ino); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READLINK && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { + expect_readlink(ino, ReturnImmediate([=](auto in __unused, auto out) { 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); } + +TEST_F(PushSymlinksIn, readlink) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char dst[] = "/dst"; + const uint64_t ino = 42; + char buf[MAXPATHLEN], wd[MAXPATHLEN], want[MAXPATHLEN]; + int len; + + expect_lookup(RELPATH, ino); + expect_readlink(ino, ReturnImmediate([=](auto in __unused, auto out) { + strlcpy(out->body.str, dst, sizeof(out->body.str)); + out->header.len = sizeof(out->header) + strlen(dst) + 1; + })); + + ASSERT_NE(NULL, getcwd(wd, sizeof(wd))) << strerror(errno); + len = snprintf(want, sizeof(want), "%s/mountpoint%s", wd, dst); + ASSERT_LE(0, len) << strerror(errno); + + EXPECT_EQ((ssize_t)len + 1, readlink(FULLPATH, buf, sizeof(buf))); + EXPECT_STREQ(want, buf); +} diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index abf5e378149f..a08daf36a8c1 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -78,7 +78,8 @@ void FuseTest::SetUp() { m_maxbcachebuf = val; try { - m_mock = new MockFS(m_maxreadahead, m_init_flags); + m_mock = new MockFS(m_maxreadahead, m_push_symlinks_in, + m_default_permissions, m_init_flags); } catch (std::system_error err) { FAIL() << err.what(); } diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index adaaebb40105..7791c4e0ef2c 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -41,20 +41,24 @@ class FuseTest : public ::testing::Test { protected: uint32_t m_maxreadahead; uint32_t m_init_flags; + bool m_default_permissions; + bool m_push_symlinks_in; 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) {} + FuseTest(): + /* + * libfuse's default max_readahead is UINT_MAX, though it can + * be lowered + */ + m_maxreadahead(UINT_MAX), + m_init_flags(0), + m_default_permissions(false), + m_push_symlinks_in(false) + {} virtual void SetUp();