fuse(4): add tests for unlink, rmdir, and statfs

Also, combine some common code for sending cacheable negative lookup
responses.

Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-03-06 00:38:10 +00:00
parent 9b4318e553
commit c7c8f59051
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=344831
10 changed files with 500 additions and 78 deletions

View File

@ -14,8 +14,11 @@ ATF_TESTS_CXX+= mknod
ATF_TESTS_CXX+= open
ATF_TESTS_CXX+= readlink
ATF_TESTS_CXX+= rename
ATF_TESTS_CXX+= rmdir
ATF_TESTS_CXX+= setattr
ATF_TESTS_CXX+= statfs
ATF_TESTS_CXX+= symlink
ATF_TESTS_CXX+= unlink
SRCS.access+= access.cc
SRCS.access+= getmntopts.c
@ -67,16 +70,31 @@ SRCS.rename+= mockfs.cc
SRCS.rename+= rename.cc
SRCS.rename+= utils.cc
SRCS.rmdir+= getmntopts.c
SRCS.rmdir+= mockfs.cc
SRCS.rmdir+= rmdir.cc
SRCS.rmdir+= utils.cc
SRCS.setattr+= getmntopts.c
SRCS.setattr+= mockfs.cc
SRCS.setattr+= setattr.cc
SRCS.setattr+= utils.cc
SRCS.statfs+= getmntopts.c
SRCS.statfs+= mockfs.cc
SRCS.statfs+= statfs.cc
SRCS.statfs+= utils.cc
SRCS.symlink+= getmntopts.c
SRCS.symlink+= mockfs.cc
SRCS.symlink+= symlink.cc
SRCS.symlink+= utils.cc
SRCS.unlink+= getmntopts.c
SRCS.unlink+= mockfs.cc
SRCS.unlink+= unlink.cc
SRCS.unlink+= utils.cc
# TODO: drastically increase timeout after test development is mostly complete
TEST_METADATA+= timeout=10

View File

@ -195,21 +195,15 @@ TEST_F(Create, DISABLED_entry_cache_negative)
mode_t mode = 0755;
uint64_t ino = 42;
int fd;
/*
* Set entry_valid = 0 because this test isn't concerned with whether
* or not we actually cache negative entries, only with whether we
* interpret negative cache responses correctly.
*/
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
/* create will first do a LOOKUP, adding a negative cache entry */
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) {
/* nodeid means ENOENT and cache it */
out->body.entry.nodeid = 0;
out->header.unique = in->header.unique;
out->header.error = 0;
/*
* Set entry_valid = 0 because this test isn't concerned with
* whether or not we actually cache negative entries, only with
* whether we interpret negative cache responses correctly.
*/
out->body.entry.entry_valid = 0;
SET_OUT_HEADER_LEN(out, entry);
}));
EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
@ -258,17 +252,12 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge)
mode_t mode = 0755;
uint64_t ino = 42;
int fd;
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
/* create will first do a LOOKUP, adding a negative cache entry */
EXPECT_LOOKUP(1, RELPATH).Times(1)
.WillOnce(Invoke([=](auto in, auto out) {
/* nodeid means ENOENT and cache it */
out->body.entry.nodeid = 0;
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.entry.entry_valid = UINT64_MAX;
SET_OUT_HEADER_LEN(out, entry);
})).RetiresOnSaturation();
.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
.RetiresOnSaturation();
/* Then the CREATE should purge the negative cache entry */
EXPECT_CALL(*m_mock, process(

View File

@ -181,14 +181,11 @@ TEST_F(Lookup, entry_cache)
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */
TEST_F(Lookup, DISABLED_entry_cache_negative)
{
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
EXPECT_LOOKUP(1, "does_not_exist").Times(1)
.WillOnce(Invoke([](auto in, auto out) {
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.entry.nodeid = 0;
out->body.entry.entry_valid = UINT64_MAX;
SET_OUT_HEADER_LEN(out, entry);
}));
.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)));
EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
EXPECT_EQ(ENOENT, errno);
EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
@ -204,20 +201,15 @@ TEST_F(Lookup, entry_cache_negative_timeout)
* The timeout should be longer than the longest plausible time the
* daemon would take to complete a write(2) to /dev/fuse, but no longer.
*/
long timeout_ns = 250'000'000;
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 250'000'000};
EXPECT_LOOKUP(1, RELPATH).Times(2)
.WillRepeatedly(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.entry.nodeid = 0;
out->body.entry.entry_valid_nsec = timeout_ns;
SET_OUT_HEADER_LEN(out, entry);
}));
.WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid)));
EXPECT_NE(0, access(FULLPATH, F_OK));
EXPECT_EQ(ENOENT, errno);
usleep(2 * timeout_ns / 1000);
usleep(2 * entry_valid.tv_nsec / 1000);
/* The cache has timed out; VOP_LOOKUP should requery the daemon*/
EXPECT_NE(0, access(FULLPATH, F_OK));

