git: f0ebdb4254e8 - stable/13 - Add vn_rlimit_fsizex() and vn_rlimit_fsizex_res()

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Sat, 08 Oct 2022 00:39:10 UTC
The branch stable/13 has been updated by kib:

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

commit f0ebdb4254e81feb726a58a38a399a95f307f656
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2022-09-18 11:46:19 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2022-10-08 00:29:36 +0000

    Add vn_rlimit_fsizex() and vn_rlimit_fsizex_res()
    
    PR:     164793
    
    (cherry picked from commit 1b4b75171ee3f2213b7671878a910fd5ddb3306e)
---
 sys/kern/vfs_vnops.c | 92 +++++++++++++++++++++++++++++++++++++++++++++-------
 sys/sys/vnode.h      |  3 ++
 2 files changed, 83 insertions(+), 12 deletions(-)

diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
index a7043ae818f5..af4320f44eb5 100644
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -2378,39 +2378,107 @@ vn_rlimit_trunc(u_quad_t size, struct thread *td)
 	return (EFBIG);
 }
 
-int
-vn_rlimit_fsize(const struct vnode *vp, const struct uio *uio,
-    struct thread *td)
+static int
+vn_rlimit_fsizex1(const struct vnode *vp, struct uio *uio, off_t maxfsz,
+    bool adj, struct thread *td)
 {
 	off_t lim;
 	bool ktr_write;
 
-	if (td == NULL)
+	if (vp->v_type != VREG)
+		return (0);
+
+	/*
+	 * Handle file system maximum file size.
+	 */
+	if (maxfsz != 0 && uio->uio_offset + uio->uio_resid > maxfsz) {
+		if (!adj || uio->uio_offset >= maxfsz)
+			return (EFBIG);
+		uio->uio_resid = maxfsz - uio->uio_offset;
+	}
+
+	/*
+	 * This is kernel write (e.g. vnode_pager) or accounting
+	 * write, ignore limit.
+	 */
+	if (td == NULL || (td->td_pflags2 & TDP2_ACCT) != 0)
 		return (0);
 
 	/*
-	 * There are conditions where the limit is to be ignored.
-	 * However, since it is almost never reached, check it first.
+	 * Calculate file size limit.
 	 */
 	ktr_write = (td->td_pflags & TDP_INKTRACE) != 0;
-	lim = lim_cur(td, RLIMIT_FSIZE);
-	if (__predict_false(ktr_write))
-		lim = td->td_ktr_io_lim;
+	lim = __predict_false(ktr_write) ? td->td_ktr_io_lim :
+	    lim_cur(td, RLIMIT_FSIZE);
+
+	/*
+	 * Is the limit reached?
+	 */
 	if (__predict_true((uoff_t)uio->uio_offset + uio->uio_resid <= lim))
 		return (0);
 
 	/*
-	 * The limit is reached.
+	 * Prepared filesystems can handle writes truncated to the
+	 * file size limit.
 	 */
-	if (vp->v_type != VREG ||
-	    (td->td_pflags2 & TDP2_ACCT) != 0)
+	if (adj && (uoff_t)uio->uio_offset < lim) {
+		uio->uio_resid = lim - (uoff_t)uio->uio_offset;
 		return (0);
+	}
 
 	if (!ktr_write || ktr_filesize_limit_signal)
 		vn_send_sigxfsz(td->td_proc);
 	return (EFBIG);
 }
 
+/*
+ * Helper for VOP_WRITE() implementations, the common code to
+ * handle maximum supported file size on the filesystem, and
+ * RLIMIT_FSIZE, except for special writes from accounting subsystem
+ * and ktrace.
+ *
+ * For maximum file size (maxfsz argument):
+ * - return EFBIG if uio_offset is beyond it
+ * - otherwise, clamp uio_resid if write would extend file beyond maxfsz.
+ *
+ * For RLIMIT_FSIZE:
+ * - return EFBIG and send SIGXFSZ if uio_offset is beyond the limit
+ * - otherwise, clamp uio_resid if write would extend file beyond limit.
+ *
+ * If clamping occured, the adjustment for uio_resid is stored in
+ * *resid_adj, to be re-applied by vn_rlimit_fsizex_res() on return
+ * from the VOP.
+ */
+int
+vn_rlimit_fsizex(const struct vnode *vp, struct uio *uio, off_t maxfsz,
+    ssize_t *resid_adj, struct thread *td)
+{
+	ssize_t resid_orig;
+	int error;
+	bool adj;
+
+	resid_orig = uio->uio_resid;
+	adj = resid_adj != NULL;
+	error = vn_rlimit_fsizex1(vp, uio, maxfsz, adj, td);
+	if (adj)
+		*resid_adj = resid_orig - uio->uio_resid;
+	return (error);
+}
+
+void
+vn_rlimit_fsizex_res(struct uio *uio, ssize_t resid_adj)
+{
+	uio->uio_resid += resid_adj;
+}
+
+int
+vn_rlimit_fsize(const struct vnode *vp, const struct uio *uio,
+    struct thread *td)
+{
+	return (vn_rlimit_fsizex(vp, __DECONST(struct uio *, uio), 0, NULL,
+	    td));
+}
+
 int
 vn_chmod(struct file *fp, mode_t mode, struct ucred *active_cred,
     struct thread *td)
diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h
index 599b42b98239..529c69ccf7c7 100644
--- a/sys/sys/vnode.h
+++ b/sys/sys/vnode.h
@@ -779,6 +779,9 @@ int	vn_rdwr_inchunks(enum uio_rw rw, struct vnode *vp, void *base,
 int	vn_read_from_obj(struct vnode *vp, struct uio *uio);
 int	vn_rlimit_fsize(const struct vnode *vp, const struct uio *uio,
 	    struct thread *td);
+int	vn_rlimit_fsizex(const struct vnode *vp, struct uio *uio,
+	    off_t maxfsz, ssize_t *resid_adj, struct thread *td);
+void	vn_rlimit_fsizex_res(struct uio *uio, ssize_t resid_adj);
 int	vn_rlimit_trunc(u_quad_t size, struct thread *td);
 int	vn_start_write(struct vnode *vp, struct mount **mpp, int flags);
 int	vn_start_secondary_write(struct vnode *vp, struct mount **mpp,