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
This commit is contained in:
asomers 2019-03-18 18:05:19 +00:00
parent 1f9aeb4e2f
commit be65471ae9
8 changed files with 217 additions and 32 deletions

View File

@ -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

View File

@ -29,7 +29,6 @@
*/
extern "C" {
//#include <sys/param.h>
#include <fcntl.h>
#include <unistd.h>
}
@ -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)

View File

@ -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 <fcntl.h>
#include <unistd.h>
}
#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);
}

View File

@ -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"));

View File

@ -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 */

View File

@ -29,6 +29,8 @@
*/
extern "C" {
#include <sys/param.h>
#include <unistd.h>
}
@ -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);
}

View File

@ -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();
}

View File

@ -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;
FuseTest():
/*
* libfuse's default max_readahead is UINT_MAX, though it can be
* lowered
* 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) {}
m_maxreadahead(UINT_MAX),
m_init_flags(0),
m_default_permissions(false),
m_push_symlinks_in(false)
{}
virtual void SetUp();