Page MenuHomeFreeBSD

D49440.id152614.diff
No OneTemporary

D49440.id152614.diff

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
@@ -4940,16 +4940,17 @@
kern_copy_file_range(struct thread *td, int infd, off_t *inoffp, int outfd,
off_t *outoffp, size_t len, unsigned int flags)
{
- struct file *infp, *outfp;
+ struct file *infp, *infp1, *outfp, *outfp1;
struct vnode *invp, *outvp;
int error;
size_t retlen;
void *rl_rcookie, *rl_wcookie;
- off_t savinoff, savoutoff;
+ off_t inoff, outoff, savinoff, savoutoff;
+ bool foffsets_locked;
infp = outfp = NULL;
rl_rcookie = rl_wcookie = NULL;
- savinoff = -1;
+ foffsets_locked = false;
error = 0;
retlen = 0;
@@ -4991,13 +4992,35 @@
goto out;
}
- /* Set the offset pointers to the correct place. */
- if (inoffp == NULL)
- inoffp = &infp->f_offset;
- if (outoffp == NULL)
- outoffp = &outfp->f_offset;
- savinoff = *inoffp;
- savoutoff = *outoffp;
+ /*
+ * 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,
+ * and locking both offsets requires a loop to avoid deadlocks.
+ */
+ infp1 = outfp1 = NULL;
+ if (inoffp != NULL)
+ inoff = *inoffp;
+ else
+ infp1 = infp;
+ if (outoffp != NULL)
+ outoff = *outoffp;
+ else
+ outfp1 = outfp;
+ if (infp1 != NULL || outfp1 != NULL) {
+ if (infp1 == outfp1) {
+ /*
+ * Overlapping ranges are not allowed. A more thorough
+ * check appears below, but we must not lock the same
+ * offset twice.
+ */
+ error = EINVAL;
+ goto out;
+ }
+ foffset_lock_pair(infp1, &inoff, outfp1, &outoff, 0);
+ foffsets_locked = true;
+ }
+ savinoff = inoff;
+ savoutoff = outoff;
invp = infp->f_vnode;
outvp = outfp->f_vnode;
@@ -5017,8 +5040,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 +5050,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 (foffsets_locked) {
+ 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);
diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -892,6 +892,26 @@
}
#endif
+void
+foffset_lock_pair(struct file *fp1, off_t *off1p, struct file *fp2, off_t *off2p,
+ int flags)
+{
+ KASSERT(fp1 != fp2, ("foffset_lock_pair: fp1 == fp2"));
+
+ /* Lock in a consistent order to avoid deadlock. */
+ if ((uintptr_t)fp1 > (uintptr_t)fp2) {
+ struct file *tmpfp;
+ off_t *tmpoffp;
+
+ tmpfp = fp1, fp1 = fp2, fp2 = tmpfp;
+ tmpoffp = off1p, off1p = off2p, off2p = tmpoffp;
+ }
+ if (fp1 != NULL)
+ *off1p = foffset_lock(fp1, flags);
+ if (fp2 != NULL)
+ *off2p = foffset_lock(fp2, flags);
+}
+
void
foffset_lock_uio(struct file *fp, struct uio *uio, int flags)
{
diff --git a/sys/sys/file.h b/sys/sys/file.h
--- a/sys/sys/file.h
+++ b/sys/sys/file.h
@@ -86,6 +86,8 @@
#define FOF_NEXTOFF_W 0x08 /* Also update f_nextoff[UIO_WRITE] */
#define FOF_NOUPDATE 0x10 /* Do not update f_offset */
off_t foffset_lock(struct file *fp, int flags);
+void foffset_lock_pair(struct file *fp1, off_t *off1p, struct file *fp2,
+ off_t *off2p, int flags);
void foffset_lock_uio(struct file *fp, struct uio *uio, int flags);
void foffset_unlock(struct file *fp, off_t val, int flags);
void foffset_unlock_uio(struct file *fp, struct uio *uio, int flags);

File Metadata

Mime Type
text/plain
Expires
Sun, Apr 13, 9:26 AM (10 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17494173
Default Alt Text
D49440.id152614.diff (4 KB)

Event Timeline