fuse(4): add some miscellaneous test cases that I had overlooked

* Test that FUSE_FLUSH and FUSE_RELEASE release POSIX file locks
* Test that FUSE_SETATTR's attr caching feature works
* Fix some minor mistakes in the posix file lock tests

Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-03-15 16:16:50 +00:00
parent 4da6e8cef1
commit 9ae9282e95
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=345188
9 changed files with 214 additions and 58 deletions

View File

@ -41,12 +41,13 @@ using namespace testing;
class Flush: public FuseTest {
public:
void expect_flush(uint64_t ino, int times, ProcessMockerT r)
void expect_flush(uint64_t ino, int times, pid_t lo, ProcessMockerT r)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_FLUSH &&
in->header.nodeid == ino &&
in->body.flush.lock_owner == (uint64_t)lo &&
in->body.flush.fh == FH);
}, Eq(true)),
_)
@ -74,7 +75,12 @@ void expect_release()
}
};
// TODO: lock_owner stuff
class FlushWithLocks: public Flush {
virtual void SetUp() {
m_init_flags = FUSE_POSIX_LOCKS;
Flush::SetUp();
}
};
/* If a file descriptor is duplicated, every close causes FLUSH */
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */
@ -88,7 +94,7 @@ TEST_F(Flush, DISABLED_dup)
expect_lookup(RELPATH, ino);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
expect_flush(ino, 2, ReturnErrno(0));
expect_flush(ino, 2, 0, ReturnErrno(0));
expect_release();
fd = open(FULLPATH, O_WRONLY);
@ -119,7 +125,7 @@ TEST_F(Flush, DISABLED_eio)
expect_lookup(RELPATH, ino);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
expect_flush(ino, 1, ReturnErrno(EIO));
expect_flush(ino, 1, 0, ReturnErrno(EIO));
expect_release();
fd = open(FULLPATH, O_WRONLY);
@ -140,7 +146,7 @@ TEST_F(Flush, DISABLED_flush)
expect_lookup(RELPATH, ino);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
expect_flush(ino, 1, ReturnErrno(0));
expect_flush(ino, 1, 0, ReturnErrno(0));
expect_release();
fd = open(FULLPATH, O_WRONLY);
@ -148,3 +154,50 @@ TEST_F(Flush, DISABLED_flush)
ASSERT_TRUE(0 == close(fd)) << strerror(errno);
}
/*
* When closing a file with a POSIX file lock, flush should release the lock,
* _even_if_ it's not the process's last file descriptor for this file.
*/
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */
TEST_F(FlushWithLocks, DISABLED_unlock_on_close)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd, fd2;
struct flock fl;
pid_t pid = getpid();
expect_lookup(RELPATH, ino);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_SETLK &&
in->header.nodeid == ino &&
in->body.setlk.fh == FH);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, setlk);
out->body.setlk.lk = in->body.setlk.lk;
})));
expect_flush(ino, 1, pid, ReturnErrno(0));
fd = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd) << strerror(errno);
fl.l_start = 0;
fl.l_len = 0;
fl.l_pid = pid;
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_sysid = 0;
ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
fd2 = dup(fd);
ASSERT_EQ(0, close(fd2)) << strerror(errno);
/* Deliberately leak fd */
}

View File

@ -143,7 +143,7 @@ TEST_F(Fsync, close)
}, Eq(true)),
_)
).Times(0);
expect_release(ino, 1, 0);
expect_release(ino, 1, 0, 0);
fd = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd) << strerror(errno);

View File

