aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Somers <asomers@FreeBSD.org>2021-12-05 20:39:10 +0000
committerAlan Somers <asomers@FreeBSD.org>2021-12-07 04:41:50 +0000
commit41ae9f9e644d1196bebacdb3748670f36b354384 (patch)
treeec743db32013a33569ead59e4023c9baf1810d05
parentdc433e1530af26b0430d66c06c342889e9609590 (diff)
downloadsrc-41ae9f9e644d1196bebacdb3748670f36b354384.tar.gz
src-41ae9f9e644d1196bebacdb3748670f36b354384.zip
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
-rw-r--r--sys/fs/fuse/fuse_vnops.c10
-rw-r--r--tests/sys/fs/fusefs/copy_file_range.cc78
-rw-r--r--tests/sys/fs/fusefs/utils.cc4
-rw-r--r--tests/sys/fs/fusefs/utils.hh3
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
@@ -172,6 +172,84 @@ TEST_F(CopyFileRange, eio)
}
/*
+ * 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