git: b55a7f3422d7 - stable/13 - Fix handling of errors from dmu_write_uio_dbuf() on FreeBSD

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 21 Feb 2022 15:13:30 UTC
The branch stable/13 has been updated by markj:

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

commit b55a7f3422d76a6765716b2b6e78967bd75199c9
Author:     Mark Johnston <markjdb@gmail.com>
AuthorDate: 2022-01-21 19:54:05 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2022-02-21 14:59:58 +0000

    Fix handling of errors from dmu_write_uio_dbuf() on FreeBSD
    
    FreeBSD's implementation of zfs_uio_fault_move() returns EFAULT when a
    page fault occurs while copying data in or out of user buffers.  The VFS
    treats such errors specially and will retry the I/O operation (which may
    have made some partial progress).
    
    When the FreeBSD and Linux implementations of zfs_write() were merged,
    the handling of errors from dmu_write_uio_dbuf() changed such that
    EFAULT is not handled as a partial write.  For example, when appending
    to a file, the z_size field of the znode is not updated after a partial
    write resulting in EFAULT.
    
    Restore the old handling of errors from dmu_write_uio_dbuf() to fix
    this.  This should have no impact on Linux, which has special handling
    for EFAULT already.
    
    Reviewed-by: Andriy Gapon <avg@FreeBSD.org>
    Reviewed-by: Ryan Moeller <ryan@iXsystems.com>
    Signed-off-by: Mark Johnston <markj@FreeBSD.org>
    Closes #12964
    (cherry picked from commit 063daa8350d4a78f96d1ee6550910363fd3756fb)
---
 sys/contrib/openzfs/module/zfs/zfs_vnops.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/sys/contrib/openzfs/module/zfs/zfs_vnops.c b/sys/contrib/openzfs/module/zfs/zfs_vnops.c
index 170e392abe93..54749810d45d 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_vnops.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_vnops.c
@@ -323,7 +323,7 @@ out:
 int
 zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr)
 {
-	int error = 0;
+	int error = 0, error1;
 	ssize_t start_resid = zfs_uio_resid(uio);
 
 	/*
@@ -561,7 +561,11 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr)
 				continue;
 			}
 #endif
-			if (error != 0) {
+			/*
+			 * On FreeBSD, EFAULT should be propagated back to the
+			 * VFS, which will handle faulting and will retry.
+			 */
+			if (error != 0 && error != EFAULT) {
 				dmu_tx_commit(tx);
 				break;
 			}
@@ -645,7 +649,7 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr)
 		while ((end_size = zp->z_size) < zfs_uio_offset(uio)) {
 			(void) atomic_cas_64(&zp->z_size, end_size,
 			    zfs_uio_offset(uio));
-			ASSERT(error == 0);
+			ASSERT(error == 0 || error == EFAULT);
 		}
 		/*
 		 * If we are replaying and eof is non zero then force
@@ -655,7 +659,10 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr)
 		if (zfsvfs->z_replay && zfsvfs->z_replay_eof != 0)
 			zp->z_size = zfsvfs->z_replay_eof;
 
-		error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx);
+		error1 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx);
+		if (error1 != 0)
+			/* Avoid clobbering EFAULT. */
+			error = error1;
 
 		zfs_log_write(zilog, tx, TX_WRITE, zp, woff, tx_bytes, ioflag,
 		    NULL, NULL);