@ -120,7 +120,7 @@ TEST_F(Getlk, DISABLED_no_locks)
in->body.getlk.lk.start == 10 &&
in->body.getlk.lk.end == 1009 &&
in->body.getlk.lk.type == F_RDLCK &&
in->body.getlk.lk.pid == 10);
in->body.getlk.lk.pid == (uint64_t)pid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
@ -153,7 +153,7 @@ TEST_F(Getlk, DISABLED_lock_exists)
struct flock fl;
int fd;
pid_t pid = 1234;
pid_t pid2 = 1234;
pid_t pid2 = 1235;
expect_lookup(RELPATH, ino);
expect_open(ino, 0, 1);
@ -167,7 +167,7 @@ TEST_F(Getlk, DISABLED_lock_exists)
in->body.getlk.lk.start == 10 &&
in->body.getlk.lk.end == 1009 &&
in->body.getlk.lk.type == F_RDLCK &&
in->body.getlk.lk.pid == 10);
in->body.getlk.lk.pid == (uint64_t)pid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
@ -243,19 +243,18 @@ TEST_F(Setlk, DISABLED_set)
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_SETLK &&
in->header.nodeid == ino &&
in->body.getlk.fh == FH &&
in->body.getlk.owner == (uint32_t)pid &&
in->body.getlk.lk.start == 10 &&
in->body.getlk.lk.end == 1009 &&
in->body.getlk.lk.type == F_RDLCK &&
in->body.getlk.lk.pid == 10);
in->body.setlk.fh == FH &&
in->body.setlk.owner == (uint32_t)pid &&
in->body.setlk.lk.start == 10 &&
in->body.setlk.lk.end == 1009 &&
in->body.setlk.lk.type == F_RDLCK &&
in->body.setlk.lk.pid == (uint64_t)pid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, getlk);
out->body.getlk.lk = in->body.getlk.lk;
out->body.getlk.lk.type = F_UNLCK;
SET_OUT_HEADER_LEN(out, setlk);
out->body.setlk.lk = in->body.setlk.lk;
})));
fd = open(FULLPATH, O_RDWR);
@ -288,19 +287,18 @@ TEST_F(Setlk, DISABLED_set_eof)
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_SETLK &&
in->header.nodeid == ino &&
in->body.getlk.fh == FH &&
in->body.getlk.owner == (uint32_t)pid &&
in->body.getlk.lk.start == 10 &&
in->body.getlk.lk.end == OFFSET_MAX &&
in->body.getlk.lk.type == F_RDLCK &&
in->body.getlk.lk.pid == 10);
in->body.setlk.fh == FH &&
in->body.setlk.owner == (uint32_t)pid &&
in->body.setlk.lk.start == 10 &&
in->body.setlk.lk.end == OFFSET_MAX &&
in->body.setlk.lk.type == F_RDLCK &&
in->body.setlk.lk.pid == (uint64_t)pid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, getlk);
out->body.getlk.lk = in->body.getlk.lk;
out->body.getlk.lk.type = F_UNLCK;
SET_OUT_HEADER_LEN(out, setlk);
out->body.setlk.lk = in->body.setlk.lk;
})));
fd = open(FULLPATH, O_RDWR);
@ -333,12 +331,12 @@ TEST_F(Setlk, DISABLED_eagain)
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_SETLK &&
in->header.nodeid == ino &&
in->body.getlk.fh == FH &&
in->body.getlk.owner == (uint32_t)pid &&
in->body.getlk.lk.start == 10 &&
in->body.getlk.lk.end == 1009 &&
in->body.getlk.lk.type == F_RDLCK &&
in->body.getlk.lk.pid == 10);
in->body.setlk.fh == FH &&
in->body.setlk.owner == (uint32_t)pid &&
in->body.setlk.lk.start == 10 &&
in->body.setlk.lk.end == 1009 &&
in->body.setlk.lk.type == F_RDLCK &&
in->body.setlk.lk.pid == (uint64_t)pid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnErrno(EAGAIN)));
@ -406,19 +404,18 @@ TEST_F(Setlkw, DISABLED_set)
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_SETLK &&
in->header.nodeid == ino &&
in->body.getlk.fh == FH &&
in->body.getlk.owner == (uint32_t)pid &&
in->body.getlk.lk.start == 10 &&
in->body.getlk.lk.end == 1009 &&
in->body.getlk.lk.type == F_RDLCK &&
in->body.getlk.lk.pid == 10);
in->body.setlkw.fh == FH &&
in->body.setlkw.owner == (uint32_t)pid &&
in->body.setlkw.lk.start == 10 &&
in->body.setlkw.lk.end == 1009 &&
in->body.setlkw.lk.type == F_RDLCK &&
in->body.setlkw.lk.pid == (uint64_t)pid);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, getlk);
out->body.getlk.lk = in->body.getlk.lk;
out->body.getlk.lk.type = F_UNLCK;
SET_OUT_HEADER_LEN(out, setlkw);
out->body.setlkw.lk = in->body.setlkw.lk;
})));
fd = open(FULLPATH, O_RDWR);

