extern "C" {
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <fcntl.h>
#include <libutil.h>
#include <mntopts.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
}
#include <cinttypes>
#include <gtest/gtest.h>
#include "mockfs.hh"
using namespace testing;
int verbosity = 0;
const char* opcode2opname(uint32_t opcode)
{
const char* table[] = {
"Unknown (opcode 0)",
"LOOKUP",
"FORGET",
"GETATTR",
"SETATTR",
"READLINK",
"SYMLINK",
"Unknown (opcode 7)",
"MKNOD",
"MKDIR",
"UNLINK",
"RMDIR",
"RENAME",
"LINK",
"OPEN",
"READ",
"WRITE",
"STATFS",
"RELEASE",
"Unknown (opcode 19)",
"FSYNC",
"SETXATTR",
"GETXATTR",
"LISTXATTR",
"REMOVEXATTR",
"FLUSH",
"INIT",
"OPENDIR",
"READDIR",
"RELEASEDIR",
"FSYNCDIR",
"GETLK",
"SETLK",
"SETLKW",
"ACCESS",
"CREATE",
"INTERRUPT",
"BMAP",
"DESTROY",
"IOCTL",
"POLL",
"NOTIFY_REPLY",
"BATCH_FORGET",
"FALLOCATE",
"READDIRPLUS",
"RENAME2",
"LSEEK",
"COPY_FILE_RANGE",
};
if (opcode >= nitems(table))
return ("Unknown (opcode > max)");
else
return (table[opcode]);
}
ProcessMockerT
ReturnErrno(int error)
{
return([=](auto in, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.unique = in.header.unique;
out0->header.error = -error;
out0->header.len = sizeof(out0->header);
out.push_back(std::move(out0));
});
}
ProcessMockerT
ReturnNegativeCache(const struct timespec *entry_valid)
{
return([=](auto in, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->body.entry.nodeid = 0;
out0->header.unique = in.header.unique;
out0->header.error = 0;
out0->body.entry.entry_valid = entry_valid->tv_sec;
out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
SET_OUT_HEADER_LEN(*out0, entry);
out.push_back(std::move(out0));
});
}
ProcessMockerT
ReturnImmediate(std::function<void(const mockfs_buf_in& in,
struct mockfs_buf_out &out)> f)
{
return([=](auto& in, auto &out) {
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.unique = in.header.unique;
f(in, *out0);
out.push_back(std::move(out0));
});
}
void sigint_handler(int __unused sig) {
}
void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen)
{
printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode),
in.header.nodeid);
if (verbosity > 1) {
printf(" uid=%5u gid=%5u pid=%5u unique=%" PRIu64 " len=%u"
" buflen=%zd",
in.header.uid, in.header.gid, in.header.pid,
in.header.unique, in.header.len, buflen);
}
switch (in.header.opcode) {
const char *name, *value;
case FUSE_ACCESS:
printf(" mask=%#x", in.body.access.mask);
break;
case FUSE_BMAP:
printf(" block=%" PRIx64 " blocksize=%#x",
in.body.bmap.block, in.body.bmap.blocksize);
break;
case FUSE_COPY_FILE_RANGE:
printf(" off_in=%" PRIu64 " ino_out=%" PRIu64
" off_out=%" PRIu64 " size=%" PRIu64,
in.body.copy_file_range.off_in,
in.body.copy_file_range.nodeid_out,
in.body.copy_file_range.off_out,
in.body.copy_file_range.len);
if (verbosity > 1)
printf(" fh_in=%" PRIu64 " fh_out=%" PRIu64
" flags=%" PRIx64,
in.body.copy_file_range.fh_in,
in.body.copy_file_range.fh_out,
in.body.copy_file_range.flags);
break;
case FUSE_CREATE:
if (m_kernel_minor_version >= 12)
name = (const char*)in.body.bytes +
sizeof(fuse_create_in);
else
name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
printf(" flags=%#x name=%s",
in.body.open.flags, name);
break;
case FUSE_FALLOCATE:
printf(" fh=%#" PRIx64 " offset=%" PRIu64
" length=%" PRIx64 " mode=%#x",
in.body.fallocate.fh,
in.body.fallocate.offset,
in.body.fallocate.length,
in.body.fallocate.mode);
break;
case FUSE_FLUSH:
printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64,
in.body.flush.fh,
in.body.flush.lock_owner);
break;
case FUSE_FORGET:
printf(" nlookup=%" PRIu64, in.body.forget.nlookup);
break;
case FUSE_FSYNC:
printf(" flags=%#x", in.body.fsync.fsync_flags);
break;
case FUSE_FSYNCDIR:
printf(" flags=%#x", in.body.fsyncdir.fsync_flags);
break;
case FUSE_GETLK:
printf(" fh=%#" PRIx64
" type=%u pid=%u",
in.body.getlk.fh,
in.body.getlk.lk.type,
in.body.getlk.lk.pid);
if (verbosity >= 2) {
printf(" range=[%" PRIi64 ":%" PRIi64 "]",
in.body.getlk.lk.start,
in.body.getlk.lk.end);
}
break;
case FUSE_INTERRUPT:
printf(" unique=%" PRIu64, in.body.interrupt.unique);
break;
case FUSE_LINK:
printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid);
break;
case FUSE_LISTXATTR:
printf(" size=%" PRIu32, in.body.listxattr.size);
break;
case FUSE_LOOKUP:
printf(" %s", in.body.lookup);
break;
case FUSE_LSEEK:
switch (in.body.lseek.whence) {
case SEEK_HOLE:
printf(" SEEK_HOLE offset=%jd",
in.body.lseek.offset);
break;
case SEEK_DATA:
printf(" SEEK_DATA offset=%jd",
in.body.lseek.offset);
break;
default:
printf(" whence=%u offset=%jd",
in.body.lseek.whence, in.body.lseek.offset);
break;
}
break;
case FUSE_MKDIR:
name = (const char*)in.body.bytes +
sizeof(fuse_mkdir_in);
printf(" name=%s mode=%#o umask=%#o", name,
in.body.mkdir.mode, in.body.mkdir.umask);
break;
case FUSE_MKNOD:
if (m_kernel_minor_version >= 12)
name = (const char*)in.body.bytes +
sizeof(fuse_mknod_in);
else
name = (const char*)in.body.bytes +
FUSE_COMPAT_MKNOD_IN_SIZE;
printf(" mode=%#o rdev=%x umask=%#o name=%s",
in.body.mknod.mode, in.body.mknod.rdev,
in.body.mknod.umask, name);
break;
case FUSE_OPEN:
printf(" flags=%#x", in.body.open.flags);
break;
case FUSE_OPENDIR:
printf(" flags=%#x", in.body.opendir.flags);
break;
case FUSE_READ:
printf(" offset=%" PRIu64 " size=%u",
in.body.read.offset,
in.body.read.size);
if (verbosity > 1)
printf(" flags=%#x", in.body.read.flags);
break;
case FUSE_READDIR:
printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
in.body.readdir.fh, in.body.readdir.offset,
in.body.readdir.size);
break;
case FUSE_RELEASE:
printf(" fh=%#" PRIx64 " flags=%#x lock_owner=%" PRIu64,
in.body.release.fh,
in.body.release.flags,
in.body.release.lock_owner);
break;
case FUSE_RENAME:
{
const char *src = (const char*)in.body.bytes +
sizeof(fuse_rename_in);
const char *dst = src + strlen(src) + 1;
printf(" src=%s newdir=%" PRIu64 " dst=%s",
src, in.body.rename.newdir, dst);
}
break;
case FUSE_SETATTR:
if (verbosity <= 1) {
printf(" valid=%#x", in.body.setattr.valid);
break;
}
if (in.body.setattr.valid & FATTR_MODE)
printf(" mode=%#o", in.body.setattr.mode);
if (in.body.setattr.valid & FATTR_UID)
printf(" uid=%u", in.body.setattr.uid);
if (in.body.setattr.valid & FATTR_GID)
printf(" gid=%u", in.body.setattr.gid);
if (in.body.setattr.valid & FATTR_SIZE)
printf(" size=%" PRIu64, in.body.setattr.size);
if (in.body.setattr.valid & FATTR_ATIME)
printf(" atime=%" PRIu64 ".%u",
in.body.setattr.atime,
in.body.setattr.atimensec);
if (in.body.setattr.valid & FATTR_MTIME)
printf(" mtime=%" PRIu64 ".%u",
in.body.setattr.mtime,
in.body.setattr.mtimensec);
if (in.body.setattr.valid & FATTR_FH)
printf(" fh=%" PRIu64 "", in.body.setattr.fh);
break;
case FUSE_SETLK:
printf(" fh=%#" PRIx64 " owner=%" PRIu64
" type=%u pid=%u",
in.body.setlk.fh, in.body.setlk.owner,
in.body.setlk.lk.type,
in.body.setlk.lk.pid);
if (verbosity >= 2) {
printf(" range=[%" PRIi64 ":%" PRIi64 "]",
in.body.setlk.lk.start,
in.body.setlk.lk.end);
}
break;
case FUSE_SETXATTR:
name = (const char*)in.body.bytes +
sizeof(fuse_setxattr_in);
value = name + strlen(name) + 1;
printf(" %s=%s", name, value);
break;
case FUSE_WRITE:
printf(" fh=%#" PRIx64 " offset=%" PRIu64
" size=%u write_flags=%u",
in.body.write.fh,
in.body.write.offset, in.body.write.size,
in.body.write.write_flags);
if (verbosity > 1)
printf(" flags=%#x", in.body.write.flags);
break;
default:
break;
}
printf("\n");
}
void MockFS::debug_response(const mockfs_buf_out &out) {
const char *name;
if (verbosity == 0)
return;
switch (out.header.error) {
case FUSE_NOTIFY_INVAL_ENTRY:
name = (const char*)out.body.bytes +
sizeof(fuse_notify_inval_entry_out);
printf("<- INVAL_ENTRY parent=%" PRIu64 " %s\n",
out.body.inval_entry.parent, name);
break;
case FUSE_NOTIFY_INVAL_INODE:
printf("<- INVAL_INODE ino=%" PRIu64 " off=%" PRIi64
" len=%" PRIi64 "\n",
out.body.inval_inode.ino,
out.body.inval_inode.off,
out.body.inval_inode.len);
break;
case FUSE_NOTIFY_STORE:
printf("<- STORE ino=%" PRIu64 " off=%" PRIu64
" size=%" PRIu32 "\n",
out.body.store.nodeid,
out.body.store.offset,
out.body.store.size);
break;
default:
break;
}
}
MockFS::MockFS(int max_read, int max_readahead, bool allow_other,
bool default_permissions,
bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
uint32_t kernel_minor_version, uint32_t max_write, bool async,
bool noclusterr, unsigned time_gran, bool nointr, bool noatime,
const char *fsname, const char *subtype, bool no_auto_init)
: m_daemon_id(NULL),
m_kernel_minor_version(kernel_minor_version),
m_kq(pm == KQ ? kqueue() : -1),
m_maxread(max_read),
m_maxreadahead(max_readahead),
m_pid(getpid()),
m_uniques(new std::unordered_set<uint64_t>),
m_pm(pm),
m_time_gran(time_gran),
m_child_pid(-1),
m_maxwrite(MIN(max_write, max_max_write)),
m_nready(-1),
m_quit(false)
{
struct sigaction sa;
struct iovec *iov = NULL;
int iovlen = 0;
char fdstr[15];
const bool trueval = true;
if (mkdir("mountpoint" , 0755) && errno != EEXIST)
throw(std::system_error(errno, std::system_category(),
"Couldn't make mountpoint directory"));
switch (m_pm) {
case BLOCKING:
m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
break;
default:
m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
break;
}
if (m_fuse_fd < 0)
throw(std::system_error(errno, std::system_category(),
"Couldn't open /dev/fuse"));
build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
build_iovec(&iov, &iovlen, "fspath",
__DECONST(void *, "mountpoint"), -1);
build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
sprintf(fdstr, "%d", m_fuse_fd);
build_iovec(&iov, &iovlen, "fd", fdstr, -1);
if (m_maxread > 0) {
char val[12];
snprintf(val, sizeof(val), "%d", m_maxread);
build_iovec(&iov, &iovlen, "max_read=", &val, -1);
}
if (allow_other) {
build_iovec(&iov, &iovlen, "allow_other",
__DECONST(void*, &trueval), sizeof(bool));
}
if (default_permissions) {
build_iovec(&iov, &iovlen, "default_permissions",
__DECONST(void*, &trueval), sizeof(bool));
}
if (push_symlinks_in) {
build_iovec(&iov, &iovlen, "push_symlinks_in",
__DECONST(void*, &trueval), sizeof(bool));
}
if (ro) {
build_iovec(&iov, &iovlen, "ro",
__DECONST(void*, &trueval), sizeof(bool));
}
if (async) {
build_iovec(&iov, &iovlen, "async", __DECONST(void*, &trueval),
sizeof(bool));
}
if (noatime) {
build_iovec(&iov, &iovlen, "noatime",
__DECONST(void*, &trueval), sizeof(bool));
}
if (noclusterr) {
build_iovec(&iov, &iovlen, "noclusterr",
__DECONST(void*, &trueval), sizeof(bool));
}
if (nointr) {
build_iovec(&iov, &iovlen, "nointr",
__DECONST(void*, &trueval), sizeof(bool));
} else {
build_iovec(&iov, &iovlen, "intr",
__DECONST(void*, &trueval), sizeof(bool));
}
if (*fsname) {
build_iovec(&iov, &iovlen, "fsname=",
__DECONST(void*, fsname), -1);
}
if (*subtype) {
build_iovec(&iov, &iovlen, "subtype=",
__DECONST(void*, subtype), -1);
}
if (nmount(iov, iovlen, 0))
throw(std::system_error(errno, std::system_category(),
"Couldn't mount filesystem"));
free_iovec(&iov, &iovlen);
ON_CALL(*this, process(_, _))
.WillByDefault(Invoke(this, &MockFS::process_default));
if (!no_auto_init)
init(flags);
bzero(&sa, sizeof(sa));
sa.sa_handler = sigint_handler;
sa.sa_flags = 0;
if (0 != sigaction(SIGUSR1, &sa, NULL))
throw(std::system_error(errno, std::system_category(),
"Couldn't handle SIGUSR1"));
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() {
kill_daemon();
join_daemon();
::unmount("mountpoint", MNT_FORCE);
rmdir("mountpoint");
if (m_kq >= 0)
close(m_kq);
}
void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) {
uint32_t inlen = in.header.len;
size_t fih = sizeof(in.header);
switch (in.header.opcode) {
case FUSE_LOOKUP:
case FUSE_RMDIR:
case FUSE_SYMLINK:
case FUSE_UNLINK:
EXPECT_GT(inlen, fih) << "Missing request filename";
break;
case FUSE_FORGET:
EXPECT_EQ(inlen, fih + sizeof(in.body.forget));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_GETATTR:
EXPECT_EQ(inlen, fih + sizeof(in.body.getattr));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_SETATTR:
EXPECT_EQ(inlen, fih + sizeof(in.body.setattr));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_READLINK:
EXPECT_EQ(inlen, fih) << "Unexpected request body";
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_MKNOD:
{
size_t s;
if (m_kernel_minor_version >= 12)
s = sizeof(in.body.mknod);
else
s = FUSE_COMPAT_MKNOD_IN_SIZE;
EXPECT_GE(inlen, fih + s) << "Missing request body";
EXPECT_GT(inlen, fih + s) << "Missing request filename";
break;
}
case FUSE_MKDIR:
EXPECT_GE(inlen, fih + sizeof(in.body.mkdir)) <<
"Missing request body";
EXPECT_GT(inlen, fih + sizeof(in.body.mkdir)) <<
"Missing request filename";
break;
case FUSE_RENAME:
EXPECT_GE(inlen, fih + sizeof(in.body.rename)) <<
"Missing request body";
EXPECT_GT(inlen, fih + sizeof(in.body.rename)) <<
"Missing request filename";
break;
case FUSE_LINK:
EXPECT_GE(inlen, fih + sizeof(in.body.link)) <<
"Missing request body";
EXPECT_GT(inlen, fih + sizeof(in.body.link)) <<
"Missing request filename";
break;
case FUSE_OPEN:
EXPECT_EQ(inlen, fih + sizeof(in.body.open));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_READ:
EXPECT_EQ(inlen, fih + sizeof(in.body.read));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_WRITE:
{
size_t s;
if (m_kernel_minor_version >= 9)
s = sizeof(in.body.write);
else
s = FUSE_COMPAT_WRITE_IN_SIZE;
EXPECT_GE(inlen, fih + s) << "Missing request body";
EXPECT_EQ((size_t)buflen, fih + s + in.body.write.size);
break;
}
case FUSE_DESTROY:
case FUSE_STATFS:
EXPECT_EQ(inlen, fih);
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_RELEASE:
EXPECT_EQ(inlen, fih + sizeof(in.body.release));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_FSYNC:
case FUSE_FSYNCDIR:
EXPECT_EQ(inlen, fih + sizeof(in.body.fsync));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_SETXATTR:
EXPECT_GE(inlen, fih + sizeof(in.body.setxattr)) <<
"Missing request body";
EXPECT_GT(inlen, fih + sizeof(in.body.setxattr)) <<
"Missing request attribute name";
break;
case FUSE_GETXATTR:
EXPECT_GE(inlen, fih + sizeof(in.body.getxattr)) <<
"Missing request body";
EXPECT_GT(inlen, fih + sizeof(in.body.getxattr)) <<
"Missing request attribute name";
break;
case FUSE_LISTXATTR:
EXPECT_EQ(inlen, fih + sizeof(in.body.listxattr));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_REMOVEXATTR:
EXPECT_GT(inlen, fih) << "Missing request attribute name";
break;
case FUSE_FLUSH:
EXPECT_EQ(inlen, fih + sizeof(in.body.flush));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_INIT:
EXPECT_EQ(inlen, fih + sizeof(in.body.init));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_OPENDIR:
EXPECT_EQ(inlen, fih + sizeof(in.body.opendir));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_READDIR:
EXPECT_EQ(inlen, fih + sizeof(in.body.readdir));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_RELEASEDIR:
EXPECT_EQ(inlen, fih + sizeof(in.body.releasedir));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_GETLK:
EXPECT_EQ(inlen, fih + sizeof(in.body.getlk));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_SETLK:
case FUSE_SETLKW:
EXPECT_EQ(inlen, fih + sizeof(in.body.setlk));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_ACCESS:
EXPECT_EQ(inlen, fih + sizeof(in.body.access));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_CREATE:
EXPECT_GE(inlen, fih + sizeof(in.body.create)) <<
"Missing request body";
EXPECT_GT(inlen, fih + sizeof(in.body.create)) <<
"Missing request filename";
break;
case FUSE_INTERRUPT:
EXPECT_EQ(inlen, fih + sizeof(in.body.interrupt));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_FALLOCATE:
EXPECT_EQ(inlen, fih + sizeof(in.body.fallocate));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_BMAP:
EXPECT_EQ(inlen, fih + sizeof(in.body.bmap));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_LSEEK:
EXPECT_EQ(inlen, fih + sizeof(in.body.lseek));
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_COPY_FILE_RANGE:
EXPECT_EQ(inlen, fih + sizeof(in.body.copy_file_range));
EXPECT_EQ(0ul, in.body.copy_file_range.flags);
EXPECT_EQ((size_t)buflen, inlen);
break;
case FUSE_NOTIFY_REPLY:
case FUSE_BATCH_FORGET:
case FUSE_IOCTL:
case FUSE_POLL:
case FUSE_READDIRPLUS:
FAIL() << "Unsupported opcode?";
default:
FAIL() << "Unknown opcode " << in.header.opcode;
}
if (m_uniques->find(in.header.unique) != m_uniques->end())
FAIL() << "Non-unique \"unique\" value";
m_uniques->insert(in.header.unique);
}
void MockFS::init(uint32_t flags) {
ssize_t buflen;
std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
read_request(*in, buflen);
if (verbosity > 0)
debug_request(*in, buflen);
audit_request(*in, buflen);
ASSERT_EQ(FUSE_INIT, in->header.opcode);
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.init.major = FUSE_KERNEL_VERSION;
out->body.init.minor = m_kernel_minor_version;;
out->body.init.flags = in->body.init.flags & flags;
out->body.init.max_write = m_maxwrite;
out->body.init.max_readahead = m_maxreadahead;
if (m_kernel_minor_version < 23) {
SET_OUT_HEADER_LEN(*out, init_7_22);
} else {
out->body.init.time_gran = m_time_gran;
SET_OUT_HEADER_LEN(*out, init);
}
write(m_fuse_fd, out.get(), out->header.len);
}
void MockFS::kill_daemon() {
m_quit = true;
if (m_daemon_id != NULL)
pthread_kill(m_daemon_id, SIGUSR1);
close(m_fuse_fd);
m_fuse_fd = -1;
}
void MockFS::join_daemon() {
if (m_daemon_id != NULL) {
pthread_join(m_daemon_id, NULL);
m_daemon_id = NULL;
}
}
void MockFS::loop() {
std::vector<std::unique_ptr<mockfs_buf_out>> out;
std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
ASSERT_TRUE(in != NULL);
while (!m_quit) {
ssize_t buflen;
bzero(in.get(), sizeof(*in));
read_request(*in, buflen);
if (m_quit)
break;
if (verbosity > 0)
debug_request(*in, buflen);
audit_request(*in, buflen);
if (pid_ok((pid_t)in->header.pid)) {
process(*in, out);
} else {
if (verbosity > 1)
printf("\tREJECTED (wrong pid %d)\n",
in->header.pid);
process_default(*in, out);
}
for (auto &it: out)
write_response(*it);
out.clear();
}
}
int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
{
std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
out->header.unique = 0;
out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
out->body.inval_entry.parent = parent;
out->body.inval_entry.namelen = namelen;
strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
namelen;
debug_response(*out);
write_response(*out);
return 0;
}
int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len)
{
std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
out->header.unique = 0;
out->header.error = FUSE_NOTIFY_INVAL_INODE;
out->body.inval_inode.ino = ino;
out->body.inval_inode.off = off;
out->body.inval_inode.len = len;
out->header.len = sizeof(out->header) + sizeof(out->body.inval_inode);
debug_response(*out);
write_response(*out);
return 0;
}
int MockFS::notify_store(ino_t ino, off_t off, const void* data, ssize_t size)
{
std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
out->header.unique = 0;
out->header.error = FUSE_NOTIFY_STORE;
out->body.store.nodeid = ino;
out->body.store.offset = off;
out->body.store.size = size;
bcopy(data, (char*)&out->body.bytes + sizeof(out->body.store), size);
out->header.len = sizeof(out->header) + sizeof(out->body.store) + size;
debug_response(*out);
write_response(*out);
return 0;
}
bool MockFS::pid_ok(pid_t pid) {
if (pid == m_pid) {
return (true);
} else if (pid == m_child_pid) {
return (true);
} else {
struct kinfo_proc *ki;
bool ok = false;
ki = kinfo_getproc(pid);
if (ki == NULL)
return (false);
if (0 == strncmp("aiod", ki->ki_comm, 4))
ok = true;
free(ki);
return (ok);
}
}
void MockFS::process_default(const mockfs_buf_in& in,
std::vector<std::unique_ptr<mockfs_buf_out>> &out)
{
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
out0->header.unique = in.header.unique;
out0->header.error = -EOPNOTSUPP;
out0->header.len = sizeof(out0->header);
out.push_back(std::move(out0));
}
void MockFS::read_request(mockfs_buf_in &in, ssize_t &res) {
int nready = 0;
fd_set readfds;
pollfd fds[1];
struct kevent changes[1];
struct kevent events[1];
struct timespec timeout_ts;
struct timeval timeout_tv;
const int timeout_ms = 999;
int timeout_int, nfds;
int fuse_fd;
switch (m_pm) {
case BLOCKING:
break;
case KQ:
timeout_ts.tv_sec = 0;
timeout_ts.tv_nsec = timeout_ms * 1'000'000;
while (nready == 0) {
EV_SET(&changes[0], m_fuse_fd, EVFILT_READ,
EV_ADD | EV_ONESHOT, 0, 0, 0);
nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
&timeout_ts);
if (m_quit)
return;
}
ASSERT_LE(0, nready) << strerror(errno);
ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
if (events[0].flags & EV_ERROR)
FAIL() << strerror(events[0].data);
else if (events[0].flags & EV_EOF)
FAIL() << strerror(events[0].fflags);
m_nready = events[0].data;
break;
case POLL:
timeout_int = timeout_ms;
fds[0].fd = m_fuse_fd;
fds[0].events = POLLIN;
while (nready == 0) {
nready = poll(fds, 1, timeout_int);
if (m_quit)
return;
}
ASSERT_LE(0, nready) << strerror(errno);
ASSERT_TRUE(fds[0].revents & POLLIN);
break;
case SELECT:
fuse_fd = m_fuse_fd;
if (fuse_fd < 0)
break;
timeout_tv.tv_sec = 0;
timeout_tv.tv_usec = timeout_ms * 1'000;
nfds = fuse_fd + 1;
while (nready == 0) {
FD_ZERO(&readfds);
FD_SET(fuse_fd, &readfds);
nready = select(nfds, &readfds, NULL, NULL,
&timeout_tv);
if (m_quit)
return;
}
ASSERT_LE(0, nready) << strerror(errno);
ASSERT_TRUE(FD_ISSET(fuse_fd, &readfds));
break;
default:
FAIL() << "not yet implemented";
}
res = read(m_fuse_fd, &in, sizeof(in));
if (res < 0 && !m_quit) {
m_quit = true;
FAIL() << "read: " << strerror(errno);
}
ASSERT_TRUE(res >= static_cast<ssize_t>(sizeof(in.header)) || m_quit);
ASSERT_TRUE(res == static_cast<ssize_t>(in.header.len) || m_quit);
}
void MockFS::write_response(const mockfs_buf_out &out) {
fd_set writefds;
pollfd fds[1];
struct kevent changes[1];
struct kevent events[1];
int nready, nfds;
ssize_t r;
switch (m_pm) {
case BLOCKING:
break;
case KQ:
EV_SET(&changes[0], m_fuse_fd, EVFILT_WRITE,
EV_ADD | EV_ONESHOT, 0, 0, 0);
nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
NULL);
ASSERT_LE(0, nready) << strerror(errno);
ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
if (events[0].flags & EV_ERROR)
FAIL() << strerror(events[0].data);
else if (events[0].flags & EV_EOF)
FAIL() << strerror(events[0].fflags);
m_nready = events[0].data;
break;
case POLL:
fds[0].fd = m_fuse_fd;
fds[0].events = POLLOUT;
nready = poll(fds, 1, INFTIM);
ASSERT_LE(0, nready) << strerror(errno);
ASSERT_EQ(1, nready) << "NULL timeout expired?";
ASSERT_TRUE(fds[0].revents & POLLOUT);
break;
case SELECT:
FD_ZERO(&writefds);
FD_SET(m_fuse_fd, &writefds);
nfds = m_fuse_fd + 1;
nready = select(nfds, NULL, &writefds, NULL, NULL);
ASSERT_LE(0, nready) << strerror(errno);
ASSERT_EQ(1, nready) << "NULL timeout expired?";
ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
break;
default:
FAIL() << "not yet implemented";
}
r = write(m_fuse_fd, &out, out.header.len);
if (out.expected_errno) {
ASSERT_EQ(-1, r);
ASSERT_EQ(out.expected_errno, errno) << strerror(errno);
} else {
if (r <= 0 && errno == EINVAL) {
printf("Failed to write response. unique=%" PRIu64
":\n", out.header.unique);
}
ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
}
}
void* MockFS::service(void *pthr_data) {
MockFS *mock_fs = (MockFS*)pthr_data;
mock_fs->loop();
return (NULL);
}
void MockFS::unmount() {
::unmount("mountpoint", 0);
}