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:
parent
9b4318e553
commit
c7c8f59051
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=344831
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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";
|
||||
|
90
tests/sys/fs/fuse/rmdir.cc
Normal file
90
tests/sys/fs/fuse/rmdir.cc
Normal 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
118
tests/sys/fs/fuse/statfs.cc
Normal 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));
|
||||
}
|
90
tests/sys/fs/fuse/unlink.cc
Normal file
90
tests/sys/fs/fuse/unlink.cc
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user