git: cd192185662e - stable/13 - copy_file_range(2): improve copying of a large hole to EOF

Rick Macklem rmacklem at FreeBSD.org
Sun May 16 23:52:15 UTC 2021


The branch stable/13 has been updated by rmacklem:

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

commit cd192185662ef90f52614ea17cb739f5047d5e0c
Author:     Rick Macklem <rmacklem at FreeBSD.org>
AuthorDate: 2021-05-02 23:04:27 +0000
Commit:     Rick Macklem <rmacklem at FreeBSD.org>
CommitDate: 2021-05-16 23:48:06 +0000

    copy_file_range(2): improve copying of a large hole to EOF
    
    PR#255523 reported that a file copy for a file with a large hole
    to EOF on ZFS ran slowly over NFSv4.2.
    The problem was that vn_generic_copy_file_range() would
    loop around reading the hole's data and then see it is all
    0s. It was coded this way since UFS always allocates a data
    block near the end of the file, such that a hole to EOF never exists.
    
    This patch modifies vn_generic_copy_file_range() to check for a
    ENXIO returned from VOP_IOCTL(..FIOSEEKDATA..) and handle that
    case as a hole to EOF. asomers@ confirms that it works for his
    ZFS test case.
    
    PR:     255523
    
    (cherry picked from commit 4f592683c356379c5bac56b52807ed4ad54ee647)
---
 sys/kern/vfs_vnops.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
index 832c717a33b7..d4396f67a67b 100644
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -3099,13 +3099,13 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
     struct vnode *outvp, off_t *outoffp, size_t *lenp, unsigned int flags,
     struct ucred *incred, struct ucred *outcred, struct thread *fsize_td)
 {
-	struct vattr va;
+	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;
+	bool cantseek, readzeros, eof, lastblock, holetoeof;
 	ssize_t aresid;
 	size_t copylen, len, rem, savlen;
 	char *dat;
@@ -3122,7 +3122,11 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 		goto out;
 	if (VOP_PATHCONF(invp, _PC_MIN_HOLE_SIZE, &holein) != 0)
 		holein = 0;
+	if (holein > 0)
+		error = VOP_GETATTR(invp, &inva, incred);
 	VOP_UNLOCK(invp);
+	if (error != 0)
+		goto out;
 
 	mp = NULL;
 	error = vn_start_write(outvp, &mp, V_WAIT);
@@ -3203,7 +3207,7 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 	 * Note that some file systems such as NFSv3, NFSv4.0 and NFSv4.1 may
 	 * support holes on the server, but do not support FIOSEEKHOLE.
 	 */
-	eof = false;
+	holetoeof = eof = false;
 	while (len > 0 && error == 0 && !eof && interrupted == 0) {
 		endoff = 0;			/* To shut up compilers. */
 		cantseek = true;
@@ -3212,8 +3216,7 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 
 		/*
 		 * Find the next data area.  If there is just a hole to EOF,
-		 * FIOSEEKDATA should fail and then we drop down into the
-		 * inner loop and create the hole on the outvp file.
+		 * FIOSEEKDATA should fail with ENXIO.
 		 * (I do not know if any file system will report a hole to
 		 *  EOF via FIOSEEKHOLE, but I am pretty sure FIOSEEKDATA
 		 *  will fail for those file systems.)
@@ -3222,10 +3225,16 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 		 * the code just falls through to the inner copy loop.
 		 */
 		error = EINVAL;
-		if (holein > 0)
+		if (holein > 0) {
 			error = VOP_IOCTL(invp, FIOSEEKDATA, &startoff, 0,
 			    incred, curthread);
-		if (error == 0) {
+			if (error == ENXIO) {
+				startoff = endoff = inva.va_size;
+				eof = holetoeof = true;
+				error = 0;
+			}
+		}
+		if (error == 0 && !holetoeof) {
 			endoff = startoff;
 			error = VOP_IOCTL(invp, FIOSEEKHOLE, &endoff, 0,
 			    incred, curthread);
@@ -3256,11 +3265,12 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 				}
 
 				if (error == 0 && *outoffp + xfer >
-				    va.va_size && xfer == len)
-					/* Grow last block. */
+				    va.va_size && (xfer == len || holetoeof)) {
+					/* Grow output file (hole at end). */
 					error = vn_write_outvp(outvp, dat,
 					    *outoffp, xfer, blksize, true,
 					    false, outcred);
+				}
 				if (error == 0) {
 					*inoffp += xfer;
 					*outoffp += xfer;


More information about the dev-commits-src-all mailing list