extern "C" {
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <semaphore.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
#ifndef VNOVAL
#define VNOVAL (-1)
#endif
class Mknod: public FuseTest {
mode_t m_oldmask;
const static mode_t c_umask = 022;
public:
Mknod() {
m_oldmask = umask(c_umask);
}
virtual void SetUp() {
if (geteuid() != 0) {
GTEST_SKIP() << "Only root may use most mknod(2) variations";
}
FuseTest::SetUp();
}
virtual void TearDown() {
FuseTest::TearDown();
(void)umask(m_oldmask);
}
void expect_mknod(uint64_t parent_ino, const char* relpath, uint64_t ino,
mode_t mode, dev_t dev)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_mknod_in);
return (in.header.nodeid == parent_ino &&
in.header.opcode == FUSE_MKNOD &&
in.body.mknod.mode == mode &&
in.body.mknod.rdev == (uint32_t)dev &&
in.body.mknod.umask == c_umask &&
(0 == strcmp(relpath, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = mode;
out.body.entry.nodeid = ino;
out.body.entry.entry_valid = UINT64_MAX;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.attr.rdev = dev;
})));
}
};
class Mknod_7_11: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 11;
if (geteuid() != 0) {
GTEST_SKIP() << "Only root may use most mknod(2) variations";
}
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);
}
void expect_mknod(uint64_t parent_ino, const char* relpath, uint64_t ino,
mode_t mode, dev_t dev)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
FUSE_COMPAT_MKNOD_IN_SIZE;
return (in.header.nodeid == parent_ino &&
in.header.opcode == FUSE_MKNOD &&
in.body.mknod.mode == mode &&
in.body.mknod.rdev == (uint32_t)dev &&
(0 == strcmp(relpath, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = mode;
out.body.entry.nodeid = ino;
out.body.entry.entry_valid = UINT64_MAX;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.attr.rdev = dev;
})));
}
};
TEST_F(Mknod, blk)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFBLK | 0755;
dev_t rdev = 0xfe00;
uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno);
}
TEST_F(Mknod, chr)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFCHR | 0755;
dev_t rdev = 54;
uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno);
}
TEST_F(Mknod, eperm)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFIFO | 0755;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_mknod_in);
return (in.header.opcode == FUSE_MKNOD &&
in.body.mknod.mode == mode &&
(0 == strcmp(RELPATH, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EPERM)));
EXPECT_NE(0, mkfifo(FULLPATH, mode));
EXPECT_EQ(EPERM, errno);
}
TEST_F(Mknod, fifo)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFIFO | 0755;
dev_t rdev = VNOVAL;
uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno);
}
TEST_F(Mknod, socket)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFSOCK | 0755;
struct sockaddr_un sa;
int fd;
dev_t rdev = -1;
uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
fd = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, fd) << strerror(errno);
sa.sun_family = AF_UNIX;
strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path));
sa.sun_len = sizeof(FULLPATH);
ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa)))
<< strerror(errno);
leak(fd);
}
TEST_F(Mknod, parent_inode)
{
const char FULLPATH[] = "mountpoint/parent/some_node";
const char PPATH[] = "parent";
const char RELPATH[] = "some_node";
mode_t mode = S_IFSOCK | 0755;
struct sockaddr_un sa;
sem_t sem;
int fd;
dev_t rdev = -1;
uint64_t ino = 42;
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
expect_lookup(PPATH, ino, S_IFDIR | 0755, 0, 1);
EXPECT_LOOKUP(ino, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(ino, RELPATH, ino, mode, rdev);
expect_forget(ino, 1, &sem);
fd = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, fd) << strerror(errno);
sa.sun_family = AF_UNIX;
strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path));
sa.sun_len = sizeof(FULLPATH);
ASSERT_EQ(-1, bind(fd, (struct sockaddr*)&sa, sizeof(sa)));
ASSERT_EQ(EIO, errno);
leak(fd);
sem_wait(&sem);
sem_destroy(&sem);
}
TEST_F(Mknod, DISABLED_whiteout)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFWHT | 0755;
dev_t rdev = VNOVAL;
uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno);
}
TEST_F(Mknod_7_11, fifo)
{
const char FULLPATH[] = "mountpoint/some_node";
const char RELPATH[] = "some_node";
mode_t mode = S_IFIFO | 0755;
dev_t rdev = VNOVAL;
uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno);
}