aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Belousov <kib@FreeBSD.org>2022-09-18 11:46:19 +0000
committerKonstantin Belousov <kib@FreeBSD.org>2022-09-24 16:41:33 +0000
commit1b4b75171ee3f2213b7671878a910fd5ddb3306e (patch)
treee917be73c4eeaa546591ae11d0bfa2a19a068dfc
parentb5b16659c5aceb9caa0a9b76c7746e1d12a505ce (diff)
downloadsrc-1b4b75171ee3f2213b7671878a910fd5ddb3306e.tar.gz
src-1b4b75171ee3f2213b7671878a910fd5ddb3306e.zip
Add vn_rlimit_fsizex() and vn_rlimit_fsizex_res()
The vn_rlimit_fsizex() function: - checks that the write does not exceed RLIMIT_FSIZE limit and fs maximum supported file size - truncates write length if it exceeds the RLIMIT_FSIZE or max file size, but there are some bytes to write - sends SIGXFSZ if RLIMIT_FSIZE would be exceed otherwise POSIX mandates the truncated write in case when some bytes can be written but whole write request fails the RLIMIT_FSIZE check. The function is supposed to be used from VOP_WRITE()s. Due to pecularity in the VFS generic write syscall layer, uio_resid must correctly reflect the written amount (noted by markj). Provide the dual vn_rlimit_fsizex_res() function to correct uio_resid after the clamp done in vn_rlimit_fsizex() on VOP_WRITE() return. PR: 164793 Reviewed by: asomers, jah, markj Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 2 weeks Differential revision: https://reviews.freebsd.org/D36625
-rw-r--r--sys/kern/vfs_vnops.c92
-rw-r--r--sys/sys/vnode.h3
2 files changed, 83 insertions, 12 deletions
diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
index 6eef39a37f25..21f3f99a6741 100644
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -2389,39 +2389,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 da17170cbb2d..63b3c23bd072 100644
--- a/sys/sys/vnode.h
+++ b/sys/sys/vnode.h
@@ -786,6 +786,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,