From nobody Mon Feb 12 17:43:45 2024 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4TYWz53rKBz5BDJY; Mon, 12 Feb 2024 17:43:45 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4TYWz53Jynz4VRj; Mon, 12 Feb 2024 17:43:45 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1707759825; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jsWpPX2tFIHxfXen1xppNIJ7ITjGgVs2T4kBABiIUzY=; b=NWxnHDCDnkNADkV2ADuOB/lVnwVOhLQk3f5cCRDxfFnFKpLM8LPW3BDC7pFh4YebLJf+lg AWoEvF/3VpeFbhr1gQRsc/Vb+SZt6KWuC6kP4ncgoUqHVQQF+j7PV7oPE4HpmSrLDpxvgd zFx5TvfBsoIMHn5SfkNDv/KUE0j8MK8jAJIXvosCOwNo8ufFl5OlpT+U1LAJFh6ErKo7Jz 6qU84//UDxD2HgqpmsLkZXrDsBzwJId5e23hHlSLiAIoz9Z2h0up98h9lhobROmFupBPvX c3vu+P9mtF086xJoP6KHB2kA79DpotK+IwiS6malfkP8r3T3snl36LMXdfT4rw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1707759825; a=rsa-sha256; cv=none; b=tQgMHyfeFw/zpUCMO94UtOBNrRcm6in55enZjcL06xZkBgqlYyA54DLWsIOWQmfxubVYxl Vd9nuFZ/IObpnaXjq+xr2EhXt+LseqhPyITWKeZdXma3c9lpYLQC1D9XgpE0LgMzQRj8SK LXnXcY9774cbTxePoeGf5FtT11ZsE57y4j2UwY+/NOkrgYuxX8tfi6JgCHALsOJ3Mv8Nwm RFNoEz4nkUe7yfB0RSBmcvtg97VcJ8MQhnz2K3v6nBwK1WBRAQHbnaNCUoDe6jbdUjBlwN Hx+kjNizab1aHovJw8YMX9YBl+RLCDKIsF+6Gif+fpTRR8IkIwvJIYNpDVisvA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1707759825; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jsWpPX2tFIHxfXen1xppNIJ7ITjGgVs2T4kBABiIUzY=; b=G4+Npn5dkvDFBkDQY81DuNufK/h08l/fRpf3D3szOgRg3sIKQULV8gXVbVoSOl8iCOa2BV kratHf5cn3etqe+sqe8D9b3PXYBgK53D6PJ9ggKLM2zbcALelZOo4LyD/l6IlxMtxx+kyb fyT6wCR5p5YUlj4HP8iN96cx1B6MZb8Bw6ygtf6qYGkdpgzkvEsLfJYnnOMglYHh6CwIm8 J1lQad3BGAeoMPOeZqPRqeBOEh/WGmGAKhGcFoYTolgyvG7djqGhVgYzmJlzaVlNFKXDLx zlxkQyXc5nFLYse9EVIRfXbeuIJG2rANVmS1XHvNi1nzBMd+7OBznhKLLGdeCw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4TYWz52MKrzhFw; Mon, 12 Feb 2024 17:43:45 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 41CHhjAa012173; Mon, 12 Feb 2024 17:43:45 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 41CHhj7G012170; Mon, 12 Feb 2024 17:43:45 GMT (envelope-from git) Date: Mon, 12 Feb 2024 17:43:45 GMT Message-Id: <202402121743.41CHhj7G012170@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Alan Somers Subject: git: 9826f8eb0cca - stable/14 - fusefs: fix an interaction between copy_file_range and mmap List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: asomers X-Git-Repository: src X-Git-Refname: refs/heads/stable/14 X-Git-Reftype: branch X-Git-Commit: 9826f8eb0cca1a755d411174c24222c3588881d9 Auto-Submitted: auto-generated The branch stable/14 has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=9826f8eb0cca1a755d411174c24222c3588881d9 commit 9826f8eb0cca1a755d411174c24222c3588881d9 Author: Alan Somers AuthorDate: 2023-12-31 14:31:16 +0000 Commit: Alan Somers CommitDate: 2024-02-12 17:42:46 +0000 fusefs: fix an interaction between copy_file_range and mmap If a copy_file_range operation tries to read from a page that was previously written via mmap, that page must be flushed first. Reviewed by: kib Differential Revision: https://reviews.freebsd.org/D43451 (cherry picked from commit 1c909c300b92601f7690610097ac98126caff835) --- sys/fs/fuse/fuse_vnops.c | 1 + tests/sys/fs/fusefs/copy_file_range.cc | 68 +++++++++++++++++++++++++ tests/sys/fs/fusefs/io.cc | 90 ++++++++++++++++++++++++++++++++-- 3 files changed, 154 insertions(+), 5 deletions(-) diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 9728afb66654..b5e177dac376 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -907,6 +907,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap) if (err) goto unlock; + vnode_pager_clean_sync(invp); err = fuse_inval_buf_range(outvp, outfilesize, *ap->a_outoffp, *ap->a_outoffp + io.uio_resid); if (err) diff --git a/tests/sys/fs/fusefs/copy_file_range.cc b/tests/sys/fs/fusefs/copy_file_range.cc index 8640780c0b58..17b21b888736 100644 --- a/tests/sys/fs/fusefs/copy_file_range.cc +++ b/tests/sys/fs/fusefs/copy_file_range.cc @@ -27,6 +27,7 @@ extern "C" { #include +#include #include #include @@ -320,6 +321,73 @@ TEST_F(CopyFileRange, fallback) ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); } +/* + * Writes via mmap should not conflict with using copy_file_range. Any dirty + * pages that overlap with copy_file_range's input should be flushed before + * FUSE_COPY_FILE_RANGE is sent. + */ +TEST_F(CopyFileRange, mmap_write) +{ + const char FULLPATH[] = "mountpoint/src.txt"; + const char RELPATH[] = "src.txt"; + uint8_t *wbuf, *fbuf; + void *p; + size_t fsize = 0x6000; + size_t wsize = 0x3000; + ssize_t r; + off_t offset2_in = 0; + off_t offset2_out = wsize; + size_t copysize = wsize; + const uint64_t ino = 42; + const uint64_t fh = 0xdeadbeef1a7ebabe; + int fd; + const mode_t mode = 0644; + + fbuf = (uint8_t*)calloc(1, fsize); + wbuf = (uint8_t*)malloc(wsize); + memset(wbuf, 1, wsize); + + expect_lookup(RELPATH, ino, S_IFREG | mode, fsize, 1); + expect_open(ino, 0, 1, fh); + /* This read is initiated by the mmap write */ + expect_read(ino, 0, fsize, fsize, fbuf, -1, fh); + /* This write flushes the buffer filled by the mmap write */ + expect_write(ino, 0, wsize, wsize, wbuf); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_COPY_FILE_RANGE && + (off_t)in.body.copy_file_range.off_in == offset2_in && + (off_t)in.body.copy_file_range.off_out == offset2_out && + in.body.copy_file_range.len == copysize + ); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, write); + out.body.write.size = copysize; + }))); + + fd = open(FULLPATH, O_RDWR); + + /* First, write some data via mmap */ + p = mmap(NULL, wsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_NE(MAP_FAILED, p) << strerror(errno); + memmove((uint8_t*)p, wbuf, wsize); + ASSERT_EQ(0, munmap(p, wsize)) << strerror(errno); + + /* + * Then copy it around the file via copy_file_range. This should + * trigger a FUSE_WRITE to flush the pages written by mmap. + */ + r = copy_file_range(fd, &offset2_in, fd, &offset2_out, copysize, 0); + ASSERT_EQ(copysize, (size_t)r) << strerror(errno); + + free(wbuf); + free(fbuf); +} + + /* * copy_file_range should send SIGXFSZ and return EFBIG when the operation * would exceed the limit imposed by RLIMIT_FSIZE. diff --git a/tests/sys/fs/fusefs/io.cc b/tests/sys/fs/fusefs/io.cc index fda13a72cc4c..357772c31c2c 100644 --- a/tests/sys/fs/fusefs/io.cc +++ b/tests/sys/fs/fusefs/io.cc @@ -73,7 +73,7 @@ static void compare(const void *tbuf, const void *controlbuf, off_t baseofs, } } -typedef tuple IoParam; +typedef tuple IoParam; class Io: public FuseTest, public WithParamInterface { public: @@ -112,6 +112,7 @@ void SetUp() default: FAIL() << "Unknown cache mode"; } + m_kernel_minor_version = get<3>(GetParam()); m_noatime = true; // To prevent SETATTR for atime on close FuseTest::SetUp(); @@ -120,9 +121,10 @@ void SetUp() if (verbosity > 0) { printf("Test Parameters: init_flags=%#x maxwrite=%#x " - "%sasync cache=%s\n", + "%sasync cache=%s kernel_minor_version=%d\n", m_init_flags, m_maxwrite, m_async? "" : "no", - cache_mode_to_s(get<2>(GetParam()))); + cache_mode_to_s(get<2>(GetParam())), + m_kernel_minor_version); } expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); @@ -195,6 +197,30 @@ void SetUp() }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnErrno(0))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_COPY_FILE_RANGE && + in.header.nodeid == ino && + in.body.copy_file_range.nodeid_out == ino && + in.body.copy_file_range.flags == 0); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto& out) { + off_t off_in = in.body.copy_file_range.off_in; + off_t off_out = in.body.copy_file_range.off_out; + ASSERT_EQ((ssize_t)in.body.copy_file_range.len, + copy_file_range(m_backing_fd, &off_in, m_backing_fd, + &off_out, in.body.copy_file_range.len, 0)); + SET_OUT_HEADER_LEN(out, write); + out.body.write.size = in.body.copy_file_range.len; + }))); + /* Claim that we don't support FUSE_LSEEK */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_LSEEK); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnErrno(ENOSYS))); m_test_fd = open(FULLPATH, O_RDWR ); EXPECT_LE(0, m_test_fd) << strerror(errno); @@ -223,6 +249,31 @@ void do_closeopen() ASSERT_LE(0, m_control_fd) << strerror(errno); } +void do_copy_file_range(off_t off_in, off_t off_out, size_t size) +{ + ssize_t r; + off_t test_off_in = off_in; + off_t test_off_out = off_out; + off_t test_size = size; + off_t control_off_in = off_in; + off_t control_off_out = off_out; + off_t control_size = size; + + while (test_size > 0) { + r = copy_file_range(m_test_fd, &test_off_in, m_test_fd, + &test_off_out, test_size, 0); + ASSERT_GT(r, 0) << strerror(errno); + test_size -= r; + } + while (control_size > 0) { + r = copy_file_range(m_control_fd, &control_off_in, m_control_fd, + &control_off_out, control_size, 0); + ASSERT_GT(r, 0) << strerror(errno); + control_size -= r; + } + m_filesize = std::max(m_filesize, off_out + (off_t)size); +} + void do_ftruncate(off_t offs) { ASSERT_EQ(0, ftruncate(m_test_fd, offs)) << strerror(errno); @@ -345,6 +396,13 @@ virtual void SetUp() { } }; +class IoCopyFileRange: public Io { +public: +virtual void SetUp() { + Io::SetUp(); +} +}; + /* * Extend a file with dirty data in the last page of the last block. * @@ -517,16 +575,38 @@ TEST_P(IoCacheable, vnode_pager_generic_putpage_clean_block_at_eof) do_mapwrite(0x1bbc3, 0x3b4e0); } +/* + * A copy_file_range that follows an mmap write to the input area needs to + * flush the mmap buffer first. + */ +TEST_P(IoCopyFileRange, copy_file_range_from_mapped_write) +{ + do_mapwrite(0x1000, 0); + do_copy_file_range(0, 0x1000, 0x1000); + do_read(0x1000, 0x1000); +} + + INSTANTIATE_TEST_SUITE_P(Io, Io, Combine(Bool(), /* async read */ Values(0x1000, 0x10000, 0x20000), /* m_maxwrite */ - Values(Uncached, Writethrough, Writeback, WritebackAsync) + Values(Uncached, Writethrough, Writeback, WritebackAsync), + Values(28) /* kernel_minor_vers */ ) ); INSTANTIATE_TEST_SUITE_P(Io, IoCacheable, Combine(Bool(), /* async read */ Values(0x1000, 0x10000, 0x20000), /* m_maxwrite */ - Values(Writethrough, Writeback, WritebackAsync) + Values(Writethrough, Writeback, WritebackAsync), + Values(28) /* kernel_minor_vers */ + ) +); + +INSTANTIATE_TEST_SUITE_P(Io, IoCopyFileRange, + Combine(Values(true), /* async read */ + Values(0x10000), /* m_maxwrite */ + Values(Writethrough, Writeback, WritebackAsync), + Values(27, 28) /* kernel_minor_vers */ ) );