Skip to content

Commit 77a1fb9

Browse files
Darrick J. Wonggregkh
authored andcommitted
xfs: fix various problems in xfs_atomic_write_cow_iomap_begin
commit 8d7bba1e8314013ecc817a91624104ceb9352ddc upstream. I think there are several things wrong with this function: A) xfs_bmapi_write can return a much larger unwritten mapping than what the caller asked for. We convert part of that range to written, but return the entire written mapping to iomap even though that's inaccurate. B) The arguments to xfs_reflink_convert_cow_locked are wrong -- an unwritten mapping could be *smaller* than the write range (or even the hole range). In this case, we convert too much file range to written state because we then return a smaller mapping to iomap. C) It doesn't handle delalloc mappings. This I covered in the patch that I already sent to the list. D) Reassigning count_fsb to handle the hole means that if the second cmap lookup attempt succeeds (due to racing with someone else) we trim the mapping more than is strictly necessary. The changing meaning of count_fsb makes this harder to notice. E) The tracepoint is kinda wrong because @Length is mutated. That makes it harder to chase the data flows through this function because you can't just grep on the pos/bytecount strings. F) We don't actually check that the br_state = XFS_EXT_NORM assignment is accurate, i.e that the cow fork actually contains a written mapping for the range we're interested in G) Somewhat inadequate documentation of why we need to xfs_trim_extent so aggressively in this function. H) Not sure why xfs_iomap_end_fsb is used here, the vfs already clamped the write range to s_maxbytes. Fix these issues, and then the atomic writes regressions in generic/760, generic/617, generic/091, generic/263, and generic/521 all go away for me. Cc: [email protected] # v6.16 Fixes: bd1d2c2 ("xfs: add xfs_atomic_write_cow_iomap_begin()") Signed-off-by: Darrick J. Wong <[email protected]> Reviewed-by: John Garry <[email protected]> Signed-off-by: Carlos Maiolino <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 8e31320 commit 77a1fb9

1 file changed

Lines changed: 50 additions & 11 deletions

File tree

fs/xfs/xfs_iomap.c

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,29 @@ const struct iomap_ops xfs_zoned_direct_write_iomap_ops = {
10821082
};
10831083
#endif /* CONFIG_XFS_RT */
10841084

