extern "C" {
#include <sys/param.h>
#include <fcntl.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
typedef tuple<tuple<bool, bool, bool>, cache_mode, ssize_t, ssize_t> CacheParam;
class Cache: public FuseTest, public WithParamInterface<CacheParam> {
public:
bool m_direct_io;
Cache(): m_direct_io(false) {};
virtual void SetUp() {
int cache_mode = get<1>(GetParam());
switch (cache_mode) {
case Uncached:
m_direct_io = true;
break;
case WritebackAsync:
m_async = true;
case Writeback:
m_init_flags |= FUSE_WRITEBACK_CACHE;
case Writethrough:
break;
default:
FAIL() << "Unknown cache mode";
}
m_noatime = true;
FuseTest::SetUp();
if (IsSkipped())
return;
}
void expect_getattr(uint64_t ino, int times, uint64_t size, uint64_t attr_valid)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_GETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).Times(times)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr_valid = attr_valid;
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0644;
out.body.attr.attr.size = size;
})));
}
void expect_lookup(const char *relpath, uint64_t ino,
uint64_t size, uint64_t entry_valid, uint64_t attr_valid)
{
EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
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;
out.body.entry.attr_valid = attr_valid;
out.body.entry.attr.size = size;
out.body.entry.entry_valid = entry_valid;
})));
}
void expect_open(uint64_t ino, int times)
{
FuseTest::expect_open(ino, m_direct_io ? FOPEN_DIRECT_IO: 0, times);
}
void expect_release(uint64_t ino, ProcessMockerT r)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_RELEASE &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(r));
}
};
TEST_P(Cache, truncate_by_surprise_invalidates_cache)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
uint64_t ino = 42;
uint64_t attr_valid, entry_valid;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
bool reopen = get<0>(get<0>(GetParam()));
bool cache_lookups = get<1>(get<0>(GetParam()));
bool cache_attrs = get<2>(get<0>(GetParam()));
ssize_t osize = get<2>(GetParam());
ssize_t nsize = get<3>(GetParam());
ASSERT_LE(osize, bufsize);
ASSERT_LE(nsize, bufsize);
if (cache_attrs)
attr_valid = UINT64_MAX;
else
attr_valid = 0;
if (cache_lookups)
entry_valid = UINT64_MAX;
else
entry_valid = 0;
expect_lookup(RELPATH, ino, osize, entry_valid, attr_valid);
expect_open(ino, 1);
if (!cache_attrs)
expect_getattr(ino, 2, osize, attr_valid);
expect_read(ino, 0, osize, osize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(osize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, osize));
if (reopen) {
expect_flush(ino, 1, ReturnErrno(ENOSYS));
expect_release(ino, ReturnErrno(0));
ASSERT_EQ(0, close(fd));
expect_lookup(RELPATH, ino, nsize, entry_valid, attr_valid);
expect_open(ino, 1);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
}
if (!cache_attrs)
expect_getattr(ino, 1, nsize, attr_valid);
expect_read(ino, 0, nsize, nsize, CONTENTS);
ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
ASSERT_EQ(nsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, nsize));
leak(fd);
}
INSTANTIATE_TEST_SUITE_P(Cache, Cache,
Combine(
Values(
std::make_tuple(false, false, false),
std::make_tuple(false, true, false),
std::make_tuple(true, false, false),
std::make_tuple(true, false, true),
std::make_tuple(true, true, false)
),
Values(Writethrough, Writeback),
Values(20),
Values(10, 25)
)
);