diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 3023323545f4289dba7de4e31a1f98c85d42ac44..6831c43cd1cd4a5eeb40df717be103cf67641f9b 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -491,6 +491,7 @@ xfs_reflink_allocate_cow( xfs_filblks_t count_fsb = imap->br_blockcount; struct xfs_trans *tp; int nimaps, error = 0; + unsigned int seq_before = READ_ONCE(ip->i_df.if_seq); bool found; bool secondary_evicting = false; xfs_filblks_t resaligned; @@ -533,8 +534,21 @@ xfs_reflink_allocate_cow( *lockmode = XFS_ILOCK_EXCL; /* - * Check for an overlapping extent again now that we dropped the ilock. + * The data fork mapping may have changed while we dropped the ILOCK + * (a racing O_DIRECT writer under IOLOCK_SHARED can complete a full + * CoW cycle including xfs_reflink_end_cow(), which remaps this offset + * and drops the refcount of the old shared block). Re-read it so the + * shared-status recheck below and the caller's in-place iomap both + * operate on the current mapping rather than a stale physical block. */ + if (seq_before != READ_ONCE(ip->i_df.if_seq)) { + nimaps = 1; + error = xfs_bmapi_read(ip, imap->br_startoff, + imap->br_blockcount, imap, &nimaps, 0); + if (error) + goto out_trans_cancel; + } + error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found); if (error || !*shared) goto out_trans_cancel;