git: a9d0655fa413 - stable/12 - fusefs: fix a cached attributes bug during directory rename

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Sat, 20 Aug 2022 00:31:36 UTC
The branch stable/12 has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=a9d0655fa413ef10592815c9e123e6dc7b412b65

commit a9d0655fa413ef10592815c9e123e6dc7b412b65
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2022-02-22 05:00:42 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2022-08-19 22:44:11 +0000

    fusefs: fix a cached attributes bug during directory rename
    
    When renaming a directory into a different parent directory, invalidate
    the cached attributes of the new parent.  Otherwise, stat will show the
    wrong st_nlink value.
    
    Reviewed by:    ngie
    Differential Revision: https://reviews.freebsd.org/D34336
    
    (cherry picked from commit e8553be9bcfc2c0d78e9f379bd166dc0a9cae719)
---
 sys/fs/fuse/fuse_vnops.c      |  2 +-
 tests/sys/fs/fusefs/rename.cc | 38 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index bd55a21358ba..9e7cd474e29a 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -1647,7 +1647,7 @@ fuse_vnop_rename(struct vop_rename_args *ap)
 		cache_purge(tvp);
 	}
 	if (vnode_isdir(fvp)) {
-		if ((tvp != NULL) && vnode_isdir(tvp)) {
+		if (((tvp != NULL) && vnode_isdir(tvp)) || vnode_isdir(fvp)) {
 			cache_purge(tdvp);
 		}
 		cache_purge(fdvp);
diff --git a/tests/sys/fs/fusefs/rename.cc b/tests/sys/fs/fusefs/rename.cc
index ba1d5ec1ec6d..23d25c9965bf 100644
--- a/tests/sys/fs/fusefs/rename.cc
+++ b/tests/sys/fs/fusefs/rename.cc
@@ -221,7 +221,8 @@ TEST_F(Rename, parent)
 	const char RELDST[] = "dst";
 	const char FULLSRC[] = "mountpoint/src";
 	const char RELSRC[] = "src";
-	const char FULLDSTPARENT[] = "mountpoint/dstdir/dst/..";
+	const char FULLDSTPARENT[] = "mountpoint/dstdir";
+	const char FULLDSTDOTDOT[] = "mountpoint/dstdir/dst/..";
 	Sequence seq;
 	uint64_t dst_dir_ino = 43;
 	uint64_t ino = 42;
@@ -229,13 +230,14 @@ TEST_F(Rename, parent)
 
 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1);
 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
-	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
 		SET_OUT_HEADER_LEN(out, entry);
 		out.body.entry.nodeid = dst_dir_ino;
 		out.body.entry.entry_valid = UINT64_MAX;
 		out.body.entry.attr_valid = UINT64_MAX;
 		out.body.entry.attr.mode = S_IFDIR | 0755;
 		out.body.entry.attr.ino = dst_dir_ino;
+		out.body.entry.attr.nlink = 2;
 	})));
 	EXPECT_LOOKUP(dst_dir_ino, RELDST)
 	.InSequence(seq)
@@ -252,6 +254,31 @@ TEST_F(Rename, parent)
 		}, Eq(true)),
 		_)
 	).WillOnce(Invoke(ReturnErrno(0)));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in.header.opcode == FUSE_GETATTR &&
+				in.header.nodeid == 1);
+		}, Eq(true)),
+		_)
+	).InSequence(seq)
+	.WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
+		SET_OUT_HEADER_LEN(out, attr);
+		out.body.attr.attr_valid = UINT64_MAX;
+		out.body.attr.attr.ino = 1;
+		out.body.attr.attr.mode = S_IFDIR | 0755;
+		out.body.attr.attr.nlink = 2;
+	})));
+	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
+	.InSequence(seq)
+	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+		SET_OUT_HEADER_LEN(out, entry);
+		out.body.entry.nodeid = dst_dir_ino;
+		out.body.entry.entry_valid = UINT64_MAX;
+		out.body.entry.attr_valid = UINT64_MAX;
+		out.body.entry.attr.mode = S_IFDIR | 0755;
+		out.body.entry.attr.ino = dst_dir_ino;
+		out.body.entry.attr.nlink = 3;
+	})));
 	EXPECT_LOOKUP(dst_dir_ino, RELDST)
 	.InSequence(seq)
 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
@@ -263,7 +290,14 @@ TEST_F(Rename, parent)
 	})));
 
 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
+
+	ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
+	EXPECT_EQ(2ul, sb.st_nlink);
+
 	ASSERT_EQ(0, stat(FULLDSTPARENT, &sb)) << strerror(errno);
+	EXPECT_EQ(3ul, sb.st_nlink);
+
+	ASSERT_EQ(0, stat(FULLDSTDOTDOT, &sb)) << strerror(errno);
 	ASSERT_EQ(dst_dir_ino, sb.st_ino);
 }