diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -4945,11 +4945,11 @@ int error; size_t retlen; void *rl_rcookie, *rl_wcookie; - off_t savinoff, savoutoff; + off_t inoff, outoff, savinoff, savoutoff; infp = outfp = NULL; rl_rcookie = rl_wcookie = NULL; - savinoff = -1; + savinoff = -1; /* sentinel value for error handling */ error = 0; retlen = 0; @@ -4991,13 +4991,40 @@ goto out; } - /* Set the offset pointers to the correct place. */ + /* + * Figure out which file offsets we're reading from and writing to. + * If the offsets come from the file descriptions, we need to lock them, + * which requires a loop to avoid deadlocks. + */ +foff_retry: if (inoffp == NULL) - inoffp = &infp->f_offset; - if (outoffp == NULL) - outoffp = &outfp->f_offset; - savinoff = *inoffp; - savoutoff = *outoffp; + inoff = foffset_lock(infp, 0); + else + inoff = *inoffp; + if (outoffp == NULL && inoffp == NULL) { + if (infp == outfp) { + /* + * Overlapping ranges are not allowed. A more thorough + * check is below, but we must not lock the same offset + * twice. + */ + error = EINVAL; + goto out; + } + outoff = foffset_lock(outfp, FOF_NONBLOCK); + if (outoff == -1) { + foffset_unlock(infp, inoff, FOF_NOUPDATE); + (void)foffset_lock(outfp, 0); + foffset_unlock(outfp, outoff, FOF_NOUPDATE); + goto foff_retry; + } + } else if (outoffp == NULL) { + outoff = foffset_lock(outfp, 0); + } else { + outoff = *outoffp; + } + savinoff = inoff; + savoutoff = outoff; invp = infp->f_vnode; outvp = outfp->f_vnode; @@ -5017,8 +5044,8 @@ * overlap. */ if (invp == outvp) { - if ((savinoff <= savoutoff && savinoff + len > savoutoff) || - (savinoff > savoutoff && savoutoff + len > savinoff)) { + if ((inoff <= outoff && inoff + len > outoff) || + (inoff > outoff && outoff + len > inoff)) { error = EINVAL; goto out; } @@ -5027,28 +5054,36 @@ /* Range lock the byte ranges for both invp and outvp. */ for (;;) { - rl_wcookie = vn_rangelock_wlock(outvp, *outoffp, *outoffp + - len); - rl_rcookie = vn_rangelock_tryrlock(invp, *inoffp, *inoffp + - len); + rl_wcookie = vn_rangelock_wlock(outvp, outoff, outoff + len); + rl_rcookie = vn_rangelock_tryrlock(invp, inoff, inoff + len); if (rl_rcookie != NULL) break; vn_rangelock_unlock(outvp, rl_wcookie); - rl_rcookie = vn_rangelock_rlock(invp, *inoffp, *inoffp + len); + rl_rcookie = vn_rangelock_rlock(invp, inoff, inoff + len); vn_rangelock_unlock(invp, rl_rcookie); } retlen = len; - error = vn_copy_file_range(invp, inoffp, outvp, outoffp, &retlen, + error = vn_copy_file_range(invp, &inoff, outvp, &outoff, &retlen, flags, infp->f_cred, outfp->f_cred, td); out: if (rl_rcookie != NULL) vn_rangelock_unlock(invp, rl_rcookie); if (rl_wcookie != NULL) vn_rangelock_unlock(outvp, rl_wcookie); - if (savinoff != -1 && (error == EINTR || error == ERESTART)) { - *inoffp = savinoff; - *outoffp = savoutoff; + if (savinoff != -1) { + if (error == EINTR || error == ERESTART) { + inoff = savinoff; + outoff = savoutoff; + } + if (inoffp == NULL) + foffset_unlock(infp, inoff, 0); + else + *inoffp = inoff; + if (outoffp == NULL) + foffset_unlock(outfp, outoff, 0); + else + *outoffp = outoff; } if (outfp != NULL) fdrop(outfp, td);