The branch main has been updated by asomers:

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

commit 52360ca32ff90b605ac7481fd79e6a251e8b5116
Author:     Alan Somers <asom...@freebsd.org>
AuthorDate: 2022-09-25 22:53:36 +0000
Commit:     Alan Somers <asom...@freebsd.org>
CommitDate: 2022-09-26 21:22:29 +0000

    copy_file_range: truncate write if it would exceed RLIMIT_FSIZE
    
    PR:             266611
    MFC after:      2 weeks
    Reviewed by:    kib
    Differential Revision: https://reviews.freebsd.org/D36706
---
 sys/fs/fuse/fuse_vnops.c               |  15 +++--
 sys/fs/nfsclient/nfs_clvnops.c         |   8 ++-
 sys/kern/vfs_vnops.c                   |  14 ++--
 tests/sys/fs/fusefs/copy_file_range.cc | 113 +++++++++++++++++++++++++--------
 4 files changed, 115 insertions(+), 35 deletions(-)

diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index 6fa9a2b248c6..f96fc95dba30 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -811,6 +811,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args 
*ap)
        struct thread *td;
        struct uio io;
        off_t outfilesize;
+       ssize_t r = 0;
        pid_t pid;
        int err;
 
@@ -858,11 +859,11 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args 
*ap)
        if (err)
                goto unlock;
 
+       io.uio_resid = *ap->a_lenp;
        if (ap->a_fsizetd) {
                io.uio_offset = *ap->a_outoffp;
-               io.uio_resid = *ap->a_lenp;
-               err = vn_rlimit_fsize(outvp, &io, ap->a_fsizetd);
-               if (err)
+               err = vn_rlimit_fsizex(outvp, &io, 0, &r, ap->a_fsizetd);
+               if (err != 0)
                        goto unlock;
        }
 
@@ -871,7 +872,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args 
*ap)
                goto unlock;
 
        err = fuse_inval_buf_range(outvp, outfilesize, *ap->a_outoffp,
-               *ap->a_outoffp + *ap->a_lenp);
+               *ap->a_outoffp + io.uio_resid);
        if (err)
                goto unlock;
 
@@ -883,7 +884,7 @@ fuse_vnop_copy_file_range(struct vop_copy_file_range_args 
*ap)
        fcfri->nodeid_out = VTOI(outvp);
        fcfri->fh_out = outfufh->fh_id;
        fcfri->off_out = *ap->a_outoffp;
-       fcfri->len = *ap->a_lenp;
+       fcfri->len = io.uio_resid;
        fcfri->flags = 0;
 
        err = fdisp_wait_answ(&fdi);
@@ -915,6 +916,10 @@ fallback:
                    ap->a_incred, ap->a_outcred, ap->a_fsizetd);
        }
 
+       /*
+        * No need to call vn_rlimit_fsizex_res before return, since the uio is
+        * local.
+        */
        return (err);
 }
 
diff --git a/sys/fs/nfsclient/nfs_clvnops.c b/sys/fs/nfsclient/nfs_clvnops.c
index 817c1093374c..4c6a3b527049 100644
--- a/sys/fs/nfsclient/nfs_clvnops.c
+++ b/sys/fs/nfsclient/nfs_clvnops.c
@@ -3889,6 +3889,7 @@ nfs_copy_file_range(struct vop_copy_file_range_args *ap)
        struct uio io;
        struct nfsmount *nmp;
        size_t len, len2;
+       ssize_t r;
        int error, inattrflag, outattrflag, ret, ret2;
        off_t inoff, outoff;
        bool consecutive, must_commit, tryoutcred;
@@ -3937,7 +3938,12 @@ nfs_copy_file_range(struct vop_copy_file_range_args *ap)
         */
        io.uio_offset = *ap->a_outoffp;
        io.uio_resid = *ap->a_lenp;
-       error = vn_rlimit_fsize(outvp, &io, ap->a_fsizetd);
+       error = vn_rlimit_fsizex(outvp, &io, 0, &r, ap->a_fsizetd);
+       *ap->a_lenp = io.uio_resid;
+       /*
+        * No need to call vn_rlimit_fsizex_res before return, since the uio is
+        * local.
+        */
 
        /*
         * Flush the input file so that the data is up to date before
diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
index 21f3f99a6741..1c7db3852776 100644
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -3261,12 +3261,11 @@ vn_generic_copy_file_range(struct vnode *invp, off_t 
*inoffp,
 {
        struct vattr va, inva;
        struct mount *mp;
-       struct uio io;
        off_t startoff, endoff, xfer, xfer2;
        u_long blksize;
        int error, interrupted;
        bool cantseek, readzeros, eof, lastblock, holetoeof;
-       ssize_t aresid;
+       ssize_t aresid, r = 0;
        size_t copylen, len, rem, savlen;
        char *dat;
        long holein, holeout;
@@ -3295,13 +3294,20 @@ vn_generic_copy_file_range(struct vnode *invp, off_t 
*inoffp,
                error = vn_lock(outvp, LK_EXCLUSIVE);
        if (error == 0) {
                /*
-                * If fsize_td != NULL, do a vn_rlimit_fsize() call,
+                * If fsize_td != NULL, do a vn_rlimit_fsizex() call,
                 * now that outvp is locked.
                 */
                if (fsize_td != NULL) {
+                       struct uio io;
+
                        io.uio_offset = *outoffp;
                        io.uio_resid = len;
-                       error = vn_rlimit_fsize(outvp, &io, fsize_td);
+                       error = vn_rlimit_fsizex(outvp, &io, 0, &r, fsize_td);
+                       len = savlen = io.uio_resid;
+                       /*
+                        * No need to call vn_rlimit_fsizex_res before return,
+                        * since the uio is local.
+                        */
                }
                if (VOP_PATHCONF(outvp, _PC_MIN_HOLE_SIZE, &holeout) != 0)
                        holeout = 0;
