The branch stable/13 has been updated by asomers:

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

commit 642399932d58e880ac4a65ab6011a94292b37134
Author:     Alan Somers <[email protected]>
AuthorDate: 2021-12-05 20:39:10 +0000
Commit:     Alan Somers <[email protected]>
CommitDate: 2022-01-03 03:03:54 +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
    Reviewed by:    pfg
    Differential Revision: https://reviews.freebsd.org/D33280
    
    (cherry picked from commit 41ae9f9e644d1196bebacdb3748670f36b354384)
---
 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 36507fae4d59..7fe3f8271f4f 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

Reply via email to