1085+
#ifdef DEBUG
1086+
static void
1087+
xfs_check_atomic_cow_conversion(
1088+
struct xfs_inode *ip,
1089+
xfs_fileoff_t offset_fsb,
1090+
xfs_filblks_t count_fsb,
1091+
const struct xfs_bmbt_irec *cmap)
1092+
{
1093+
struct xfs_iext_cursor icur;
1094+
struct xfs_bmbt_irec cmap2 = { };
1095+
1096+
if (xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap2))
1097+
xfs_trim_extent(&cmap2, offset_fsb, count_fsb);
1098+
1099+
ASSERT(cmap2.br_startoff == cmap->br_startoff);
1100+
ASSERT(cmap2.br_blockcount == cmap->br_blockcount);
1101+
ASSERT(cmap2.br_startblock == cmap->br_startblock);
1102+
ASSERT(cmap2.br_state == cmap->br_state);
1103+
}
1104+
#else
1105+
# define xfs_check_atomic_cow_conversion(...) ((void)0)
1106+
#endif
1107+
10851108
static int
10861109
xfs_atomic_write_cow_iomap_begin(
10871110
struct inode *inode,
@@ -1093,9 +1116,10 @@ xfs_atomic_write_cow_iomap_begin(
10931116
{
10941117
struct xfs_inode *ip = XFS_I(inode);
10951118
struct xfs_mount *mp = ip->i_mount;
1096-
const xfs_fileoff_t offset_fsb = XFS_B_TO_FSBT(mp, offset);
1097-
xfs_fileoff_t end_fsb = xfs_iomap_end_fsb(mp, offset, length);
1098-
xfs_filblks_t count_fsb = end_fsb - offset_fsb;
1119+
const xfs_fileoff_t offset_fsb = XFS_B_TO_FSBT(mp, offset);
1120+
const xfs_fileoff_t end_fsb = XFS_B_TO_FSB(mp, offset + length);
1121+
const xfs_filblks_t count_fsb = end_fsb - offset_fsb;
1122+
xfs_filblks_t hole_count_fsb;
10991123
int nmaps = 1;
11001124
xfs_filblks_t resaligned;
11011125
struct xfs_bmbt_irec cmap;
@@ -1134,14 +1158,20 @@ xfs_atomic_write_cow_iomap_begin(
11341158
if (cmap.br_startoff <= offset_fsb) {
11351159
if (isnullstartblock(cmap.br_startblock))
11361160
goto convert_delay;
1161+
1162+
/*
1163+
* cmap could extend outside the write range due to previous
1164+
* speculative preallocations. We must trim cmap to the write
1165+
* range because the cow fork treats written mappings to mean
1166+
* "write in progress".
1167+
*/
11371168
xfs_trim_extent(&cmap, offset_fsb, count_fsb);
11381169
goto found;
11391170
}
11401171

1141-
end_fsb = cmap.br_startoff;
1142-
count_fsb = end_fsb - offset_fsb;
1172+
hole_count_fsb = cmap.br_startoff - offset_fsb;
11431173

1144-
resaligned = xfs_aligned_fsb_count(offset_fsb, count_fsb,
1174+
resaligned = xfs_aligned_fsb_count(offset_fsb, hole_count_fsb,
11451175
xfs_get_cowextsz_hint(ip));
11461176
xfs_iunlock(ip, XFS_ILOCK_EXCL);
11471177

@@ -1177,7 +1207,7 @@ xfs_atomic_write_cow_iomap_begin(
11771207
* atomic writes to that same range will be aligned (and don't require
11781208
* this COW-based method).
11791209
*/
1180-
error = xfs_bmapi_write(tp, ip, offset_fsb, count_fsb,
1210+
error = xfs_bmapi_write(tp, ip, offset_fsb, hole_count_fsb,
11811211
XFS_BMAPI_COWFORK | XFS_BMAPI_PREALLOC |
11821212
XFS_BMAPI_EXTSZALIGN, 0, &cmap, &nmaps);
11831213
if (error) {
@@ -1190,17 +1220,26 @@ xfs_atomic_write_cow_iomap_begin(
11901220
if (error)
11911221
goto out_unlock;
11921222

1223+
/*
1224+
* cmap could map more blocks than the range we passed into bmapi_write
1225+
* because of EXTSZALIGN or adjacent pre-existing unwritten mappings
1226+
* that were merged. Trim cmap to the original write range so that we
1227+
* don't convert more than we were asked to do for this write.
1228+
*/
1229+
xfs_trim_extent(&cmap, offset_fsb, count_fsb);
1230+
11931231
found:
11941232
if (cmap.br_state != XFS_EXT_NORM) {
1195-
error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
1196-
count_fsb);
1233+
error = xfs_reflink_convert_cow_locked(ip, cmap.br_startoff,
1234+
cmap.br_blockcount);
11971235
if (error)
11981236
goto out_unlock;
11991237
cmap.br_state = XFS_EXT_NORM;
1238+
xfs_check_atomic_cow_conversion(ip, offset_fsb, count_fsb,
1239+
&cmap);
12001240
}
12011241

1202-
length = XFS_FSB_TO_B(mp, cmap.br_startoff + cmap.br_blockcount);
1203-
trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
1242+
trace_xfs_iomap_found(ip, offset, length, XFS_COW_FORK, &cmap);
12041243
seq = xfs_iomap_inode_sequence(ip, IOMAP_F_SHARED);
12051244
xfs_iunlock(ip, XFS_ILOCK_EXCL);
12061245
return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);

0 commit comments

Comments
 (0)