View File

@ -75,21 +75,15 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative)
const char RELPATH[] = "some_file.txt";
mode_t mode = 0755;
uint64_t ino = 42;
/*
* Set entry_valid = 0 because this test isn't concerned with whether
* or not we actually cache negative entries, only with whether we
* interpret negative cache responses correctly.
*/
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
/* mkdir will first do a LOOKUP, adding a negative cache entry */
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) {
/* nodeid means ENOENT and cache it */
out->body.entry.nodeid = 0;
out->header.unique = in->header.unique;
out->header.error = 0;
/*
* Set entry_valid = 0 because this test isn't concerned with
* whether or not we actually cache negative entries, only with
* whether we interpret negative cache responses correctly.
*/
out->body.entry.entry_valid = 0;
SET_OUT_HEADER_LEN(out, entry);
}));
EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
@ -122,17 +116,12 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge)
const char RELPATH[] = "some_file.txt";
mode_t mode = 0755;
uint64_t ino = 42;
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
/* mkdir will first do a LOOKUP, adding a negative cache entry */
EXPECT_LOOKUP(1, RELPATH).Times(1)
.WillOnce(Invoke([=](auto in, auto out) {
/* nodeid means ENOENT and cache it */
out->body.entry.nodeid = 0;
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.entry.entry_valid = UINT64_MAX;
SET_OUT_HEADER_LEN(out, entry);
})).RetiresOnSaturation();
.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
.RetiresOnSaturation();
/* Then the MKDIR should purge the negative cache entry */
EXPECT_CALL(*m_mock, process(

View File

@ -112,6 +112,21 @@ ReturnErrno(int error)
});
}
/* Helper function used for returning negative cache entries for LOOKUP */
std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
ReturnNegativeCache(const struct timespec *entry_valid)
{
return([=](auto in, auto out) {
/* nodeid means ENOENT and cache it */
out->body.entry.nodeid = 0;
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.entry.entry_valid = entry_valid->tv_sec;
out->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
SET_OUT_HEADER_LEN(out, entry);
});
}
void sigint_handler(int __unused sig) {
quit = 1;
}
@ -135,6 +150,9 @@ MockFS::MockFS() {
int iovlen = 0;
char fdstr[15];
m_daemon_id = NULL;
quit = 0;
/*
* Kyua sets pwd to a testcase-unique tempdir; no need to use
* mkdtemp
@ -169,18 +187,14 @@ MockFS::MockFS() {
.WillByDefault(Invoke(this, &MockFS::process_default));
init();
if (pthread_create(&m_thr, NULL, service, (void*)this))
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() {
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);
kill_daemon();
::unmount("mountpoint", MNT_FORCE);
rmdir("mountpoint");
}
@ -206,6 +220,18 @@ void MockFS::init() {
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;
mockfs_buf_out out;
@ -258,10 +284,6 @@ void MockFS::read_request(mockfs_buf_in *in) {
void* MockFS::service(void *pthr_data) {
MockFS *mock_fs = (MockFS*)pthr_data;
mock_fs->m_daemon_id = pthread_self();
quit = 0;
signal(SIGUSR1, sigint_handler);
mock_fs->loop();

View File

@ -37,6 +37,8 @@ extern "C" {
#include <gmock/gmock.h>
#define TIME_T_MAX (std::numeric_limits<time_t>::max())
#define SET_OUT_HEADER_LEN(out, variant) { \
(out)->header.len = (sizeof((out)->header) + \
sizeof((out)->body.variant)); \
@ -79,7 +81,9 @@ union fuse_payloads_in {
fuse_mknod_in mknod;
fuse_open_in open;
fuse_rename_in rename;
char rmdir[0];
fuse_setattr_in setattr;
char unlink[0];
};
struct mockfs_buf_in {
@ -93,6 +97,7 @@ union fuse_payloads_out {
fuse_entry_out entry;
fuse_init_out init;
fuse_open_out open;
fuse_statfs_out statfs;
/*
* The protocol places no limits on the length of the string. This is
* merely convenient for testing.
@ -112,6 +117,10 @@ struct mockfs_buf_out {
std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
ReturnErrno(int error);
/* Helper function used for returning negative cache entries for LOOKUP */
std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
ReturnNegativeCache(const struct timespec *entry_valid);
/*
* Fake FUSE filesystem
*
@ -122,7 +131,12 @@ ReturnErrno(int error);
*/
class MockFS {
public:
/* thread id of the fuse daemon thread */
/*
* 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;
private:
@ -132,14 +146,6 @@ class MockFS {
/* pid of the test process */
pid_t m_pid;
/*
* Thread that's running the mockfs daemon.
*
* It must run in a separate thread so it doesn't deadlock with the
* client test code.
*/
pthread_t m_thr;
/* Initialize a session after mounting */
void init();
@ -157,6 +163,9 @@ class MockFS {
MockFS();
virtual ~MockFS();
/* Kill the filesystem daemon without unmounting the filesystem */
void kill_daemon();
/* Process FUSE requests endlessly */
void loop();

View File

@ -87,6 +87,111 @@ TEST_F(Rename, enoent)
ASSERT_EQ(ENOENT, errno);
}
/*
* Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst
*/
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */
TEST_F(Rename, DISABLED_entry_cache_negative)
{
const char FULLDST[] = "mountpoint/dst";
const char RELDST[] = "dst";
const char FULLSRC[] = "mountpoint/src";
const char RELSRC[] = "src";
// FUSE hardcodes the mountpoint to inocde 1
uint64_t dst_dir_ino = 1;
uint64_t ino = 42;
/*
* Set entry_valid = 0 because this test isn't concerned with whether
* or not we actually cache negative entries, only with whether we
* interpret negative cache responses correctly.
*/
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = ino;
SET_OUT_HEADER_LEN(out, entry);
}));
/* LOOKUP returns a negative cache entry for dst */
EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *src = (const char*)in->body.bytes +
sizeof(fuse_rename_in);
const char *dst = src + strlen(src) + 1;
return (in->header.opcode == FUSE_RENAME &&
in->body.rename.newdir == dst_dir_ino &&
(0 == strcmp(RELDST, dst)) &&
(0 == strcmp(RELSRC, src)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(0)));
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
}
/*
* Renaming a file should purge any negative namecache entries for the dst
*/
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */
TEST_F(Rename, DISABLED_entry_cache_negative_purge)
{
const char FULLDST[] = "mountpoint/dst";
const char RELDST[] = "dst";
const char FULLSRC[] = "mountpoint/src";
const char RELSRC[] = "src";
// FUSE hardcodes the mountpoint to inocde 1
uint64_t dst_dir_ino = 1;
uint64_t ino = 42;
/*
* Set entry_valid = 0 because this test isn't concerned with whether
* or not we actually cache negative entries, only with whether we
* interpret negative cache responses correctly.
*/
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = ino;
SET_OUT_HEADER_LEN(out, entry);
}));
/* LOOKUP returns a negative cache entry for dst */
EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid))
.RetiresOnSaturation();
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *src = (const char*)in->body.bytes +
sizeof(fuse_rename_in);
const char *dst = src + strlen(src) + 1;
return (in->header.opcode == FUSE_RENAME &&
in->body.rename.newdir == dst_dir_ino &&
(0 == strcmp(RELDST, dst)) &&
(0 == strcmp(RELSRC, src)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(0)));
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
/* Finally, a subsequent lookup should query the daemon */
EXPECT_LOOKUP(1, RELDST).Times(1)
.WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.entry.nodeid = ino;
out->body.entry.attr.mode = S_IFREG | 0644;
SET_OUT_HEADER_LEN(out, entry);
}));
ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno);
}
TEST_F(Rename, exdev)
{
const char FULLB[] = "mountpoint/src";

View File

@ -0,0 +1,90 @@
/*-
* Copyright (c) 2019 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by BFF Storage Systems, LLC under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
extern "C" {
#include <fcntl.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
class Rmdir: public FuseTest {};
TEST_F(Rmdir, enotempty)
{
const char FULLPATH[] = "mountpoint/some_dir";
const char RELPATH[] = "some_dir";
uint64_t ino = 42;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFDIR | 0755;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 2;
}));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_RMDIR &&
0 == strcmp(RELPATH, in->body.rmdir) &&
in->header.nodeid == 1);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(ENOTEMPTY)));
ASSERT_NE(0, rmdir(FULLPATH));
ASSERT_EQ(ENOTEMPTY, errno);
}
TEST_F(Rmdir, ok)
{
const char FULLPATH[] = "mountpoint/some_dir";
const char RELPATH[] = "some_dir";
uint64_t ino = 42;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFDIR | 0755;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 2;
}));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_RMDIR &&
0 == strcmp(RELPATH, in->body.rmdir) &&
in->header.nodeid == 1);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(0)));
ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
}

118
tests/sys/fs/fuse/statfs.cc Normal file
View File

@ -0,0 +1,118 @@
/*-
* Copyright (c) 2019 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by BFF Storage Systems, LLC under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
extern "C" {
#include <sys/param.h>
#include <sys/mount.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
class Statfs: public FuseTest {};
TEST_F(Statfs, eio)
{
struct statfs statbuf;
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_STATFS);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EIO)));
ASSERT_NE(0, statfs("mountpoint", &statbuf));
ASSERT_EQ(EIO, errno);
}
/*
* When the daemon is dead but the filesystem is still mounted, fuse(4) fakes
* the statfs(2) response, which is necessary for unmounting.
*/
TEST_F(Statfs, enotconn)
{
struct statfs statbuf;
char mp[PATH_MAX];
m_mock->kill_daemon();
ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno);
strlcat(mp, "/mountpoint", PATH_MAX);
ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
EXPECT_EQ(getuid(), statbuf.f_owner);
EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename));
EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname));
EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname));
}
TEST_F(Statfs, ok)
{
struct statfs statbuf;
char mp[PATH_MAX];
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_STATFS);
}, Eq(true)),
_)
).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, statfs);
out->body.statfs.st.blocks = 1000;
out->body.statfs.st.bfree = 100;
out->body.statfs.st.bavail = 200;
out->body.statfs.st.files = 5;
out->body.statfs.st.ffree = 6;
out->body.statfs.st.namelen = 128;
out->body.statfs.st.frsize = 1024;
}));
ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno);
strlcat(mp, "/mountpoint", PATH_MAX);
ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
EXPECT_EQ(1024ul, statbuf.f_bsize);
/*
* fuse(4) ignores the filesystem's reported optimal transfer size, and
* chooses a size that works well with the rest of the system instead
*/
EXPECT_EQ(1000ul, statbuf.f_blocks);
EXPECT_EQ(100ul, statbuf.f_bfree);
EXPECT_EQ(200l, statbuf.f_bavail);
EXPECT_EQ(5ul, statbuf.f_files);
EXPECT_EQ(6l, statbuf.f_ffree);
EXPECT_EQ(128u, statbuf.f_namemax);
EXPECT_EQ(getuid(), statbuf.f_owner);
EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename));
EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname));
EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname));
}

View File

@ -0,0 +1,90 @@
/*-
* Copyright (c) 2019 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by BFF Storage Systems, LLC under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
extern "C" {
#include <fcntl.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
class Unlink: public FuseTest {};
TEST_F(Unlink, eperm)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 1;
}));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_UNLINK &&
0 == strcmp(RELPATH, in->body.unlink) &&
in->header.nodeid == 1);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EPERM)));
ASSERT_NE(0, unlink(FULLPATH));
ASSERT_EQ(EPERM, errno);
}
TEST_F(Unlink, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 1;
}));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_UNLINK &&
0 == strcmp(RELPATH, in->body.unlink) &&
in->header.nodeid == 1);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(0)));
ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
}