git: 0f0a0bdff052 - stable/13 - fusefs: truncate write if it would exceed RLIMIT_FSIZE

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Wed, 12 Oct 2022 04:50:02 UTC
The branch stable/13 has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=0f0a0bdff052e4a61b20f6a0fc252e381c57c3ab

commit 0f0a0bdff052e4a61b20f6a0fc252e381c57c3ab
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2022-09-25 18:59:33 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2022-10-12 04:49:34 +0000

    fusefs: truncate write if it would exceed RLIMIT_FSIZE
    
    PR:             164793
    Reviewed by:    kib
    Differential Revision: https://reviews.freebsd.org/D36703
    
    (cherry picked from commit be280f60dd8e8ef765a76966aac9c6ca7d6264d0)
---
 sys/fs/fuse/fuse_io.c        | 16 ++++++---
 tests/sys/fs/fusefs/write.cc | 78 ++++++++++++++++++++++++++++++++++++++------
 2 files changed, 80 insertions(+), 14 deletions(-)

diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c
index 179ee9e94f43..019093abf75a 100644
--- a/sys/fs/fuse/fuse_io.c
+++ b/sys/fs/fuse/fuse_io.c
@@ -303,6 +303,7 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
 	struct fuse_write_out *fwo;
 	struct fuse_dispatcher fdi;
 	size_t chunksize;
+	ssize_t r;
 	void *fwi_data;
 	off_t as_written_offset;
 	int diff;
@@ -338,9 +339,11 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
 	if (ioflag & IO_APPEND)
 		uio_setoffset(uio, filesize);
 
-	err = vn_rlimit_fsize(vp, uio, uio->uio_td);
-	if (err != 0)
+	err = vn_rlimit_fsizex(vp, uio, 0, &r, uio->uio_td);
+	if (err != 0) {
+		vn_rlimit_fsizex_res(uio, r);
 		return (err);
+	}
 
 	fdisp_init(&fdi, 0);
 
@@ -456,6 +459,7 @@ retry:
 	if (wrote_anything)
 		fuse_vnode_undirty_cached_timestamps(vp, false);
 
+	vn_rlimit_fsizex_res(uio, r);
 	return (err);
 }
 
@@ -472,6 +476,7 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio,
 	struct buf *bp;
 	daddr_t lbn;
 	off_t filesize;
+	ssize_t r;
 	int bcount;
 	int n, on, seqcount, err = 0;
 
@@ -494,9 +499,11 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio,
 	if (ioflag & IO_APPEND)
 		uio_setoffset(uio, filesize);
 
-	err = vn_rlimit_fsize(vp, uio, uio->uio_td);
-	if (err != 0)
+	err = vn_rlimit_fsizex(vp, uio, 0, &r, uio->uio_td);
+	if (err != 0) {
+		vn_rlimit_fsizex_res(uio, r);
 		return (err);
+	}
 
 	do {
 		bool direct_append, extending;
@@ -723,6 +730,7 @@ again:
 			break;
 	} while (uio->uio_resid > 0 && n > 0);
 
+	vn_rlimit_fsizex_res(uio, r);
 	return (err);
 }
 
diff --git a/tests/sys/fs/fusefs/write.cc b/tests/sys/fs/fusefs/write.cc
index d685bd13aa17..4e76414a601a 100644
--- a/tests/sys/fs/fusefs/write.cc
+++ b/tests/sys/fs/fusefs/write.cc
@@ -52,10 +52,7 @@ using namespace testing;
 class Write: public FuseTest {
 
 public:
-static sig_atomic_t s_sigxfsz;
-
 void SetUp() {
-	s_sigxfsz = 0;
 	FuseTest::SetUp();
 }
 
@@ -118,8 +115,6 @@ void maybe_expect_write(uint64_t ino, uint64_t offset, uint64_t size,
 
 };
 
-sig_atomic_t Write::s_sigxfsz = 0;
-
 class Write_7_8: public FuseTest {
 
 public:
@@ -211,8 +206,28 @@ virtual void SetUp() {
 class WriteEofDuringVnopStrategy: public Write, public WithParamInterface<int>
 {};
 
+class WriteRlimitFsize: public Write, public WithParamInterface<int> {
+public:
+static sig_atomic_t s_sigxfsz;
+struct rlimit	m_initial_limit;
+
+void SetUp() {
+	s_sigxfsz = 0;
+	getrlimit(RLIMIT_FSIZE, &m_initial_limit);
+	FuseTest::SetUp();
+}
+
+void TearDown() {
+	setrlimit(RLIMIT_FSIZE, &m_initial_limit);
+
+	FuseTest::TearDown();
+}
+};
+
+sig_atomic_t WriteRlimitFsize::s_sigxfsz = 0;
+
 void sigxfsz_handler(int __unused sig) {
-	Write::s_sigxfsz = 1;
+	WriteRlimitFsize::s_sigxfsz = 1;
 }
 
 /* AIO writes need to set the header's pid field correctly */
@@ -531,7 +546,7 @@ TEST_F(Write, direct_io_short_write_iov)
 }
 
 /* fusefs should respect RLIMIT_FSIZE */
-TEST_F(Write, rlimit_fsize)
+TEST_P(WriteRlimitFsize, rlimit_fsize)
 {
 	const char FULLPATH[] = "mountpoint/some_file.txt";
 	const char RELPATH[] = "some_file.txt";
@@ -540,17 +555,19 @@ TEST_F(Write, rlimit_fsize)
 	ssize_t bufsize = strlen(CONTENTS);
 	off_t offset = 1'000'000'000;
 	uint64_t ino = 42;
-	int fd;
+	int fd, oflag;
+
+	oflag = GetParam();
 
 	expect_lookup(RELPATH, ino, 0);
 	expect_open(ino, 0, 1);
 
 	rl.rlim_cur = offset;
-	rl.rlim_max = 10 * offset;
+	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);
 
-	fd = open(FULLPATH, O_WRONLY);
+	fd = open(FULLPATH, O_WRONLY | oflag);
 
 	ASSERT_LE(0, fd) << strerror(errno);
 
@@ -560,6 +577,47 @@ TEST_F(Write, rlimit_fsize)
 	leak(fd);
 }
 
+/*
+ * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
+ * aborted.
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=164793
+ */
+TEST_P(WriteRlimitFsize, rlimit_fsize_truncate)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
+	struct rlimit rl;
+	ssize_t bufsize = strlen(CONTENTS);
+	uint64_t ino = 42;
+	off_t offset = 1 << 30;
+	off_t limit = offset + strlen(CONTENTS) / 2;
+	int fd, oflag;
+
+	oflag = GetParam();
+
+	expect_lookup(RELPATH, ino, 0);
+	expect_open(ino, 0, 1);
+	expect_write(ino, offset, bufsize / 2, bufsize / 2, CONTENTS);
+
+	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);
+
+	fd = open(FULLPATH, O_WRONLY | oflag);
+
+	ASSERT_LE(0, fd) << strerror(errno);
+
+	ASSERT_EQ(bufsize / 2, pwrite(fd, CONTENTS, bufsize, offset))
+		<< strerror(errno);
+	leak(fd);
+}
+
+INSTANTIATE_TEST_CASE_P(W, WriteRlimitFsize,
+	Values(0, O_DIRECT)
+);
+
 /* 
  * A short read indicates EOF.  Test that nothing bad happens if we get EOF
  * during the R of a RMW operation.