View File

@ -159,6 +159,9 @@ void debug_fuseop(const mockfs_buf_in *in)
in->header.unique, in->header.len);
}
switch (in->header.opcode) {
case FUSE_FLUSH:
printf(" lock_owner=%lu", in->body.flush.lock_owner);
break;
case FUSE_FORGET:
printf(" nlookup=%lu", in->body.forget.nlookup);
break;
@ -187,6 +190,11 @@ void debug_fuseop(const mockfs_buf_in *in)
printf(" offset=%lu size=%u", in->body.readdir.offset,
in->body.readdir.size);
break;
case FUSE_RELEASE:
printf(" flags=%#x lock_owner=%lu",
in->body.release.flags,
in->body.release.lock_owner);
break;
case FUSE_SETATTR:
if (verbosity <= 1) {
printf(" valid=%#x", in->body.setattr.valid);

View File

@ -104,6 +104,7 @@ union fuse_payloads_in {
fuse_setattr_in setattr;
fuse_setxattr_in setxattr;
fuse_lk_in setlk;
fuse_lk_in setlkw;
char unlink[0];
fuse_write_in write;
};
@ -125,6 +126,7 @@ union fuse_payloads_out {
fuse_listxattr_out listxattr;
fuse_open_out open;
fuse_lk_out setlk;
fuse_lk_out setlkw;
fuse_statfs_out statfs;
/*
* The protocol places no limits on the length of the string. This is

View File

@ -47,7 +47,13 @@ void expect_lookup(const char *relpath, uint64_t ino, int times)
}
};
// TODO: lock owner stuff
class ReleaseWithLocks: public Release {
virtual void SetUp() {
m_init_flags = FUSE_POSIX_LOCKS;
Release::SetUp();
}
};
/* If a file descriptor is duplicated, only the last close causes RELEASE */
TEST_F(Release, dup)
@ -60,7 +66,7 @@ TEST_F(Release, dup)
expect_lookup(RELPATH, ino, 1);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
expect_release(ino, 1, 0);
expect_release(ino, 1, 0, 0);
fd = open(FULLPATH, O_RDONLY);
EXPECT_LE(0, fd) << strerror(errno);
@ -89,7 +95,7 @@ TEST_F(Release, eio)
expect_lookup(RELPATH, ino, 1);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
expect_release(ino, 1, EIO);
expect_release(ino, 1, 0, EIO);
fd = open(FULLPATH, O_WRONLY);
EXPECT_LE(0, fd) << strerror(errno);
@ -112,7 +118,7 @@ TEST_F(Release, multiple_opens)
expect_lookup(RELPATH, ino, 2);
expect_open(ino, 0, 2);
expect_getattr(ino, 0);
expect_release(ino, 2, 0);
expect_release(ino, 2, 0, 0);
fd = open(FULLPATH, O_RDONLY);
EXPECT_LE(0, fd) << strerror(errno);
@ -134,10 +140,51 @@ TEST_F(Release, ok)
expect_lookup(RELPATH, ino, 1);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
expect_release(ino, 1, 0);
expect_release(ino, 1, 0, 0);
fd = open(FULLPATH, O_RDONLY);
EXPECT_LE(0, fd) << strerror(errno);
ASSERT_EQ(0, close(fd)) << strerror(errno);
}
/* When closing a file with a POSIX file lock, release should release the lock*/
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */
TEST_F(ReleaseWithLocks, DISABLED_unlock_on_close)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
uint64_t ino = 42;
int fd;
struct flock fl;
pid_t pid = getpid();
expect_lookup(RELPATH, ino, 1);
expect_open(ino, 0, 1);
expect_getattr(ino, 0);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_SETLK &&
in->header.nodeid == ino &&
in->body.setlk.fh == FH);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, setlk);
out->body.setlk.lk = in->body.setlk.lk;
})));
expect_release(ino, 1, (uint64_t)pid, 0);
fd = open(FULLPATH, O_RDWR);
ASSERT_LE(0, fd) << strerror(errno);
fl.l_start = 0;
fl.l_len = 0;
fl.l_pid = pid;
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_sysid = 0;
ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
ASSERT_EQ(0, close(fd)) << strerror(errno);
}

