Index: sys/fs/fuse/fuse_internal.c =================================================================== --- sys/fs/fuse/fuse_internal.c +++ sys/fs/fuse/fuse_internal.c @@ -158,6 +158,7 @@ return 0; } +SDT_PROBE_DEFINE0(fusefs, , internal, access_vadmin); /* Synchronously send a FUSE_ACCESS operation */ int fuse_internal_access(struct vnode *vp, @@ -213,7 +214,12 @@ if (!fsess_isimpl(mp, FUSE_ACCESS)) return 0; - if ((mode & (VWRITE | VAPPEND | VADMIN)) != 0) + if (mode & VADMIN) { + // The FUSE protocol doesn't have an equivalent of VADMIN, so + // it's a bug if we ever reach this point with that bit set. + SDT_PROBE0(fusefs, , internal, access_vadmin); + } + if ((mode & (VWRITE | VAPPEND)) != 0) mask |= W_OK; if ((mode & VREAD) != 0) mask |= R_OK; Index: sys/fs/fuse/fuse_vnops.c =================================================================== --- sys/fs/fuse/fuse_vnops.c +++ sys/fs/fuse/fuse_vnops.c @@ -235,6 +235,7 @@ { struct mount *mp = vnode_mount(vp); struct fuse_data *data = fuse_get_mpdata(mp); + int default_permissions = data->dataflags & FSESS_DEFAULT_PERMISSIONS; /* * Kernel-invoked always succeeds. @@ -248,12 +249,16 @@ */ switch (ns) { case EXTATTR_NAMESPACE_SYSTEM: - if (data->dataflags & FSESS_DEFAULT_PERMISSIONS) { + if (default_permissions) { return (priv_check_cred(cred, PRIV_VFS_EXTATTR_SYSTEM)); } /* FALLTHROUGH */ case EXTATTR_NAMESPACE_USER: - return (fuse_internal_access(vp, accmode, td, cred)); + if (default_permissions) { + return (fuse_internal_access(vp, accmode, td, cred)); + } else { + return (0); + } default: return (EPERM); } @@ -985,6 +990,8 @@ int wantparent = flags & (LOCKPARENT | WANTPARENT); int islastcn = flags & ISLASTCN; struct mount *mp = vnode_mount(dvp); + struct fuse_data *data = fuse_get_mpdata(mp); + int default_permissions = data->dataflags & FSESS_DEFAULT_PERMISSIONS; int err = 0; int lookup_err = 0; @@ -1108,7 +1115,11 @@ if (lookup_err) { /* Entry not found */ if ((nameiop == CREATE || nameiop == RENAME) && islastcn) { - err = fuse_internal_access(dvp, VWRITE, td, cred); + if (default_permissions) + err = fuse_internal_access(dvp, VWRITE, td, + cred); + else + err = 0; if (!err) { /* * Set the SAVENAME flag to hold onto the @@ -1191,7 +1202,7 @@ &fvdat->entry_cache_timeout); if ((nameiop == DELETE || nameiop == RENAME) && - islastcn) + islastcn && default_permissions) { struct vattr dvattr; @@ -1828,7 +1839,9 @@ if (vfs_isrdonly(mp)) return EROFS; - err = fuse_internal_access(vp, accmode, td, cred); + if (data->dataflags & FSESS_DEFAULT_PERMISSIONS) { + err = fuse_internal_access(vp, accmode, td, cred); + } if (err) return err; else Index: tests/sys/fs/fusefs/access.cc =================================================================== --- tests/sys/fs/fusefs/access.cc +++ tests/sys/fs/fusefs/access.cc @@ -31,6 +31,9 @@ */ extern "C" { +#include +#include + #include #include } @@ -42,10 +45,33 @@ class Access: public FuseTest { public: +virtual void SetUp() { + FuseTest::SetUp(); + // Clear the default FUSE_ACCESS expectation + Mock::VerifyAndClearExpectations(m_mock); +} + void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } + +/* + * Expect tha FUSE_ACCESS will never be called for the given inode, with any + * bits in the supplied access_mask set + */ +void expect_noaccess(uint64_t ino, mode_t access_mask) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_ACCESS && + in.header.nodeid == ino && + in.body.access.mask & access_mask); + }, Eq(true)), + _) + ).Times(0); +} + }; class RofsAccess: public Access { @@ -56,6 +82,69 @@ } }; +/* + * Change the mode of a file. + * + * There should never be a FUSE_ACCESS sent for this operation, except possibly + * during the lookup phase. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, chmod) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const mode_t newmode = 0644; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_lookup(RELPATH, ino); + expect_noaccess(ino, 0); + 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; // Must match nodeid + out.body.attr.attr.mode = S_IFREG | newmode; + }))); + + EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); +} + +/* + * Create a new file + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, create) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = S_IFREG | 0755; + uint64_t ino = 42; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK); + //expect_lookup(RELPATH, ino); + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillOnce(Invoke(ReturnErrno(ENOENT))); + expect_noaccess(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_CREATE); + }, Eq(true)), + _) + ).WillOnce(ReturnErrno(EPERM)); + + EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); + EXPECT_EQ(EPERM, errno); +} + /* The error case of FUSE_ACCESS. */ TEST_F(Access, eaccess) { @@ -105,6 +194,33 @@ ASSERT_EQ(EROFS, errno); } + +/* + * Lookup an extended attribute + * + * There should never be a FUSE_ACCESS sent for this operation, except possibly + * during the lookup phase. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, Getxattr) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + char data[80]; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_lookup(RELPATH, ino); + expect_noaccess(ino, 0); + expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + ASSERT_EQ(ENOATTR, errno); +} + /* The successful case of FUSE_ACCESS. */ TEST_F(Access, ok) { @@ -118,4 +234,71 @@ expect_access(ino, access_mode, 0); ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); +} + +/* + * Unlink a file + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, unlink) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); + expect_noaccess(ino, 0); + expect_lookup(RELPATH, ino); + expect_unlink(1, RELPATH, EPERM); + + ASSERT_NE(0, unlink(FULLPATH)); + ASSERT_EQ(EPERM, errno); +} + +/* + * Lookup an extended attribute + * + * There should never be a FUSE_ACCESS sent for this operation, except possibly + * during the lookup phase. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, unlink_sticky_directory) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); + expect_noaccess(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_GETATTR && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) + { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = FUSE_ROOT_ID; + out.body.attr.attr.mode = S_IFDIR | 01777; + out.body.attr.attr.uid = 0; + out.body.attr.attr_valid = UINT64_MAX; + }))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_ACCESS && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).Times(0); + expect_lookup(RELPATH, ino); + expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM); + + ASSERT_EQ(-1, unlink(FULLPATH)); + ASSERT_EQ(EPERM, errno); } Index: tests/sys/fs/fusefs/default_permissions.cc =================================================================== --- tests/sys/fs/fusefs/default_permissions.cc +++ tests/sys/fs/fusefs/default_permissions.cc @@ -125,7 +125,7 @@ out.body.attr.attr.mode = mode; out.body.attr.attr.size = 0; out.body.attr.attr.uid = uid; - out.body.attr.attr.uid = gid; + out.body.attr.attr.gid = gid; out.body.attr.attr_valid = attr_valid; }))); } Index: tests/sys/fs/fusefs/lookup.cc =================================================================== --- tests/sys/fs/fusefs/lookup.cc +++ tests/sys/fs/fusefs/lookup.cc @@ -39,7 +39,31 @@ using namespace testing; -class Lookup: public FuseTest {}; +class Lookup: public FuseTest { +public: +/* + * Expect a getattr of the mountpoint that may or may not happen. It usually + * doesn't, but it can, for example when mac_bsdextended.ko is loaded and + * active. In any case, it's allowed + */ +void maybe_expect_getattr() +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_GETATTR && + in.header.nodeid == 1); + }, Eq(true)), + _) + ).Times(AtMost(1)) + .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = 1; + out.body.attr.attr.mode = S_IFDIR | 0755; + out.body.attr.attr_valid = UINT64_MAX; + }))); +} +}; + class Lookup_7_8: public Lookup { public: virtual void SetUp() { @@ -301,6 +325,7 @@ const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; + //maybe_expect_getattr(); EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { SET_OUT_HEADER_LEN(out, entry); Index: tests/sys/fs/fusefs/notify.cc =================================================================== --- tests/sys/fs/fusefs/notify.cc +++ tests/sys/fs/fusefs/notify.cc @@ -323,12 +323,13 @@ EXPECT_EQ(0, (intptr_t)thr0_value); } -TEST_F(Notify, inval_inode_with_clean_cache) +/* Invalidate I/O in the pattern generated by cephfs */ +TEST_F(Notify, inval_inode_like_cephfs) { const static char FULLPATH[] = "mountpoint/foo"; const static char RELPATH[] = "foo"; - const char CONTENTS0[] = "abcdefgh"; - const char CONTENTS1[] = "ijklmnopqrstuvwxyz"; + const char CONTENTS0[] = "0123456789"; + const char CONTENTS1[] = "0123456789Singapore"; struct inval_inode_args iia; struct stat sb; ino_t ino = 42; @@ -343,6 +344,25 @@ expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size0, seq); expect_open(ino, 0, 1); + expect_read(ino, 0, size0, size0, CONTENTS0); + + /* Fill the data cache */ + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(size0, read(fd, buf, sizeof(buf))) << strerror(errno); + EXPECT_EQ(0, memcmp(buf, CONTENTS0, size0)); + + /* Evict the data cache */ + iia.mock = m_mock; + iia.ino = ino; + iia.off = 0; + iia.len = 0; + ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia)) + << strerror(errno); + pthread_join(th0, &thr0_value); + EXPECT_EQ(0, (intptr_t)thr0_value); + + /* cache attributes were purged; this will trigger a new GETATTR */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in.header.opcode == FUSE_GETATTR && @@ -356,13 +376,45 @@ out.body.attr.attr.size = size1; out.body.attr.attr.uid = uid; }))); - expect_read(ino, 0, size0, size0, CONTENTS0); expect_read(ino, 0, size1, size1, CONTENTS1); + ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); + EXPECT_EQ(uid, sb.st_uid); + EXPECT_EQ(size1, sb.st_size); + /* This read should not be serviced by cache */ + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(size1, read(fd, buf, sizeof(buf))) << strerror(errno); + EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1)); + + leak(fd); +} + +TEST_F(Notify, inval_inode_with_clean_cache) +{ + const static char FULLPATH[] = "mountpoint/foo"; + const static char RELPATH[] = "foo"; + const char CONTENTS0[] = "abcdefgh"; + const char CONTENTS1[] = "ijklmnopqrstuvwxyz"; + struct inval_inode_args iia; + struct stat sb; + ino_t ino = 42; + void *thr0_value; + Sequence seq; + uid_t uid = 12345; + pthread_t th0; + ssize_t size0 = sizeof(CONTENTS0); + ssize_t size1 = sizeof(CONTENTS1); + char buf[80]; + int fd; + + expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size0, seq); + expect_open(ino, 0, 1); + expect_read(ino, 0, size0, size0, CONTENTS0); + /* Fill the data cache */ fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); - ASSERT_EQ(size0, read(fd, buf, size0)) << strerror(errno); + ASSERT_EQ(size0, read(fd, buf, sizeof(buf))) << strerror(errno); EXPECT_EQ(0, memcmp(buf, CONTENTS0, size0)); /* Evict the data cache */ @@ -376,13 +428,27 @@ EXPECT_EQ(0, (intptr_t)thr0_value); /* cache attributes were purged; this will trigger a new GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_GETATTR && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.mode = S_IFREG | 0644; + out.body.attr.attr_valid = UINT64_MAX; + out.body.attr.attr.size = size1; + out.body.attr.attr.uid = uid; + }))); + expect_read(ino, 0, size1, size1, CONTENTS1); ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(uid, sb.st_uid); EXPECT_EQ(size1, sb.st_size); /* This read should not be serviced by cache */ ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); - ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno); + ASSERT_EQ(size1, read(fd, buf, sizeof(buf))) << strerror(errno); EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1)); leak(fd); @@ -441,7 +507,7 @@ EXPECT_EQ(0, (intptr_t)thr0_value); /* This read should be serviced by cache */ - ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno); + ASSERT_EQ(size1, read(fd, buf, sizeof(buf))) << strerror(errno); EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1)); leak(fd); @@ -451,13 +517,16 @@ { const static char FULLPATH[] = "mountpoint/foo"; const static char RELPATH[] = "foo"; - const char CONTENTS[] = "abcdefgh"; + const char CONTENTS0[] = "abcdefgh"; + const char CONTENTS1[] = "ijklmnop"; struct inval_inode_args iia; ino_t ino = 42; void *thr0_value; Sequence seq; pthread_t th0; - ssize_t bufsize = sizeof(CONTENTS); + ssize_t size0 = sizeof(CONTENTS0); + ssize_t size1 = sizeof(CONTENTS1); + char buf[80]; int fd; expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq); @@ -466,9 +535,9 @@ /* Fill the data cache */ fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd); - ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + ASSERT_EQ(size0, write(fd, CONTENTS0, size0)) << strerror(errno); - expect_write(ino, 0, bufsize, CONTENTS); + expect_write(ino, 0, size0, CONTENTS0); /* * The FUSE protocol does not require an fsync here, but FreeBSD's * bufobj_invalbuf sends it anyway @@ -484,6 +553,26 @@ << strerror(errno); pthread_join(th0, &thr0_value); EXPECT_EQ(0, (intptr_t)thr0_value); + + /* + * Reading again should trigger another FUSE_READ because the cache + * is empty. + */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_GETATTR && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.mode = S_IFREG | 0644; + out.body.attr.attr_valid = UINT64_MAX; + out.body.attr.attr.size = size1; + }))); + expect_read(ino, 0, size1, size1, CONTENTS1); + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(size1, read(fd, buf, sizeof(buf))) << strerror(errno); leak(fd); } Index: tests/sys/fs/fusefs/read.cc =================================================================== --- tests/sys/fs/fusefs/read.cc +++ tests/sys/fs/fusefs/read.cc @@ -828,7 +828,7 @@ /* sendfile should fail gracefully if fuse declines the read */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */ -TEST_F(Read, sendfile_eio) +TEST_F(Read, DISABLED_sendfile_eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; Index: tests/sys/fs/fusefs/rename.cc =================================================================== --- tests/sys/fs/fusefs/rename.cc +++ tests/sys/fs/fusefs/rename.cc @@ -53,24 +53,6 @@ FuseTest::TearDown(); } - - void expect_getattr(uint64_t ino, mode_t mode) - { - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in.header.opcode == FUSE_GETATTR && - in.header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke( - ReturnImmediate([=](auto i __unused, auto& out) { - SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid - out.body.attr.attr.mode = mode; - out.body.attr.attr_valid = UINT64_MAX; - }))); - } - }; // EINVAL, dst is subdir of src @@ -82,7 +64,6 @@ const char RELSRC[] = "src"; uint64_t src_ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2); EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); @@ -123,7 +104,6 @@ */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) @@ -158,7 +138,6 @@ uint64_t ino = 42; struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) @@ -196,7 +175,6 @@ tmpfd = mkstemp(tmpfile); ASSERT_LE(0, tmpfd) << strerror(errno); - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2); ASSERT_NE(0, rename(tmpfile, FULLB)); @@ -215,7 +193,6 @@ uint64_t dst_dir_ino = FUSE_ROOT_ID; uint64_t ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) .WillOnce(Invoke(ReturnErrno(ENOENT))); @@ -251,7 +228,6 @@ struct stat sb; expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1); - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR) .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { SET_OUT_HEADER_LEN(out, entry); @@ -303,7 +279,6 @@ uint64_t dst_dir_ino = FUSE_ROOT_ID; uint64_t ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( Index: tests/sys/fs/fusefs/rmdir.cc =================================================================== --- tests/sys/fs/fusefs/rmdir.cc +++ tests/sys/fs/fusefs/rmdir.cc @@ -42,22 +42,6 @@ class Rmdir: public FuseTest { public: -void expect_getattr(uint64_t ino, mode_t mode) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in.header.opcode == FUSE_GETATTR && - in.header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { - SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid - out.body.attr.attr.mode = mode; - out.body.attr.attr_valid = UINT64_MAX; - }))); -} - void expect_lookup(const char *relpath, uint64_t ino, int times=1) { EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) @@ -95,25 +79,34 @@ struct stat sb; sem_t sem; uint64_t ino = 42; + Sequence seq; ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { + return (in.header.opcode == FUSE_RMDIR && + 0 == strcmp(RELPATH, in.body.rmdir) && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).InSequence(seq) + .WillOnce(Invoke(ReturnErrno(0))); + expect_forget(ino, 1, &sem); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { return (in.header.opcode == FUSE_GETATTR && in.header.nodeid == FUSE_ROOT_ID); }, Eq(true)), _) - ).Times(2) + ).InSequence(seq) .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid + out.body.attr.attr.ino = FUSE_ROOT_ID; out.body.attr.attr.mode = S_IFDIR | 0755; out.body.attr.attr_valid = UINT64_MAX; }))); - expect_lookup(RELPATH, ino); - expect_rmdir(FUSE_ROOT_ID, RELPATH, 0); - expect_forget(ino, 1, &sem); ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno); EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); @@ -127,7 +120,6 @@ const char RELPATH[] = "some_dir"; uint64_t ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELPATH, ino); expect_rmdir(FUSE_ROOT_ID, RELPATH, ENOTEMPTY); @@ -143,7 +135,6 @@ sem_t sem; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 2); expect_rmdir(FUSE_ROOT_ID, RELPATH, 0); expect_forget(ino, 1, &sem); @@ -163,7 +154,6 @@ ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELPATH, ino); expect_rmdir(FUSE_ROOT_ID, RELPATH, 0); expect_forget(ino, 1, &sem); Index: tests/sys/fs/fusefs/unlink.cc =================================================================== --- tests/sys/fs/fusefs/unlink.cc +++ tests/sys/fs/fusefs/unlink.cc @@ -41,22 +41,6 @@ class Unlink: public FuseTest { public: -void expect_getattr(uint64_t ino, mode_t mode) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in.header.opcode == FUSE_GETATTR && - in.header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { - SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid - out.body.attr.attr.mode = mode; - out.body.attr.attr_valid = UINT64_MAX; - }))); -} - void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1) { EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) @@ -89,7 +73,6 @@ struct stat sb_old, sb_new; int fd1; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH0, ino, 1, 2); expect_lookup(RELPATH1, ino, 1, 2); expect_open(ino, 0, 1); @@ -117,23 +100,32 @@ const char RELPATH[] = "some_file.txt"; struct stat sb; uint64_t ino = 42; + Sequence seq; + /* Use nlink=2 so we don't get a FUSE_FORGET */ + expect_lookup(RELPATH, ino, 1, 2); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { + return (in.header.opcode == FUSE_UNLINK && + 0 == strcmp(RELPATH, in.body.unlink) && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).InSequence(seq) + .WillOnce(Invoke(ReturnErrno(0))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { return (in.header.opcode == FUSE_GETATTR && in.header.nodeid == FUSE_ROOT_ID); }, Eq(true)), _) - ).Times(2) + ).InSequence(seq) .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid + out.body.attr.attr.ino = FUSE_ROOT_ID; out.body.attr.attr.mode = S_IFDIR | 0755; out.body.attr.attr_valid = UINT64_MAX; }))); - /* Use nlink=2 so we don't get a FUSE_FORGET */ - expect_lookup(RELPATH, ino, 1, 2); - expect_unlink(1, RELPATH, 0); ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); @@ -145,7 +137,6 @@ const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 1); expect_unlink(1, RELPATH, EPERM); @@ -162,7 +153,6 @@ const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 2, 2); expect_unlink(1, RELPATH, 0); @@ -182,7 +172,6 @@ const char RELPATH1[] = "other_file.txt"; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH0, ino, 1, 2); expect_unlink(1, RELPATH0, 0); EXPECT_CALL(*m_mock, process( @@ -213,7 +202,6 @@ ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 1); expect_unlink(1, RELPATH, 0); expect_forget(ino, 1, &sem); @@ -233,7 +221,6 @@ uint64_t ino = 42; int fd; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH0, ino, 2); expect_open(ino, 0, 1); expect_unlink(1, RELPATH0, 0); Index: tests/sys/fs/fusefs/utils.hh =================================================================== --- tests/sys/fs/fusefs/utils.hh +++ tests/sys/fs/fusefs/utils.hh @@ -133,6 +133,12 @@ void expect_getattr(uint64_t ino, uint64_t size); /* + * Create an expectation that FUSE_GETXATTR will be called once for the + * given inode. + */ + void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r); + + /* * Create an expectation that FUSE_LOOKUP will be called for the given * path exactly times times and cache validity period. It will respond * with inode ino, mode mode, filesize size. Index: tests/sys/fs/fusefs/utils.cc =================================================================== --- tests/sys/fs/fusefs/utils.cc +++ tests/sys/fs/fusefs/utils.cc @@ -273,6 +273,20 @@ }))); } +void FuseTest::expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in.body.bytes + + sizeof(fuse_getxattr_in); + return (in.header.opcode == FUSE_GETXATTR && + in.header.nodeid == ino && + 0 == strcmp(attr, a)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid) { Index: tests/sys/fs/fusefs/xattr.cc =================================================================== --- tests/sys/fs/fusefs/xattr.cc +++ tests/sys/fs/fusefs/xattr.cc @@ -62,20 +62,6 @@ class Xattr: public FuseTest { public: -void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - const char *a = (const char*)in.body.bytes + - sizeof(fuse_getxattr_in); - return (in.header.opcode == FUSE_GETXATTR && - in.header.nodeid == ino && - 0 == strcmp(attr, a)); - }, Eq(true)), - _) - ).WillOnce(Invoke(r)); -} - void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r, Sequence *seq = NULL) {