extern "C" {
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <aio.h>
#include <fcntl.h>
#include <semaphore.h>
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
class Read: public FuseTest {
public:
void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
{
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
}
};
class RofsRead: public Read {
public:
virtual void SetUp() {
m_ro = true;
Read::SetUp();
}
};
class Read_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
{
FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
}
};
class AioRead: public Read {
public:
virtual void SetUp() {
if (!is_unsafe_aio_enabled())
GTEST_SKIP() <<
"vfs.aio.enable_unsafe must be set for this test";
FuseTest::SetUp();
}
};
class AsyncRead: public AioRead {
virtual void SetUp() {
m_init_flags = FUSE_ASYNC_READ;
AioRead::SetUp();
}
};
class ReadAhead: public Read,
public WithParamInterface<tuple<bool, int>>
{
virtual void SetUp() {
int val;
const char *node = "vfs.maxbcachebuf";
size_t size = sizeof(val);
ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
<< strerror(errno);
m_maxreadahead = val * get<1>(GetParam());
m_noclusterr = get<0>(GetParam());
Read::SetUp();
}
};
class ReadMaxRead: public Read {
virtual void SetUp() {
m_maxread = 16384;
Read::SetUp();
}
};
class ReadNoatime: public Read {
virtual void SetUp() {
m_noatime = true;
Read::SetUp();
}
};
class ReadSigbus: public Read
{
public:
static jmp_buf s_jmpbuf;
static void *s_si_addr;
void TearDown() {
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(SIGBUS, &sa, NULL);
FuseTest::TearDown();
}
};
static void
handle_sigbus(int signo __unused, siginfo_t *info, void *uap __unused) {
ReadSigbus::s_si_addr = info->si_addr;
longjmp(ReadSigbus::s_jmpbuf, 1);
}
jmp_buf ReadSigbus::s_jmpbuf;
void *ReadSigbus::s_si_addr;
class TimeGran: public Read, public WithParamInterface<unsigned> {
public:
virtual void SetUp() {
m_time_gran = 1 << GetParam();
Read::SetUp();
}
};
TEST_F(AioRead, aio_read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
struct aiocb iocb, *piocb;
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
iocb.aio_nbytes = bufsize;
iocb.aio_fildes = fd;
iocb.aio_buf = buf;
iocb.aio_offset = 0;
iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
leak(fd);
}
TEST_F(AioRead, async_read_disabled)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
ssize_t bufsize = 50;
char buf0[bufsize], buf1[bufsize];
off_t off0 = 0;
off_t off1 = m_maxbcachebuf;
struct aiocb iocb0, iocb1;
volatile sig_atomic_t read_count = 0;
expect_lookup(RELPATH, ino, 131072);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == FH &&
in.body.read.offset == (uint64_t)off0);
}, Eq(true)),
_)
).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
read_count++;
}));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == FH &&
in.body.read.offset == (uint64_t)off1);
}, Eq(true)),
_)
).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
read_count++;
}));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
iocb0.aio_nbytes = bufsize;
iocb0.aio_fildes = fd;
iocb0.aio_buf = buf0;
iocb0.aio_offset = off0;
iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
iocb1.aio_nbytes = bufsize;
iocb1.aio_fildes = fd;
iocb1.aio_buf = buf1;
iocb1.aio_offset = off1;
iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
nap();
EXPECT_EQ(read_count, 1);
m_mock->kill_daemon();
(void)aio_waitcomplete(NULL, NULL);
leak(fd);
}
TEST_F(AsyncRead, async_read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
ssize_t bufsize = 50;
char buf0[bufsize], buf1[bufsize];
off_t off0 = 0;
off_t off1 = m_maxbcachebuf;
off_t fsize = 2 * m_maxbcachebuf;
struct aiocb iocb0, iocb1;
sem_t sem;
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
expect_lookup(RELPATH, ino, fsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == FH &&
in.body.read.offset == (uint64_t)off0);
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
sem_post(&sem);
}));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == FH &&
in.body.read.offset == (uint64_t)off1);
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
sem_post(&sem);
}));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
iocb0.aio_nbytes = bufsize;
iocb0.aio_fildes = fd;
iocb0.aio_buf = buf0;
iocb0.aio_offset = off0;
iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
iocb1.aio_nbytes = bufsize;
iocb1.aio_fildes = fd;
iocb1.aio_buf = buf1;
iocb1.aio_offset = off1;
iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
m_mock->kill_daemon();
(void)aio_waitcomplete(NULL, NULL);
leak(fd);
}
TEST_F(Read, atime)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct stat sb1, sb2;
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb1));
nap();
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb2));
EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, <));
EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
leak(fd);
}
TEST_F(Read, atime_cached)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct stat sb1, sb2;
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb1));
nap();
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb2));
EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, <));
EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
leak(fd);
}
TEST_F(Read, atime_during_close)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct stat sb;
uint64_t ino = 42;
const mode_t newmode = 0755;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
uint32_t valid = FATTR_ATIME;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
(time_t)in.body.setattr.atime ==
sb.st_atim.tv_sec &&
(long)in.body.setattr.atimensec ==
sb.st_atim.tv_nsec);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
})));
expect_flush(ino, 1, ReturnErrno(0));
expect_release(ino, FuseTest::FH);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
nap();
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb));
close(fd);
}
TEST_F(Read, atime_during_close_eacces)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
uint32_t valid = FATTR_ATIME;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EACCES)));
expect_flush(ino, 1, ReturnErrno(0));
expect_release(ino, FuseTest::FH);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
nap();
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, close(fd));
}
TEST_F(Read, atime_during_setattr)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct stat sb;
uint64_t ino = 42;
const mode_t newmode = 0755;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
uint32_t valid = FATTR_MODE | FATTR_ATIME;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
(time_t)in.body.setattr.atime ==
sb.st_atim.tv_sec &&
(long)in.body.setattr.atimensec ==
sb.st_atim.tv_nsec);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
})));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
nap();
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb));
ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
leak(fd);
}
TEST_F(Read, direct_io_read_nothing)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
uint64_t offset = 100;
char buf[80];
expect_lookup(RELPATH, ino, offset + 1000);
expect_open(ino, FOPEN_DIRECT_IO, 1);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
leak(fd);
}
TEST_F(Read, direct_io_pread)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
uint64_t offset = 100;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, offset + bufsize);
expect_open(ino, FOPEN_DIRECT_IO, 1);
expect_read(ino, offset, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
expect_read(ino, offset, bufsize, bufsize, CONTENTS);
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
leak(fd);
}
TEST_F(Read, direct_io_short_read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefghijklmnop";
uint64_t ino = 42;
int fd;
uint64_t offset = 100;
ssize_t bufsize = strlen(CONTENTS);
ssize_t halfbufsize = bufsize / 2;
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, offset + bufsize);
expect_open(ino, FOPEN_DIRECT_IO, 1);
expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
<< strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
leak(fd);
}
TEST_F(Read, eio)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EIO)));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(EIO, errno);
leak(fd);
}
TEST_F(Read, eof)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefghijklmnop";
uint64_t ino = 42;
int fd;
uint64_t offset = 100;
ssize_t bufsize = strlen(CONTENTS);
ssize_t partbufsize = 3 * bufsize / 4;
ssize_t r;
uint8_t buf[bufsize];
struct stat sb;
expect_lookup(RELPATH, ino, offset + bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, offset + bufsize, offset + partbufsize, CONTENTS);
expect_getattr(ino, offset + partbufsize);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
r = pread(fd, buf, bufsize, offset);
ASSERT_LE(0, r) << strerror(errno);
EXPECT_EQ(partbufsize, r) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb));
EXPECT_EQ((off_t)(offset + partbufsize), sb.st_size);
leak(fd);
}
TEST_F(Read, eof_of_whole_buffer)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefghijklmnop";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
off_t old_filesize = m_maxbcachebuf * 2 + bufsize;
uint8_t buf[bufsize];
struct stat sb;
expect_lookup(RELPATH, ino, old_filesize);
expect_open(ino, 0, 1);
expect_read(ino, 2 * m_maxbcachebuf, bufsize, bufsize, CONTENTS);
expect_read(ino, m_maxbcachebuf, m_maxbcachebuf, 0, CONTENTS);
expect_getattr(ino, m_maxbcachebuf);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2))
<< strerror(errno);
ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf))
<< strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb));
EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size);
leak(fd);
}
TEST_F(Read, keep_cache)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd0, fd1;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
expect_open(ino, FOPEN_KEEP_CACHE, 2);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd0 = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd0) << strerror(errno);
ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
fd1 = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd1) << strerror(errno);
ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno);
leak(fd0);
leak(fd1);
}
TEST_F(Read, keep_cache_disabled)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd0, fd1;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
expect_open(ino, 0, 2);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd0 = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd0) << strerror(errno);
ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
fd1 = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd1) << strerror(errno);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno);
ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
leak(fd0);
leak(fd1);
}
TEST_F(Read, mmap)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t len;
size_t bufsize = strlen(CONTENTS);
void *p;
len = getpagesize();
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == Read::FH &&
in.body.read.offset == 0 &&
in.body.read.size == bufsize);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
out.header.len = sizeof(struct fuse_out_header) + bufsize;
memmove(out.body.bytes, CONTENTS, bufsize);
})));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
leak(fd);
}
TEST_F(ReadMaxRead, split)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
ssize_t bufsize = 65536;
ssize_t fragsize = bufsize / 4;
char *rbuf, *frag0, *frag1, *frag2, *frag3;
rbuf = new char[bufsize]();
frag0 = new char[fragsize]();
frag1 = new char[fragsize]();
frag2 = new char[fragsize]();
frag3 = new char[fragsize]();
memset(frag0, '0', fragsize);
memset(frag1, '1', fragsize);
memset(frag2, '2', fragsize);
memset(frag3, '3', fragsize);
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, fragsize, fragsize, frag0);
expect_read(ino, fragsize, fragsize, fragsize, frag1);
expect_read(ino, 2 * fragsize, fragsize, fragsize, frag2);
expect_read(ino, 3 * fragsize, fragsize, fragsize, frag3);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(rbuf, frag0, fragsize));
ASSERT_EQ(0, memcmp(rbuf + fragsize, frag1, fragsize));
ASSERT_EQ(0, memcmp(rbuf + 2 * fragsize, frag2, fragsize));
ASSERT_EQ(0, memcmp(rbuf + 3 * fragsize, frag3, fragsize));
delete[] frag3;
delete[] frag2;
delete[] frag1;
delete[] frag0;
delete[] rbuf;
leak(fd);
}
TEST_F(ReadNoatime, atime)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct stat sb1, sb2;
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb1));
nap();
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb2));
EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, ==));
EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
leak(fd);
}
TEST_F(ReadNoatime, atime_cached)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct stat sb1, sb2;
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb1));
nap();
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
ASSERT_EQ(0, fstat(fd, &sb2));
EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, ==));
EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
leak(fd);
}
TEST_F(ReadSigbus, mmap_eio)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct sigaction sa;
uint64_t ino = 42;
int fd;
ssize_t len;
size_t bufsize = strlen(CONTENTS);
void *p;
len = getpagesize();
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == Read::FH);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnErrno(EIO)));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sa.sa_sigaction = handle_sigbus;
sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno);
if (setjmp(ReadSigbus::s_jmpbuf) == 0) {
atomic_signal_fence(std::memory_order::memory_order_seq_cst);
volatile char x __unused = *(volatile char*)p;
FAIL() << "shouldn't get here";
}
ASSERT_EQ(p, ReadSigbus::s_si_addr);
ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
leak(fd);
}
TEST_F(Read, mmap_eof)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t len;
size_t bufsize = strlen(CONTENTS);
struct stat sb;
void *p;
len = getpagesize();
expect_lookup(RELPATH, ino, m_maxbcachebuf);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == Read::FH &&
in.body.read.offset == 0 &&
in.body.read.size == (uint32_t)m_maxbcachebuf);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
out.header.len = sizeof(struct fuse_out_header) + bufsize;
memmove(out.body.bytes, CONTENTS, bufsize);
})));
expect_getattr(ino, bufsize);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
EXPECT_EQ((off_t)bufsize, sb.st_size);
ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
leak(fd);
}
TEST_F(ReadSigbus, mmap_getblksz_fail)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct sigaction sa;
Sequence seq;
uint64_t ino = 42;
int fd;
ssize_t len;
size_t bufsize = strlen(CONTENTS);
mode_t mode = S_IFREG | 0644;
void *p;
len = getpagesize();
FuseTest::expect_lookup(RELPATH, ino, mode, bufsize, 1, 0);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_GETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).Times(2)
.InSequence(seq)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = mode;
out.body.attr.attr.size = bufsize;
out.body.attr.attr_valid = 0;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_GETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).InSequence(seq)
.WillRepeatedly(Invoke(ReturnErrno(EIO)));
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ);
}, Eq(true)),
_)
).Times(0);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sa.sa_sigaction = handle_sigbus;
sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno);
if (setjmp(ReadSigbus::s_jmpbuf) == 0) {
atomic_signal_fence(std::memory_order::memory_order_seq_cst);
volatile char x __unused = *(volatile char*)p;
FAIL() << "shouldn't get here";
}
ASSERT_EQ(p, ReadSigbus::s_si_addr);
ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
leak(fd);
}
TEST_F(Read, o_direct)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
leak(fd);
}
TEST_F(Read, pread)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
uint64_t offset = m_maxbcachebuf;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, offset + bufsize);
expect_open(ino, 0, 1);
expect_read(ino, offset, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
leak(fd);
}
TEST_F(Read, read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
leak(fd);
}
TEST_F(Read_7_8, read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
leak(fd);
}
TEST_F(Read, cache_block)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS0 = "abcdefghijklmnop";
uint64_t ino = 42;
int fd;
ssize_t bufsize = 8;
ssize_t filesize = m_maxbcachebuf * 2;
char *contents;
char buf[bufsize];
const char *contents1 = CONTENTS0 + bufsize;
contents = new char[filesize]();
memmove(contents, CONTENTS0, strlen(CONTENTS0));
expect_lookup(RELPATH, ino, filesize);
expect_open(ino, 0, 1);
expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf,
contents);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
leak(fd);
delete[] contents;
}
TEST_F(Read, sendfile)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
size_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
int sp[2];
off_t sbytes;
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == Read::FH &&
in.body.read.offset == 0 &&
in.body.read.size == bufsize);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
out.header.len = sizeof(struct fuse_out_header) + bufsize;
memmove(out.body.bytes, CONTENTS, bufsize);
})));
ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
<< strerror(errno);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
<< strerror(errno);
ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize))
<< strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
close(sp[1]);
close(sp[0]);
leak(fd);
}
TEST_F(Read, sendfile_eio)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
int sp[2];
off_t sbytes;
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EIO)));
ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
<< strerror(errno);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
close(sp[1]);
close(sp[0]);
leak(fd);
}
TEST_P(ReadAhead, readahead) {
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd, maxcontig, clustersize;
ssize_t bufsize = 4 * m_maxbcachebuf;
ssize_t filesize = bufsize;
uint64_t len;
char *rbuf, *contents;
off_t offs;
contents = new char[filesize];
memset(contents, 'X', filesize);
rbuf = new char[bufsize]();
expect_lookup(RELPATH, ino, filesize);
expect_open(ino, 0, 1);
maxcontig = m_noclusterr ? m_maxbcachebuf :
m_maxbcachebuf + m_maxreadahead;
clustersize = MIN((unsigned long )maxcontig, m_maxphys);
for (offs = 0; offs < bufsize; offs += clustersize) {
len = std::min((size_t)clustersize, (size_t)(filesize - offs));
expect_read(ino, offs, len, len, contents + offs);
}
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(rbuf, contents, bufsize));
leak(fd);
delete[] rbuf;
delete[] contents;
}
INSTANTIATE_TEST_SUITE_P(RA, ReadAhead,
Values(tuple<bool, int>(false, 0),
tuple<bool, int>(false, 1),
tuple<bool, int>(false, 2),
tuple<bool, int>(false, 3),
tuple<bool, int>(true, 0),
tuple<bool, int>(true, 1),
tuple<bool, int>(true, 2)));
TEST_F(RofsRead, atime_during_close)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_SETATTR);
}, Eq(true)),
_)
).Times(0);
expect_flush(ino, 1, ReturnErrno(0));
expect_release(ino, FuseTest::FH);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
nap();
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
close(fd);
}
TEST_P(TimeGran, atime_during_setattr)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
ssize_t bufsize = strlen(CONTENTS);
uint8_t buf[bufsize];
uint64_t ino = 42;
const mode_t newmode = 0755;
int fd;
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
uint32_t valid = FATTR_MODE | FATTR_ATIME;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.atimensec % m_time_gran == 0);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
})));
fd = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
leak(fd);
}
INSTANTIATE_TEST_SUITE_P(TG, TimeGran, Range(0u, 10u));