extern "C" {
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <semaphore.h>
#include <signal.h>
}
#include "mockfs.hh"
#include "utils.hh"
using namespace testing;
class Setattr : public FuseTest {
public:
static sig_atomic_t s_sigxfsz;
};
class RofsSetattr: public Setattr {
public:
virtual void SetUp() {
s_sigxfsz = 0;
m_ro = true;
Setattr::SetUp();
}
};
class Setattr_7_8: public Setattr {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
Setattr::SetUp();
}
};
sig_atomic_t Setattr::s_sigxfsz = 0;
void sigxfsz_handler(int __unused sig) {
Setattr::s_sigxfsz = 1;
}
TEST_F(Setattr, attr_cache)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
struct stat sb;
const mode_t newmode = 0644;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
out.body.attr.attr_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in.header.opcode == FUSE_GETATTR);
}, Eq(true)),
_)
).Times(0);
ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
ASSERT_EQ(0, stat(FULLPATH, &sb));
EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
}
TEST_F(Setattr, chmod)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const mode_t oldmode = 0755;
const mode_t newmode = 0644;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | oldmode;
out.body.entry.nodeid = ino;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
uint32_t valid = FATTR_MODE;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.mode == newmode);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
})));
EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
}
TEST_F(Setattr, chmod_multiply_linked)
{
const char FULLPATH0[] = "mountpoint/some_file.txt";
const char RELPATH0[] = "some_file.txt";
const char FULLPATH1[] = "mountpoint/other_file.txt";
const char RELPATH1[] = "other_file.txt";
struct stat sb;
const uint64_t ino = 42;
const mode_t oldmode = 0777;
const mode_t newmode = 0666;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | oldmode;
out.body.entry.nodeid = ino;
out.body.entry.attr.nlink = 2;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | oldmode;
out.body.entry.nodeid = ino;
out.body.entry.attr.nlink = 2;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
uint32_t valid = FATTR_MODE;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.mode == newmode);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
out.body.attr.attr.nlink = 2;
out.body.attr.attr_valid = UINT64_MAX;
})));
ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
}
TEST_F(Setattr, chown)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const gid_t oldgroup = 66;
const gid_t newgroup = 99;
const uid_t olduser = 33;
const uid_t newuser = 44;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.attr.gid = oldgroup;
out.body.entry.attr.uid = olduser;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
uint32_t valid = FATTR_GID | FATTR_UID;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.uid == newuser &&
in.body.setattr.gid == newgroup);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0644;
out.body.attr.attr.uid = newuser;
out.body.attr.attr.gid = newgroup;
})));
EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
}
TEST_F(Setattr, eperm)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0777;
out.body.entry.nodeid = ino;
out.body.entry.attr.uid = in.header.uid;
out.body.entry.attr.gid = in.header.gid;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EPERM)));
EXPECT_NE(0, truncate(FULLPATH, 10));
EXPECT_EQ(EPERM, errno);
}
TEST_F(Setattr, fchmod)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
const mode_t oldmode = 0755;
const mode_t newmode = 0644;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | oldmode;
out.body.entry.nodeid = ino;
out.body.entry.attr_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_OPEN &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
out.header.len = sizeof(out.header);
SET_OUT_HEADER_LEN(out, open);
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
uint32_t valid = FATTR_MODE;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.mode == newmode);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
})));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
leak(fd);
}
TEST_F(Setattr, ftruncate)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
uint64_t fh = 0xdeadbeef1a7ebabe;
const off_t oldsize = 99;
const off_t newsize = 12345;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0755;
out.body.entry.nodeid = ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.attr.size = oldsize;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_OPEN &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
out.header.len = sizeof(out.header);
SET_OUT_HEADER_LEN(out, open);
out.body.open.fh = fh;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
uint32_t valid = FATTR_SIZE | FATTR_FH;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.fh == fh);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0755;
out.body.attr.attr.size = newsize;
})));
fd = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
leak(fd);
}
TEST_F(Setattr, truncate) {
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const uint64_t oldsize = 100'000'000;
const uint64_t newsize = 20'000'000;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
uint32_t valid = FATTR_SIZE;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.size == newsize);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0644;
out.body.attr.attr.size = newsize;
})));
EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
}
TEST_F(Setattr, truncate_discards_cached_data) {
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
char *w0buf, *r0buf, *r1buf, *expected;
off_t w0_offset = 0;
size_t w0_size = 0x30000;
off_t r0_offset = 0;
off_t r0_size = w0_size;
size_t trunc0_size = 0x400;
size_t trunc1_size = w0_size;
off_t r1_offset = trunc0_size;
off_t r1_size = w0_size - trunc0_size;
size_t cur_size = 0;
const uint64_t ino = 42;
mode_t mode = S_IFREG | 0644;
int fd, r;
bool should_have_data = false;
w0buf = new char[w0_size];
memset(w0buf, 'X', w0_size);
r0buf = new char[r0_size];
r1buf = new char[r1_size];
expected = new char[r1_size]();
expect_lookup(RELPATH, ino, mode, 0, 1);
expect_open(ino, O_RDWR, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_GETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = mode;
out.body.attr.attr.size = cur_size;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_WRITE);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
SET_OUT_HEADER_LEN(out, write);
out.body.attr.attr.ino = ino;
out.body.write.size = in.body.write.size;
cur_size = std::max(static_cast<uint64_t>(cur_size),
in.body.write.size + in.body.write.offset);
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
(in.body.setattr.valid & FATTR_SIZE));
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
auto trunc_size = in.body.setattr.size;
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = mode;
out.body.attr.attr.size = trunc_size;
cur_size = trunc_size;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
auto osize = std::min(
static_cast<uint64_t>(cur_size) - in.body.read.offset,
static_cast<uint64_t>(in.body.read.size));
assert(osize <= sizeof(out.body.bytes));
out.header.len = sizeof(struct fuse_out_header) + osize;
if (should_have_data)
memset(out.body.bytes, 'X', osize);
else
bzero(out.body.bytes, osize);
})));
fd = open(FULLPATH, O_RDWR, 0644);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(static_cast<ssize_t>(w0_size),
pwrite(fd, w0buf, w0_size, w0_offset));
should_have_data = true;
ASSERT_EQ(static_cast<ssize_t>(r0_size),
pread(fd, r0buf, r0_size, r0_offset));
EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
should_have_data = false;
EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
ASSERT_EQ(static_cast<ssize_t>(r1_size),
pread(fd, r1buf, r1_size, r1_offset));
r = memcmp(expected, r1buf, r1_size);
ASSERT_EQ(0, r);
delete[] expected;
delete[] r1buf;
delete[] r0buf;
delete[] w0buf;
leak(fd);
}
TEST_F(Setattr, truncate_rlimit_rsize)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
struct rlimit rl;
const uint64_t ino = 42;
const uint64_t oldsize = 0;
const uint64_t newsize = 100'000'000;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
})));
rl.rlim_cur = newsize / 2;
rl.rlim_max = 10 * newsize;
ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
EXPECT_EQ(-1, truncate(FULLPATH, newsize));
EXPECT_EQ(EFBIG, errno);
EXPECT_EQ(1, s_sigxfsz);
}
TEST_F(Setattr, utimensat) {
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const timespec oldtimes[2] = {
{.tv_sec = 1, .tv_nsec = 2},
{.tv_sec = 3, .tv_nsec = 4},
};
const timespec newtimes[2] = {
{.tv_sec = 5, .tv_nsec = 6},
{.tv_sec = 7, .tv_nsec = 8},
};
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.attr.atime = oldtimes[0].tv_sec;
out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
out.body.entry.attr.mtime = oldtimes[1].tv_sec;
out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
uint32_t valid = FATTR_ATIME | FATTR_MTIME;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
(time_t)in.body.setattr.atime ==
newtimes[0].tv_sec &&
(long)in.body.setattr.atimensec ==
newtimes[0].tv_nsec &&
(time_t)in.body.setattr.mtime ==
newtimes[1].tv_sec &&
(long)in.body.setattr.mtimensec ==
newtimes[1].tv_nsec);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0644;
out.body.attr.attr.atime = newtimes[0].tv_sec;
out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
out.body.attr.attr.mtime = newtimes[1].tv_sec;
out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
})));
EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
<< strerror(errno);
}
TEST_F(Setattr, utimensat_mtime_only) {
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const timespec oldtimes[2] = {
{.tv_sec = 1, .tv_nsec = 2},
{.tv_sec = 3, .tv_nsec = 4},
};
const timespec newtimes[2] = {
{.tv_sec = 5, .tv_nsec = UTIME_OMIT},
{.tv_sec = 7, .tv_nsec = 8},
};
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.attr.atime = oldtimes[0].tv_sec;
out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
out.body.entry.attr.mtime = oldtimes[1].tv_sec;
out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
uint32_t valid = FATTR_MTIME;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
(time_t)in.body.setattr.mtime ==
newtimes[1].tv_sec &&
(long)in.body.setattr.mtimensec ==
newtimes[1].tv_nsec);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0644;
out.body.attr.attr.atime = oldtimes[0].tv_sec;
out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
out.body.attr.attr.mtime = newtimes[1].tv_sec;
out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
})));
EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
<< strerror(errno);
}
TEST_F(Setattr, utimensat_utime_now) {
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const timespec oldtimes[2] = {
{.tv_sec = 1, .tv_nsec = 2},
{.tv_sec = 3, .tv_nsec = 4},
};
const timespec newtimes[2] = {
{.tv_sec = 0, .tv_nsec = UTIME_NOW},
{.tv_sec = 0, .tv_nsec = UTIME_NOW},
};
const timespec now[2] = {
{.tv_sec = 5, .tv_nsec = 7},
{.tv_sec = 6, .tv_nsec = 8},
};
struct stat sb;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0644;
out.body.entry.nodeid = ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
out.body.entry.attr.atime = oldtimes[0].tv_sec;
out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
out.body.entry.attr.mtime = oldtimes[1].tv_sec;
out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
FATTR_MTIME | FATTR_MTIME_NOW;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | 0644;
out.body.attr.attr.atime = now[0].tv_sec;
out.body.attr.attr.atimensec = now[0].tv_nsec;
out.body.attr.attr.mtime = now[1].tv_sec;
out.body.attr.attr.mtimensec = now[1].tv_nsec;
out.body.attr.attr_valid = UINT64_MAX;
})));
ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
<< strerror(errno);
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
}
TEST_F(Setattr, vtyp_conflict)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
uid_t newuser = 12345;
sem_t sem;
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | 0777;
out.body.entry.nodeid = ino;
out.body.entry.entry_valid = UINT64_MAX;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFDIR | 0777;
out.body.attr.attr.uid = newuser;
})));
expect_forget(ino, 1, &sem);
EXPECT_NE(0, chown(FULLPATH, newuser, -1));
EXPECT_EQ(ENOENT, errno);
sem_wait(&sem);
sem_destroy(&sem);
}
TEST_F(RofsSetattr, erofs)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const mode_t oldmode = 0755;
const mode_t newmode = 0644;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFREG | oldmode;
out.body.entry.nodeid = ino;
})));
ASSERT_EQ(-1, chmod(FULLPATH, newmode));
ASSERT_EQ(EROFS, errno);
}
TEST_F(Setattr_7_8, chmod)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const mode_t oldmode = 0755;
const mode_t newmode = 0644;
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out.body.entry.attr.mode = S_IFREG | oldmode;
out.body.entry.nodeid = ino;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
uint32_t valid = FATTR_MODE;
return (in.header.opcode == FUSE_SETATTR &&
in.header.nodeid == ino &&
in.body.setattr.valid == valid &&
in.body.setattr.mode == newmode);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr_7_8);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = S_IFREG | newmode;
})));
EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
}