View File

@ -42,6 +42,55 @@ using namespace testing;
class Setattr : public FuseTest {};
/*
* If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
* should use the cached attributes, rather than query the daemon
*/
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
TEST_F(Setattr, DISABLED_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(1, RELPATH)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = ino;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
/* In protocol 7.23, ctime will be changed too */
return (in->header.opcode == FUSE_SETATTR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, attr);
out->body.attr.attr.ino = ino; // Must match nodeid
out->body.attr.attr.mode = S_IFREG | newmode;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_GETATTR);
}, Eq(true)),
_)
).Times(0);
/* Set an attribute with SETATTR */
ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
/* The stat(2) should use cached attributes */
ASSERT_EQ(0, stat(FULLPATH, &sb));
EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
}
/* Change the mode of a file */
TEST_F(Setattr, chmod)
{
@ -305,7 +354,8 @@ TEST_F(Setattr, truncate) {
const uint64_t oldsize = 100'000'000;
const uint64_t newsize = 20'000'000;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
EXPECT_LOOKUP(1, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
@ -347,7 +397,8 @@ TEST_F(Setattr, utimensat) {
{.tv_sec = 7, .tv_nsec = 8},
};
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
EXPECT_LOOKUP(1, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
@ -423,7 +474,8 @@ TEST_F(Setattr, utimensat_mtime_only) {
{.tv_sec = 7, .tv_nsec = 8},
};
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
EXPECT_LOOKUP(1, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
out->header.unique = in->header.unique;
SET_OUT_HEADER_LEN(out, entry);
out->body.entry.attr.mode = S_IFREG | 0644;
@ -481,9 +533,3 @@ TEST_F(Setattr, utimensat_mtime_only) {
EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
<< strerror(errno);
}
/*
* Writethrough cache: newly changed attributes should be automatically cached,
* if the filesystem allows it
*/
//TODO TEST_F(Setattr, writethrough_cache){}

View File

@ -181,12 +181,14 @@ void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
}))).RetiresOnSaturation();
}
void FuseTest::expect_release(uint64_t ino, int times, int error)
void FuseTest::expect_release(uint64_t ino, int times, uint64_t lock_owner,
int error)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_RELEASE &&
in->header.nodeid == ino &&
in->body.release.lock_owner == lock_owner &&
in->body.release.fh == FH);
}, Eq(true)),
_)

View File

@ -103,7 +103,8 @@ class FuseTest : public ::testing::Test {
* Create an expectation that FUSE_RELEASE will be called times times
* for the given inode, returning error error
*/
void expect_release(uint64_t ino, int times, int error);
void expect_release(uint64_t ino, int times, uint64_t lock_owner,
int error);
/*
* Create an expectation that FUSE_WRITE will be called exactly once