extern "C" {
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
enum Mutator {
VOP_ALLOCATE,
VOP_COPY_FILE_RANGE,
VOP_SETATTR,
VOP_WRITE,
};
enum Mutator writer_from_str(const char* s) {
if (0 == strcmp("VOP_ALLOCATE", s))
return VOP_ALLOCATE;
else if (0 == strcmp("VOP_COPY_FILE_RANGE", s))
return VOP_COPY_FILE_RANGE;
else if (0 == strcmp("VOP_SETATTR", s))
return VOP_SETATTR;
else
return VOP_WRITE;
}
uint32_t fuse_op_from_mutator(enum Mutator mutator) {
switch(mutator) {
case VOP_ALLOCATE:
return(FUSE_FALLOCATE);
case VOP_COPY_FILE_RANGE:
return(FUSE_COPY_FILE_RANGE);
case VOP_SETATTR:
return(FUSE_SETATTR);
case VOP_WRITE:
return(FUSE_WRITE);
}
}
class LastLocalModify: public FuseTest, public WithParamInterface<const char*> {
public:
virtual void SetUp() {
m_init_flags = FUSE_EXPORT_SUPPORT;
FuseTest::SetUp();
}
};
static void* allocate_th(void* arg) {
int fd;
ssize_t r;
sem_t *sem = (sem_t*) arg;
if (sem)
sem_wait(sem);
fd = open("mountpoint/some_file.txt", O_RDWR);
if (fd < 0)
return (void*)(intptr_t)errno;
r = posix_fallocate(fd, 0, 15);
LastLocalModify::leak(fd);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
static void* copy_file_range_th(void* arg) {
ssize_t r;
int fd;
sem_t *sem = (sem_t*) arg;
off_t off_in = 0;
off_t off_out = 10;
ssize_t len = 5;
if (sem)
sem_wait(sem);
fd = open("mountpoint/some_file.txt", O_RDWR);
if (fd < 0)
return (void*)(intptr_t)errno;
r = copy_file_range(fd, &off_in, fd, &off_out, len, 0);
if (r >= 0) {
LastLocalModify::leak(fd);
return 0;
} else
return (void*)(intptr_t)errno;
}
static void* setattr_th(void* arg) {
int fd;
ssize_t r;
sem_t *sem = (sem_t*) arg;
if (sem)
sem_wait(sem);
fd = open("mountpoint/some_file.txt", O_RDWR);
if (fd < 0)
return (void*)(intptr_t)errno;
r = ftruncate(fd, 15);
LastLocalModify::leak(fd);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
static void* write_th(void* arg) {
ssize_t r;
int fd;
sem_t *sem = (sem_t*) arg;
const char BUF[] = "abcdefghijklmn";
if (sem)
sem_wait(sem);
fd = open("mountpoint/some_file.txt", O_RDWR | O_DIRECT);
if (fd < 0)
return (void*)(intptr_t)errno;
r = write(fd, BUF, sizeof(BUF));
if (r >= 0) {
LastLocalModify::leak(fd);
return 0;
} else
return (void*)(intptr_t)errno;
}
TEST_P(LastLocalModify, lookup)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
Sequence seq;
uint64_t ino = 3;
uint64_t mutator_unique;
const uint64_t oldsize = 10;
const uint64_t newsize = 15;
pthread_t th0;
void *thr0_value;
struct stat sb;
static sem_t sem;
Mutator mutator;
uint32_t mutator_op;
size_t mutator_size;
mutator = writer_from_str(GetParam());
mutator_op = fuse_op_from_mutator(mutator);
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.InSequence(seq)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
out.body.entry.attr_valid_nsec = NAP_NS / 2;
out.body.entry.attr.ino = ino;
out.body.entry.attr.mode = S_IFREG | 0644;
})));
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == mutator_op &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in, auto &out __unused) {
mutator_unique = in.header.unique;
switch(mutator) {
case VOP_WRITE:
mutator_size = in.body.write.size;
break;
case VOP_COPY_FILE_RANGE:
mutator_size = in.body.copy_file_range.len;
break;
default:
break;
}
sem_post(&sem);
}));
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.InSequence(seq)
.WillOnce(Invoke([&](auto in __unused, auto& out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
out0->header.unique = in.header.unique;
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.entry.attr.mode = S_IFREG | 0644;
out0->body.entry.nodeid = ino;
out0->body.entry.attr.ino = ino;
out0->body.entry.entry_valid = UINT64_MAX;
out0->body.entry.attr_valid = UINT64_MAX;
out0->body.entry.attr.size = oldsize;
out.push_back(std::move(out0));
out1->header.unique = mutator_unique;
switch(mutator) {
case VOP_ALLOCATE:
out1->header.error = 0;
out1->header.len = sizeof(out1->header);
break;
case VOP_COPY_FILE_RANGE:
SET_OUT_HEADER_LEN(*out1, write);
out1->body.write.size = mutator_size;
break;
case VOP_SETATTR:
SET_OUT_HEADER_LEN(*out1, attr);
out1->body.attr.attr.ino = ino;
out1->body.attr.attr.mode = S_IFREG | 0644;
out1->body.attr.attr.size = newsize;
out1->body.attr.attr_valid = UINT64_MAX;
break;
case VOP_WRITE:
SET_OUT_HEADER_LEN(*out1, write);
out1->body.write.size = mutator_size;
break;
}
out.push_back(std::move(out1));
}));
switch(mutator) {
case VOP_ALLOCATE:
ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
NULL)) << strerror(errno);
break;
case VOP_COPY_FILE_RANGE:
ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
NULL)) << strerror(errno);
break;
case VOP_SETATTR:
ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL))
<< strerror(errno);
break;
case VOP_WRITE:
ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL))
<< strerror(errno);
break;
}
sem_wait(&sem);
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
ASSERT_EQ((off_t)newsize, sb.st_size);
pthread_join(th0, &thr0_value);
EXPECT_EQ(0, (intptr_t)thr0_value);
}
TEST_P(LastLocalModify, vfs_vget)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
Sequence seq;
uint64_t ino = 3;
uint64_t lookup_unique;
const uint64_t oldsize = 10;
const uint64_t newsize = 15;
pthread_t th0;
void *thr0_value;
struct stat sb;
static sem_t sem;
fhandle_t fhp;
Mutator mutator;
uint32_t mutator_op;
if (geteuid() != 0)
GTEST_SKIP() << "This test requires a privileged user";
mutator = writer_from_str(GetParam());
mutator_op = fuse_op_from_mutator(mutator);
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.Times(1)
.InSequence(seq)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
{
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
out.body.entry.attr_valid_nsec = NAP_NS / 2;
out.body.entry.attr.ino = ino;
out.body.entry.attr.mode = S_IFREG | 0644;
})));
EXPECT_LOOKUP(ino, ".")
.InSequence(seq)
.WillOnce(Invoke([&](auto in, auto &out __unused) {
lookup_unique = in.header.unique;
sem_post(&sem);
}));
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.Times(1)
.InSequence(seq)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
{
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
out.body.entry.attr_valid_nsec = NAP_NS / 2;
out.body.entry.attr.ino = ino;
out.body.entry.attr.mode = S_IFREG | 0644;
})));
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == mutator_op &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in __unused, auto& out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
out0->header.unique = lookup_unique;
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.entry.attr.mode = S_IFREG | 0644;
out0->body.entry.nodeid = ino;
out0->body.entry.attr.ino = ino;
out0->body.entry.entry_valid = UINT64_MAX;
out0->body.entry.attr_valid = UINT64_MAX;
out0->body.entry.attr.size = oldsize;
out.push_back(std::move(out0));
out1->header.unique = in.header.unique;
switch(mutator) {
case VOP_ALLOCATE:
out1->header.error = 0;
out1->header.len = sizeof(out1->header);
break;
case VOP_COPY_FILE_RANGE:
SET_OUT_HEADER_LEN(*out1, write);
out1->body.write.size = in.body.copy_file_range.len;
break;
case VOP_SETATTR:
SET_OUT_HEADER_LEN(*out1, attr);
out1->body.attr.attr.ino = ino;
out1->body.attr.attr.mode = S_IFREG | 0644;
out1->body.attr.attr.size = newsize;
out1->body.attr.attr_valid = UINT64_MAX;
break;
case VOP_WRITE:
SET_OUT_HEADER_LEN(*out1, write);
out1->body.write.size = in.body.write.size;
break;
}
out.push_back(std::move(out1));
}));
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
switch(mutator) {
case VOP_ALLOCATE:
ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
(void*)&sem)) << strerror(errno);
break;
case VOP_COPY_FILE_RANGE:
ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
(void*)&sem)) << strerror(errno);
break;
case VOP_SETATTR:
ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th,
(void*)&sem)) << strerror(errno);
break;
case VOP_WRITE:
ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem))
<< strerror(errno);
break;
}
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
ASSERT_EQ((off_t)newsize, sb.st_size);
pthread_join(th0, &thr0_value);
EXPECT_EQ(0, (intptr_t)thr0_value);
}
INSTANTIATE_TEST_SUITE_P(LLM, LastLocalModify,
Values(
"VOP_ALLOCATE",
"VOP_COPY_FILE_RANGE",
"VOP_SETATTR",
"VOP_WRITE"
)
);