Skip to content

Commit 40214d1

Browse files
Brian Fosterdjwong
authored andcommitted
xfs: trim writepage mapping to within eof
The writeback rework in commit fbcc025 ("xfs: Introduce writeback context for writepages") introduced a subtle change in behavior with regard to the block mapping used across the ->writepages() sequence. The previous xfs_cluster_write() code would only flush pages up to EOF at the time of the writepage, thus ensuring that any pages due to file-extending writes would be handled on a separate cycle and with a new, updated block mapping. The updated code establishes a block mapping in xfs_writepage_map() that could extend beyond EOF if the file has post-eof preallocation. Because we now use the generic writeback infrastructure and pass the cached mapping to each writepage call, there is no implicit EOF limit in place. If eofblocks trimming occurs during ->writepages(), any post-eof portion of the cached mapping becomes invalid. The eofblocks code has no means to serialize against writeback because there are no pages associated with post-eof blocks. Therefore if an eofblocks trim occurs and is followed by a file-extending buffered write, not only has the mapping become invalid, but we could end up writing a page to disk based on the invalid mapping. Consider the following sequence of events: - A buffered write creates a delalloc extent and post-eof speculative preallocation. - Writeback starts and on the first writepage cycle, the delalloc extent is converted to real blocks (including the post-eof blocks) and the mapping is cached. - The file is closed and xfs_release() trims post-eof blocks. The cached writeback mapping is now invalid. - Another buffered write appends the file with a delalloc extent. - The concurrent writeback cycle picks up the just written page because the writeback range end is LLONG_MAX. xfs_writepage_map() attributes it to the (now invalid) cached mapping and writes the data to an incorrect location on disk (and where the file offset is still backed by a delalloc extent). This problem is reproduced by xfstests test generic/464, which triggers racing writes, appends, open/closes and writeback requests. To address this problem, trim the mapping used during writeback to within EOF when the mapping is validated. This ensures the mapping is revalidated for any pages encountered beyond EOF as of the time the current mapping was cached or last validated. Reported-by: Eryu Guan <eguan@redhat.com> Diagnosed-by: Eryu Guan <eguan@redhat.com> Signed-off-by: Brian Foster <bfoster@redhat.com> Reviewed-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
1 parent 5e25c26 commit 40214d1

File tree

3 files changed

+25
-0
lines changed

3 files changed

+25
-0
lines changed

fs/xfs/libxfs/xfs_bmap.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3852,6 +3852,17 @@ xfs_trim_extent(
38523852
}
38533853
}
38543854

3855+
/* trim extent to within eof */
3856+
void
3857+
xfs_trim_extent_eof(
3858+
struct xfs_bmbt_irec *irec,
3859+
struct xfs_inode *ip)
3860+
3861+
{
3862+
xfs_trim_extent(irec, 0, XFS_B_TO_FSB(ip->i_mount,
3863+
i_size_read(VFS_I(ip))));
3864+
}
3865+
38553866
/*
38563867
* Trim the returned map to the required bounds
38573868
*/

fs/xfs/libxfs/xfs_bmap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ void xfs_bmap_trace_exlist(struct xfs_inode *ip, xfs_extnum_t cnt,
208208

209209
void xfs_trim_extent(struct xfs_bmbt_irec *irec, xfs_fileoff_t bno,
210210
xfs_filblks_t len);
211+
void xfs_trim_extent_eof(struct xfs_bmbt_irec *, struct xfs_inode *);
211212
int xfs_bmap_add_attrfork(struct xfs_inode *ip, int size, int rsvd);
212213
void xfs_bmap_local_to_extents_empty(struct xfs_inode *ip, int whichfork);
213214
void xfs_bmap_add_free(struct xfs_mount *mp, struct xfs_defer_ops *dfops,

fs/xfs/xfs_aops.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,19 @@ xfs_imap_valid(
446446
{
447447
offset >>= inode->i_blkbits;
448448

449+
/*
450+
* We have to make sure the cached mapping is within EOF to protect
451+
* against eofblocks trimming on file release leaving us with a stale
452+
* mapping. Otherwise, a page for a subsequent file extending buffered
453+
* write could get picked up by this writeback cycle and written to the
454+
* wrong blocks.
455+
*
456+
* Note that what we really want here is a generic mapping invalidation
457+
* mechanism to protect us from arbitrary extent modifying contexts, not
458+
* just eofblocks.
459+
*/
460+
xfs_trim_extent_eof(imap, XFS_I(inode));
461+
449462
return offset >= imap->br_startoff &&
450463
offset < imap->br_startoff + imap->br_blockcount;
451464
}

0 commit comments

Comments
 (0)