Skip to content

Commit 5d888b4

Browse files
committed
xfs: fix reflink source file racing with directio writes
While trawling through the dedupe file comparison code trying to fix page deadlocking problems, Dave Chinner noticed that the reflink code only takes shared IOLOCK/MMAPLOCKs on the source file. Because page_mkwrite and directio writes do not take the EXCL versions of those locks, this means that reflink can race with writer processes. For pure remapping this can lead to undefined behavior and file corruption; for dedupe this means that we cannot be sure that the contents are identical when we decide to go ahead with the remapping. Signed-off-by: Darrick J. Wong <[email protected]> Reviewed-by: Christoph Hellwig <[email protected]>
1 parent edc58dd commit 5d888b4

File tree

1 file changed

+37
-26
lines changed

1 file changed

+37
-26
lines changed

fs/xfs/xfs_reflink.c

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,11 +1190,11 @@ xfs_reflink_remap_blocks(
11901190
}
11911191

11921192
/*
1193-
* Grab the exclusive iolock for a data copy from src to dest, making
1194-
* sure to abide vfs locking order (lowest pointer value goes first) and
1195-
* breaking the pnfs layout leases on dest before proceeding. The loop
1196-
* is needed because we cannot call the blocking break_layout() with the
1197-
* src iolock held, and therefore have to back out both locks.
1193+
* Grab the exclusive iolock for a data copy from src to dest, making sure to
1194+
* abide vfs locking order (lowest pointer value goes first) and breaking the
1195+
* layout leases before proceeding. The loop is needed because we cannot call
1196+
* the blocking break_layout() with the iolocks held, and therefore have to
1197+
* back out both locks.
11981198
*/
11991199
static int
12001200
xfs_iolock_two_inodes_and_break_layout(
@@ -1203,33 +1203,44 @@ xfs_iolock_two_inodes_and_break_layout(
12031203
{
12041204
int error;
12051205

1206-
retry:
1207-
if (src < dest) {
1208-
inode_lock_shared(src);
1209-
inode_lock_nested(dest, I_MUTEX_NONDIR2);
1210-
} else {
1211-
/* src >= dest */
1212-
inode_lock(dest);
1213-
}
1206+
if (src > dest)
1207+
swap(src, dest);
12141208

1215-
error = break_layout(dest, false);
1216-
if (error == -EWOULDBLOCK) {
1217-
inode_unlock(dest);
1218-
if (src < dest)
1219-
inode_unlock_shared(src);
1209+
retry:
1210+
/* Wait to break both inodes' layouts before we start locking. */
1211+
error = break_layout(src, true);
1212+
if (error)
1213+
return error;
1214+
if (src != dest) {
12201215
error = break_layout(dest, true);
12211216
if (error)
12221217
return error;
1223-
goto retry;
12241218
}
1219+
1220+
/* Lock one inode and make sure nobody got in and leased it. */
1221+
inode_lock(src);
1222+
error = break_layout(src, false);
12251223
if (error) {
1224+
inode_unlock(src);
1225+
if (error == -EWOULDBLOCK)
1226+
goto retry;
1227+
return error;
1228+
}
1229+
1230+
if (src == dest)
1231+
return 0;
1232+
1233+
/* Lock the other inode and make sure nobody got in and leased it. */
1234+
inode_lock_nested(dest, I_MUTEX_NONDIR2);
1235+
error = break_layout(dest, false);
1236+
if (error) {
1237+
inode_unlock(src);
12261238
inode_unlock(dest);
1227-
if (src < dest)
1228-
inode_unlock_shared(src);
1239+
if (error == -EWOULDBLOCK)
1240+
goto retry;
12291241
return error;
12301242
}
1231-
if (src > dest)
1232-
inode_lock_shared_nested(src, I_MUTEX_NONDIR2);
1243+
12331244
return 0;
12341245
}
12351246

@@ -1247,10 +1258,10 @@ xfs_reflink_remap_unlock(
12471258

12481259
xfs_iunlock(dest, XFS_MMAPLOCK_EXCL);
12491260
if (!same_inode)
1250-
xfs_iunlock(src, XFS_MMAPLOCK_SHARED);
1261+
xfs_iunlock(src, XFS_MMAPLOCK_EXCL);
12511262
inode_unlock(inode_out);
12521263
if (!same_inode)
1253-
inode_unlock_shared(inode_in);
1264+
inode_unlock(inode_in);
12541265
}
12551266

12561267
/*
@@ -1325,7 +1336,7 @@ xfs_reflink_remap_prep(
13251336
if (same_inode)
13261337
xfs_ilock(src, XFS_MMAPLOCK_EXCL);
13271338
else
1328-
xfs_lock_two_inodes(src, XFS_MMAPLOCK_SHARED, dest,
1339+
xfs_lock_two_inodes(src, XFS_MMAPLOCK_EXCL, dest,
13291340
XFS_MMAPLOCK_EXCL);
13301341

13311342
/* Check file eligibility and prepare for block sharing. */

0 commit comments

Comments
 (0)