Index: kern/vfs_vnops.c =================================================================== --- kern/vfs_vnops.c +++ kern/vfs_vnops.c @@ -2713,18 +2713,74 @@ } /* + * Look for a hole in the output file and, if found, adjust *outoffp + * and *xferp to skip past the hole. + * *xferp is the entire hole length to be written and xfer2 is how many bytes + * to be written as 0's upon return. + */ +static off_t +vn_skip_hole(struct vnode *outvp, off_t xfer2, off_t *outoffp, off_t *xferp, + off_t *dataoffp, off_t *holeoffp, struct ucred *cred) +{ + int error; + off_t delta; + + if (*holeoffp == 0 || *holeoffp <= *outoffp) { + *dataoffp = *outoffp; + error = VOP_IOCTL(outvp, FIOSEEKDATA, dataoffp, 0, cred, + curthread); + if (error == 0) { + *holeoffp = *dataoffp; + error = VOP_IOCTL(outvp, FIOSEEKHOLE, holeoffp, 0, cred, + curthread); + } + if (error != 0) { + *holeoffp = -1; /* Disable use of vn_skip_hole(). */ + return (xfer2); + } + } + + /* + * If there is a hole before the data starts, advance *outoffp and + * *xferp past the hole. + */ + if (*dataoffp > *outoffp) { + delta = *dataoffp - *outoffp; + if (delta >= *xferp) { + /* Entire *xferp is a hole. */ + *outoffp += *xferp; + *xferp = 0; + return (0); + } + *xferp -= delta; + *outoffp += delta; + xfer2 = MIN(xfer2, *xferp); + } + + /* + * If a hole starts before the end of this xfer2, reduce this xfer2 so + * that the write ends at the start of the hole. + */ + if (*holeoffp < *outoffp + xfer2) + xfer2 = *holeoffp - *outoffp; + return (xfer2); +} + +/* * Write an xfer sized chunk to outvp in blksize blocks from dat. * dat is a maximum of blksize in length and can be written repeatedly in * the chunk. * If growfile == true, just grow the file via vn_truncate_locked() instead * of doing actual writes. + * If checkhole == true, a hole is being punched, so skip over any hole + * already in the output file. */ static int vn_write_outvp(struct vnode *outvp, char *dat, off_t outoff, off_t xfer, - u_long blksize, bool growfile, struct ucred *cred) + u_long blksize, bool growfile, bool checkhole, struct ucred *cred) { struct mount *mp; - off_t xfer2; + off_t dataoff, holeoff, xfer2; int error, lckf; /* @@ -2733,7 +2789,29 @@ * done for each iteration, since the xfer argument can be very * large if there is a large hole to punch in the output file. */ + error = 0; + holeoff = 0; do { + xfer2 = MIN(xfer, blksize); + if (checkhole) { + /* + * Punching a hole. Skip writing if there is + * already a hole in the output file. + */ + xfer2 = vn_skip_hole(outvp, xfer2, &outoff, &xfer, + &dataoff, &holeoff, cred); + if (xfer == 0) + break; + if (holeoff < 0) + checkhole = false; + if (xfer2 <= 0) { +#ifdef INVARIANTS + printf("vn_write_outvp: SEEKDATA/SEEKHOLE " + "broken\n"); +#endif + xfer2 = MIN(xfer, blksize); + } + } bwillwrite(); mp = NULL; error = vn_start_write(outvp, &mp, V_WAIT); @@ -2749,7 +2827,6 @@ error = vn_truncate_locked(outvp, outoff + xfer, false, cred); else { - xfer2 = MIN(xfer, blksize); error = vn_rdwr(UIO_WRITE, outvp, dat, xfer2, outoff, UIO_SYSSPACE, IO_NODELOCKED, curthread->td_ucred, cred, NULL, curthread); @@ -2907,7 +2984,7 @@ memset(dat, 0, MIN(xfer2, blksize)); error = vn_write_outvp(outvp, dat, *outoffp, xfer2, blksize, false, - outcred); + holeout > 0, outcred); } if (error == 0 && *outoffp + xfer > @@ -2915,7 +2992,7 @@ /* Grow last block. */ error = vn_write_outvp(outvp, dat, *outoffp, xfer, blksize, true, - outcred); + false, outcred); if (error == 0) { *inoffp += xfer; *outoffp += xfer; @@ -2971,7 +3048,8 @@ error = vn_write_outvp(outvp, dat, *outoffp, xfer, blksize, readzeros && xfer == len && - *outoffp >= va.va_size, outcred); + *outoffp >= va.va_size, false, + outcred); if (error == 0) { *inoffp += xfer; startoff += xfer;