git: 41ae9f9e644d - main - fusefs: invalidate the cache during copy_file_range

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Tue, 07 Dec 2021 04:42:25 UTC
The branch main has been updated by asomers:

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

commit 41ae9f9e644d1196bebacdb3748670f36b354384
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2021-12-05 20:39:10 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2021-12-07 04:41:50 +0000

    fusefs: invalidate the cache during copy_file_range
    
    FUSE_COPY_FILE_RANGE instructs the server to write data to a file.
    fusefs must invalidate any cached data within the written range.
    
    PR:             260242
    MFC after:      2 weeks
    Reviewed by:    pfg
    Differential Revision: https://reviews.freebsd.org/D33280
---
 sys/fs/fuse/fuse_vnops.c               | 10 +++++
 tests/sys/fs/fusefs/copy_file_range.cc | 78 ++++++++++++++++++++++++++++++++++
 tests/sys/fs/fusefs/utils.cc           |  4 +-
 tests/sys/fs/fusefs/utils.hh           |  3 +-
 4 files changed, 92 insertions(+), 3 deletions(-)

diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index 30819b436fee..67c08f6ee47c 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -720,6 +720,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
 	struct fuse_write_out *fwo;
 	struct thread *td;
 	struct uio io;
+	off_t outfilesize;
 	pid_t pid;
 	int err;
 
@@ -775,6 +776,15 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
 			goto unlock;
 	}
 
+	err = fuse_vnode_size(outvp, &outfilesize, outcred, curthread);
+	if (err)
+		goto unlock;
+
+	err = fuse_inval_buf_range(outvp, outfilesize, *ap->a_outoffp,
+		*ap->a_outoffp + *ap->a_lenp);
+	if (err)
+		goto unlock;
+
 	fdisp_init(&fdi, sizeof(*fcfri));
 	fdisp_make_vp(&fdi, FUSE_COPY_FILE_RANGE, invp, td, incred);
 	fcfri = fdi.indata;
diff --git a/tests/sys/fs/fusefs/copy_file_range.cc b/tests/sys/fs/fusefs/copy_file_range.cc
index 03a892d35d29..a9dc9679cb6a 100644
--- a/tests/sys/fs/fusefs/copy_file_range.cc
+++ b/tests/sys/fs/fusefs/copy_file_range.cc
@@ -171,6 +171,84 @@ TEST_F(CopyFileRange, eio)
 	EXPECT_EQ(EIO, errno);
 }
 
+/*
+ * copy_file_range should evict cached data for the modified region of the
+ * destination file.
+ */
+TEST_F(CopyFileRange, evicts_cache)
+{
+	const char FULLPATH1[] = "mountpoint/src.txt";
+	const char RELPATH1[] = "src.txt";
+	const char FULLPATH2[] = "mountpoint/dst.txt";
+	const char RELPATH2[] = "dst.txt";
+	void *buf0, *buf1, *buf;
+	const uint64_t ino1 = 42;
+	const uint64_t ino2 = 43;
+	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+	const uint64_t fh2 = 0xdeadc0de88c0ffee;
+	off_t fsize1 = 1 << 20;		/* 1 MiB */
+	off_t fsize2 = 1 << 19;		/* 512 KiB */
+	off_t start1 = 1 << 18;
+	off_t start2 = 3 << 17;
+	ssize_t len = m_maxbcachebuf;
+	int fd1, fd2;
+
+	buf0 = malloc(m_maxbcachebuf);
+	memset(buf0, 42, m_maxbcachebuf);
+
+	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+	expect_open(ino1, 0, 1, fh1);
+	expect_open(ino2, 0, 1, fh2);
+	expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1,
+		fh2);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+				in.header.nodeid == ino1 &&
+				in.body.copy_file_range.fh_in == fh1 &&
+				(off_t)in.body.copy_file_range.off_in == start1 &&
+				in.body.copy_file_range.nodeid_out == ino2 &&
+				in.body.copy_file_range.fh_out == fh2 &&
+				(off_t)in.body.copy_file_range.off_out == start2 &&
+				in.body.copy_file_range.len == (size_t)len &&
+				in.body.copy_file_range.flags == 0);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+		SET_OUT_HEADER_LEN(out, write);
+		out.body.write.size = len;
+	})));
+
+	fd1 = open(FULLPATH1, O_RDONLY);
+	fd2 = open(FULLPATH2, O_RDWR);
+
+	// Prime cache
+	buf = malloc(m_maxbcachebuf);
+	ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
+		<< strerror(errno);
+	EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));
+
+	// Tell the FUSE server overwrite the region we just read
+	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+
+	// Read again.  This should bypass the cache and read direct from server
+	buf1 = malloc(m_maxbcachebuf);
+	memset(buf1, 69, m_maxbcachebuf);
+	start2 -= len;
+	expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,
+		fh2);
+	ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
+		<< strerror(errno);
+	EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));
+
+	free(buf1);
+	free(buf0);
+	free(buf);
+	leak(fd1);
+	leak(fd2);
+}
+
 /*
  * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
  * fallback to a read/write based implementation.
diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc
index 16dfc9c52939..f733fef7ebe0 100644
--- a/tests/sys/fs/fusefs/utils.cc
+++ b/tests/sys/fs/fusefs/utils.cc
@@ -367,13 +367,13 @@ void FuseTest::expect_opendir(uint64_t ino)
 }
 
 void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
-	uint64_t osize, const void *contents, int flags)
+	uint64_t osize, const void *contents, int flags, uint64_t fh)
 {
 	EXPECT_CALL(*m_mock, process(
 		ResultOf([=](auto in) {
 			return (in.header.opcode == FUSE_READ &&
 				in.header.nodeid == ino &&
-				in.body.read.fh == FH &&
+				in.body.read.fh == fh &&
 				in.body.read.offset == offset &&
 				in.body.read.size == isize &&
 				(flags == -1 ?
diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh
index a6f1d63ada6b..6f1f91b02c97 100644
--- a/tests/sys/fs/fusefs/utils.hh
+++ b/tests/sys/fs/fusefs/utils.hh
@@ -175,7 +175,8 @@ class FuseTest : public ::testing::Test {
 	 * nothing currently validates the size of the fuse_read_in struct.
 	 */
 	void expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
-		uint64_t osize, const void *contents, int flags = -1);
+		uint64_t osize, const void *contents, int flags = -1,
+		uint64_t fh = FH);
 
 	/*
 	 * Create an expectation that FUSE_READIR will be called any number of