extern "C" {
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
using IoctlTestProcT = std::function<void (int)>;
static const char INPUT_DATA[] = "input_data";
static const char OUTPUT_DATA[] = "output_data";
class Ioctl: public FuseTest {
public:
void expect_ioctl(uint64_t ino, ProcessMockerT r)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_IOCTL &&
in.header.nodeid == ino);
}, Eq(true)), _)
).WillOnce(Invoke(r)).RetiresOnSaturation();
}
void expect_ioctl_rw(uint64_t ino)
{
expect_ioctl(ino, ReturnImmediate([](auto in, auto& out) {
uint8_t *in_buf = in.body.bytes + sizeof(in.body.ioctl);
uint8_t *out_buf = out.body.bytes + sizeof(out.body.ioctl);
uint32_t cmd = in.body.ioctl.cmd;
uint32_t arg_len = IOCPARM_LEN(cmd);
int result = 0;
out.header.error = 0;
SET_OUT_HEADER_LEN(out, ioctl);
if ((cmd & IOC_VOID) != 0 && arg_len > 0) {
memcpy(&result, in_buf, sizeof(int));
goto out;
}
if ((cmd & IOC_IN) != 0) {
if (0 != strncmp(INPUT_DATA, (char *)in_buf, arg_len)) {
result = -EINVAL;
goto out;
}
}
if ((cmd & IOC_OUT) != 0) {
memcpy(out_buf, OUTPUT_DATA, sizeof(OUTPUT_DATA));
out.header.len += sizeof(OUTPUT_DATA);
}
out:
out.body.ioctl.result = result;
}));
}
};
TEST_F(Ioctl, enosys)
{
unsigned long req = _IO(0xff, 0);
int fd;
expect_opendir(FUSE_ROOT_ID);
expect_ioctl(FUSE_ROOT_ID, ReturnErrno(ENOSYS));
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
EXPECT_EQ(-1, ioctl(fd, req));
EXPECT_EQ(ENOTTY, errno);
leak(fd);
}
TEST_F(Ioctl, ior)
{
char buf[sizeof(OUTPUT_DATA) + 1] = { 0 };
unsigned long req = _IOR(0xff, 1, buf);
int fd;
expect_opendir(FUSE_ROOT_ID);
expect_ioctl_rw(FUSE_ROOT_ID);
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
leak(fd);
}
TEST_F(Ioctl, ior_overflow)
{
char buf[sizeof(OUTPUT_DATA) - 1] = { 0 };
unsigned long req = _IOR(0xff, 2, buf);
int fd;
expect_opendir(FUSE_ROOT_ID);
expect_ioctl_rw(FUSE_ROOT_ID);
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
EXPECT_EQ(-1, ioctl(fd, req, buf));
EXPECT_EQ(EIO, errno);
leak(fd);
}
TEST_F(Ioctl, iow)
{
unsigned long req = _IOW(0xff, 3, INPUT_DATA);
int fd;
expect_opendir(FUSE_ROOT_ID);
expect_ioctl_rw(FUSE_ROOT_ID);
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
EXPECT_EQ(0, ioctl(fd, req, INPUT_DATA)) << strerror(errno);
leak(fd);
}
TEST_F(Ioctl, iowr)
{
char buf[std::max(sizeof(INPUT_DATA), sizeof(OUTPUT_DATA))] = { 0 };
unsigned long req = _IOWR(0xff, 4, buf);
int fd;
expect_opendir(FUSE_ROOT_ID);
expect_ioctl_rw(FUSE_ROOT_ID);
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
memcpy(buf, INPUT_DATA, sizeof(INPUT_DATA));
EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
leak(fd);
}
TEST_F(Ioctl, iowint)
{
unsigned long req = _IOWINT(0xff, 5);
int arg = 1337;
int fd, r;
expect_opendir(FUSE_ROOT_ID);
expect_ioctl_rw(FUSE_ROOT_ID);
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
ASSERT_LE(0, fd) << strerror(errno);
r = ioctl(fd, req, arg);
EXPECT_LE(0, r) << strerror(errno);
EXPECT_EQ(arg, r);
leak(fd);
}