extern "C" {
#include <sys/types.h>
#include <sys/extattr.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
const off_t FILESIZE = 1000;
const mode_t MODE = 0755;
const char FULLDIRPATH0[] = "mountpoint/some_dir";
const char RELDIRPATH0[] = "some_dir";
const char FULLDIRPATH1[] = "mountpoint/other_dir";
const char RELDIRPATH1[] = "other_dir";
static sem_t *blocked_semaphore;
static sem_t *signaled_semaphore;
static bool killer_should_sleep = false;
void sigusr2_handler(int __unused sig) {
if (verbosity > 1) {
printf("Signaled! thread %p\n", pthread_self());
}
}
void* killer(void* target) {
if (killer_should_sleep)
nap();
else
sem_wait(blocked_semaphore);
if (verbosity > 1)
printf("Signalling! thread %p\n", target);
pthread_kill((pthread_t)target, SIGUSR2);
if (signaled_semaphore != NULL)
sem_post(signaled_semaphore);
return(NULL);
}
class Interrupt: public FuseTest {
public:
pthread_t m_child;
Interrupt(): m_child(NULL) {};
void expect_lookup(const char *relpath, uint64_t ino)
{
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
}
void expect_mkdir(uint64_t *mkdir_unique)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).WillOnce(Invoke([=](auto in, auto &out __unused) {
*mkdir_unique = in.header.unique;
sem_post(blocked_semaphore);
}));
}
void expect_read(uint64_t ino, uint64_t *read_unique)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke([=](auto in, auto &out __unused) {
*read_unique = in.header.unique;
sem_post(blocked_semaphore);
}));
}
void expect_write(uint64_t ino, uint64_t *write_unique)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_WRITE &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke([=](auto in, auto &out __unused) {
*write_unique = in.header.unique;
sem_post(blocked_semaphore);
}));
}
void setup_interruptor(pthread_t target, bool sleep = false)
{
ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
killer_should_sleep = sleep;
ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target))
<< strerror(errno);
}
void SetUp() {
const int mprot = PROT_READ | PROT_WRITE;
const int mflags = MAP_ANON | MAP_SHARED;
signaled_semaphore = NULL;
blocked_semaphore = (sem_t*)mmap(NULL, sizeof(*blocked_semaphore),
mprot, mflags, -1, 0);
ASSERT_NE(MAP_FAILED, blocked_semaphore) << strerror(errno);
ASSERT_EQ(0, sem_init(blocked_semaphore, 1, 0)) << strerror(errno);
ASSERT_EQ(0, siginterrupt(SIGUSR2, 1));
FuseTest::SetUp();
}
void TearDown() {
struct sigaction sa;
if (m_child != NULL) {
pthread_join(m_child, NULL);
}
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(SIGUSR2, &sa, NULL);
sem_destroy(blocked_semaphore);
munmap(blocked_semaphore, sizeof(*blocked_semaphore));
FuseTest::TearDown();
}
};
class Intr: public Interrupt {};
class Nointr: public Interrupt {
void SetUp() {
m_nointr = true;
Interrupt::SetUp();
}
};
static void* mkdir0(void* arg __unused) {
ssize_t r;
r = mkdir(FULLDIRPATH0, MODE);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
static void* read1(void* arg) {
const size_t bufsize = FILESIZE;
char buf[bufsize];
int fd = (int)(intptr_t)arg;
ssize_t r;
r = read(fd, buf, bufsize);
if (r >= 0)
return 0;
else
return (void*)(intptr_t)errno;
}
TEST_F(Intr, already_complete)
{
uint64_t ino = 42;
pthread_t self;
uint64_t mkdir_unique = 0;
Sequence seq;
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.InSequence(seq)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mkdir(&mkdir_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.unique = mkdir_unique;
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
out0->body.create.entry.nodeid = ino;
out.push_back(std::move(out0));
std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
out1->header.unique = in.header.unique;
out1->header.error = -EAGAIN;
out1->header.len = sizeof(out1->header);
out.push_back(std::move(out1));
}));
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.InSequence(seq)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | MODE;
out.body.entry.nodeid = ino;
out.body.entry.attr.nlink = 2;
})));
setup_interruptor(self);
EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
}
TEST_F(Intr, enosys)
{
uint64_t ino0 = 42, ino1 = 43;;
uint64_t mkdir_unique;
pthread_t self, th0;
sem_t sem0, sem1;
void *thr0_value;
Sequence seq;
self = pthread_self();
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mkdir(&mkdir_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in, 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;
out0->header.error = -ENOSYS;
out0->header.len = sizeof(out0->header);
out.push_back(std::move(out0));
SET_OUT_HEADER_LEN(*out1, entry);
out1->body.create.entry.attr.mode = S_IFDIR | MODE;
out1->body.create.entry.nodeid = ino1;
out1->header.unique = mkdir_unique;
out.push_back(std::move(out1));
}));
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
sem_post(&sem0);
sem_wait(&sem1);
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
out0->body.create.entry.nodeid = ino0;
out0->header.unique = in.header.unique;
out.push_back(std::move(out0));
}));
setup_interruptor(self);
ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
<< strerror(errno);
sem_wait(&sem0);
pthread_kill(th0, SIGUSR1);
sem_post(&sem1);
pthread_join(th0, &thr0_value);
EXPECT_EQ(0, (intptr_t)thr0_value);
}
TEST_F(Intr, ignore)
{
uint64_t ino = 42;
pthread_t self;
uint64_t mkdir_unique;
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mkdir(&mkdir_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in __unused, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.unique = mkdir_unique;
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
out0->body.create.entry.nodeid = ino;
out.push_back(std::move(out0));
}));
setup_interruptor(self);
ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
}
TEST_F(Intr, in_kernel_restartable)
{
const char FULLPATH1[] = "mountpoint/other_file.txt";
const char RELPATH1[] = "other_file.txt";
uint64_t ino0 = 42, ino1 = 43;
int fd1;
pthread_t self, th0, th1;
sem_t sem0, sem1;
void *thr0_value, *thr1_value;
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_lookup(RELPATH1, ino1);
expect_open(ino1, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
sem_post(&sem1);
sem_wait(&sem0);
SET_OUT_HEADER_LEN(out, entry);
out.body.create.entry.attr.mode = S_IFDIR | MODE;
out.body.create.entry.nodeid = ino0;
})));
FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
fd1 = open(FULLPATH1, O_RDONLY);
ASSERT_LE(0, fd1) << strerror(errno);
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
<< strerror(errno);
sem_wait(&sem1);
ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
<< strerror(errno);
setup_interruptor(self, true);
pause();
ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
nap();
pthread_join(th1, &thr1_value);
pthread_join(th0, &thr0_value);
EXPECT_EQ(0, (intptr_t)thr1_value);
EXPECT_EQ(0, (intptr_t)thr0_value);
sem_destroy(&sem1);
sem_destroy(&sem0);
leak(fd1);
}
TEST_F(Intr, in_kernel_nonrestartable)
{
const char FULLPATH1[] = "mountpoint/other_file.txt";
const char RELPATH1[] = "other_file.txt";
const char value[] = "whatever";
ssize_t value_len = strlen(value) + 1;
uint64_t ino0 = 42, ino1 = 43;
int ns = EXTATTR_NAMESPACE_USER;
int fd1;
pthread_t self, th0;
sem_t sem0, sem1;
void *thr0_value;
ssize_t r;
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_lookup(RELPATH1, ino1);
expect_open(ino1, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
sem_post(&sem1);
sem_wait(&sem0);
SET_OUT_HEADER_LEN(out, entry);
out.body.create.entry.attr.mode = S_IFDIR | MODE;
out.body.create.entry.nodeid = ino0;
})));
fd1 = open(FULLPATH1, O_WRONLY);
ASSERT_LE(0, fd1) << strerror(errno);
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
<< strerror(errno);
sem_wait(&sem1);
setup_interruptor(self, true);
r = extattr_set_fd(fd1, ns, "foo", (const void*)value, value_len);
EXPECT_NE(0, r);
EXPECT_EQ(EINTR, errno);
ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
nap();
pthread_join(th0, &thr0_value);
EXPECT_EQ(0, (intptr_t)thr0_value);
sem_destroy(&sem1);
sem_destroy(&sem0);
leak(fd1);
}
TEST_F(Intr, in_progress)
{
pthread_t self;
uint64_t mkdir_unique;
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mkdir(&mkdir_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in __unused, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.error = -EINTR;
out0->header.unique = mkdir_unique;
out0->header.len = sizeof(out0->header);
out.push_back(std::move(out0));
}));
setup_interruptor(self);
ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
EXPECT_EQ(EINTR, errno);
}
TEST_F(Intr, in_progress_read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const size_t bufsize = 80;
char buf[bufsize];
uint64_t ino = 42;
int fd;
pthread_t self;
uint64_t read_unique;
self = pthread_self();
expect_lookup(RELPATH, ino);
expect_open(ino, 0, 1);
expect_read(ino, &read_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == read_unique);
}, Eq(true)),
_)
).WillOnce(Invoke([&](auto in __unused, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.error = -EINTR;
out0->header.unique = read_unique;
out0->header.len = sizeof(out0->header);
out.push_back(std::move(out0));
}));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
setup_interruptor(self);
ASSERT_EQ(-1, read(fd, buf, bufsize));
EXPECT_EQ(EINTR, errno);
leak(fd);
}
TEST_F(Nointr, block)
{
uint64_t ino = 42;
pthread_t self;
sem_t sem0;
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
signaled_semaphore = &sem0;
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
sem_post(blocked_semaphore);
sem_wait(signaled_semaphore);
nap();
SET_OUT_HEADER_LEN(out, entry);
out.body.create.entry.attr.mode = S_IFDIR | MODE;
out.body.create.entry.nodeid = ino;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT);
}, Eq(true)),
_)
).Times(0);
setup_interruptor(self);
ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
sem_destroy(&sem0);
}
TEST_F(Intr, priority)
{
Sequence seq;
uint64_t ino1 = 43;
uint64_t mkdir_unique;
pthread_t th0;
sem_t sem0, sem1;
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
mkdir_unique = in.header.unique;
sem_post(&sem1);
sem_wait(&sem0);
out.header.error = -EINTR;
out.header.unique = mkdir_unique;
out.header.len = sizeof(out.header);
})));
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke(ReturnErrno(EAGAIN)));
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_MKDIR);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.create.entry.attr.mode = S_IFDIR | MODE;
out.body.create.entry.nodeid = ino1;
})));
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
<< strerror(errno);
signaled_semaphore = &sem0;
sem_wait(&sem1);
setup_interruptor(th0, true);
ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
pthread_join(th0, NULL);
sem_destroy(&sem1);
sem_destroy(&sem0);
}
TEST_F(Intr, too_soon)
{
Sequence seq;
pthread_t self;
uint64_t mkdir_unique;
self = pthread_self();
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mkdir(&mkdir_unique);
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke(ReturnErrno(EAGAIN)));
EXPECT_CALL(*m_mock, process(
ResultOf([&](auto in) {
return (in.header.opcode == FUSE_INTERRUPT &&
in.body.interrupt.unique == mkdir_unique);
}, Eq(true)),
_)
).InSequence(seq)
.WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.error = -EINTR;
out0->header.unique = mkdir_unique;
out0->header.len = sizeof(out0->header);
out.push_back(std::move(out0));
}));
setup_interruptor(self);
ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
EXPECT_EQ(EINTR, errno);
}