diff --git a/tests/sys/fs/fusefs/copy_file_range.cc 
b/tests/sys/fs/fusefs/copy_file_range.cc
index 7e1189648de3..84101ca6c984 100644
--- a/tests/sys/fs/fusefs/copy_file_range.cc
+++ b/tests/sys/fs/fusefs/copy_file_range.cc
@@ -44,22 +44,6 @@ using namespace testing;
 
 class CopyFileRange: public FuseTest {
 public:
-static sig_atomic_t s_sigxfsz;
-
-void SetUp() {
-       s_sigxfsz = 0;
-       FuseTest::SetUp();
-}
-
-void TearDown() {
-       struct sigaction sa;
-
-       bzero(&sa, sizeof(sa));
-       sa.sa_handler = SIG_DFL;
-       sigaction(SIGXFSZ, &sa, NULL);
-
-       FuseTest::TearDown();
-}
 
 void expect_maybe_lseek(uint64_t ino)
 {
@@ -114,12 +98,6 @@ void expect_write(uint64_t ino, uint64_t offset, uint64_t 
isize,
 
 };
 
-sig_atomic_t CopyFileRange::s_sigxfsz = 0;
-
-void sigxfsz_handler(int __unused sig) {
-       CopyFileRange::s_sigxfsz = 1;
-}
-
 
 class CopyFileRange_7_27: public CopyFileRange {
 public:
@@ -137,6 +115,37 @@ virtual void SetUp() {
 }
 };
 
+class CopyFileRangeRlimitFsize: public CopyFileRange {
+public:
+static sig_atomic_t s_sigxfsz;
+struct rlimit  m_initial_limit;
+
+virtual void SetUp() {
+       s_sigxfsz = 0;
+       getrlimit(RLIMIT_FSIZE, &m_initial_limit);
+       CopyFileRange::SetUp();
+}
+
+void TearDown() {
+       struct sigaction sa;
+
+       setrlimit(RLIMIT_FSIZE, &m_initial_limit);
+
+       bzero(&sa, sizeof(sa));
+       sa.sa_handler = SIG_DFL;
+       sigaction(SIGXFSZ, &sa, NULL);
+
+       FuseTest::TearDown();
+}
+
+};
+
+sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;
+
+void sigxfsz_handler(int __unused sig) {
+       CopyFileRangeRlimitFsize::s_sigxfsz = 1;
+}
+
 TEST_F(CopyFileRange, eio)
 {
        const char FULLPATH1[] = "mountpoint/src.txt";
@@ -313,8 +322,11 @@ TEST_F(CopyFileRange, fallback)
        ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
 }
 
-/* fusefs should respect RLIMIT_FSIZE */
-TEST_F(CopyFileRange, rlimit_fsize)
+/*
+ * copy_file_range should send SIGXFSZ and return EFBIG when the operation
+ * would exceed the limit imposed by RLIMIT_FSIZE.
+ */
+TEST_F(CopyFileRangeRlimitFsize, signal)
 {
        const char FULLPATH1[] = "mountpoint/src.txt";
        const char RELPATH1[] = "src.txt";
@@ -344,7 +356,7 @@ TEST_F(CopyFileRange, rlimit_fsize)
        ).Times(0);
 
        rl.rlim_cur = fsize2;
-       rl.rlim_max = 10 * fsize2;
+       rl.rlim_max = m_initial_limit.rlim_max;
        ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
        ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
 
@@ -355,6 +367,57 @@ TEST_F(CopyFileRange, rlimit_fsize)
        EXPECT_EQ(1, s_sigxfsz);
 }
 
+/*
+ * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
+ * aborted.
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611
+ */
+TEST_F(CopyFileRangeRlimitFsize, truncate)
+{
+       const char FULLPATH1[] = "mountpoint/src.txt";
+       const char RELPATH1[] = "src.txt";
+       const char FULLPATH2[] = "mountpoint/dst.txt";
+       const char RELPATH2[] = "dst.txt";
+       struct rlimit rl;
+       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 = fsize2;
+       ssize_t len = 65536;
+       off_t limit = start2 + len / 2;
+       int fd1, fd2;
+
+       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_CALL(*m_mock, process(
+               ResultOf([=](auto in) {
+                       return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+                               (off_t)in.body.copy_file_range.off_out == 
start2 &&
+                               in.body.copy_file_range.len == (size_t)len / 2
+                       );
+               }, Eq(true)),
+               _)
+       ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+               SET_OUT_HEADER_LEN(out, write);
+               out.body.write.size = len / 2;
+       })));
+
+       rl.rlim_cur = limit;
+       rl.rlim_max = m_initial_limit.rlim_max;
+       ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
+       ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
+
+       fd1 = open(FULLPATH1, O_RDONLY);
+       fd2 = open(FULLPATH2, O_WRONLY);
+       ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
 TEST_F(CopyFileRange, ok)
 {
        const char FULLPATH1[] = "mountpoint/src.txt";

Reply via email to