* [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1
@ 2026-07-10 5:21 Darrick J. Wong
2026-07-10 5:21 ` [PATCH 1/8] xfs: don't replace the wrong part of the cow fork Darrick J. Wong
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:21 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
Hi all,
Someone gave me a pretty generous license for a large language model, so
I pointed it at every single file under fs/xfs and told it to find bugs.
After personally filtering out the errors and garbage, here are fixes
for the real bugs that I think it found.
Most of these fixes target the online fsck code because I'm most
familiar with it, but the LLM will read other parts of the xfs codebase
so there are miscellaneous bug fixes.
I'm sending these out as smallish bundles of bug fixes. There's no
particular order or grouping.
v2: add debugging patch to make it easier to test new cow repair code,
move cow fork replacement function to xfs_bmap_util.c.
If you're going to start using this code, I strongly recommend pulling
from my git trees, which are linked below.
With a bit of luck, this should all go splendidly.
Comments and questions are, as always, welcome.
--D
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=llm-fixes-1
---
Commits in this patchset:
* xfs: don't replace the wrong part of the cow fork
* xfs: make cow repair somewhat flaky when debugging knob enabled
* xfs: move cow_replace_mapping to xfs_bmap_util.c
* xfs: don't wrap around quota ids in dqiterate
* xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging
* xfs: use the rt version of the cow staging checker
* xfs: write the rg superblock when fixing it
* xfs: grab rtrmap btree when checking rgsuper
---
fs/xfs/scrub/trace.h | 35 ---------
fs/xfs/xfs_bmap_util.h | 4 +
fs/xfs/xfs_trace.h | 41 ++++++++++
fs/xfs/scrub/cow_repair.c | 181 ++++++++++++++++++++++-----------------------
fs/xfs/scrub/dqiterate.c | 2
fs/xfs/scrub/rgsuper.c | 14 +++
fs/xfs/scrub/rtrefcount.c | 2
fs/xfs/scrub/rtrmap.c | 2
fs/xfs/xfs_bmap_util.c | 89 ++++++++++++++++++++++
9 files changed, 238 insertions(+), 132 deletions(-)
Unreviewed patches in this submission:
[PATCHSET v2] xfs: LLM-inspired bug fixes, part 1
[PATCH 1/8] xfs: don't replace the wrong part of the cow fork
[PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob
[PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c
[PATCH 7/8] xfs: write the rg superblock when fixing it
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/8] xfs: don't replace the wrong part of the cow fork
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
@ 2026-07-10 5:21 ` Darrick J. Wong
2026-07-10 5:37 ` Christoph Hellwig
2026-07-10 5:22 ` [PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob enabled Darrick J. Wong
` (6 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:21 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
LOLLM points out that xfs_iext_lookup_extent can return a @got where
got->br_startoff < startoff. In this case, xrep_cow_replace_range
replaces the entire mapping instead of just the part that had been
marked bad in the bitmap, but advances the bitmap cursor in
xrep_cow_replace by the amount replaced. As a result, we fail to
replace the end of the bad range, and replace part of the good range.
Fix this by rewriting the replace method to handle replacing the middle
of a cow fork mapping. This we do by returning both the current mapping
as @got, and the subset of the mapping that we want to replace as @rep,
using @rep to store the results of the new allocation, and comparing
@rep to @got to figure out the exact transformations needed.
Cc: <stable@vger.kernel.org> # v6.8
Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
---
fs/xfs/scrub/trace.h | 28 ++++--
fs/xfs/scrub/cow_repair.c | 203 +++++++++++++++++++++++++++++----------------
2 files changed, 148 insertions(+), 83 deletions(-)
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index 1b7d9e07a27d3d..d48a6db5b5f308 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
);
TRACE_EVENT(xrep_cow_replace_mapping,
- TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
- xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
- TP_ARGS(ip, irec, new_startblock, new_blockcount),
+ TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
+ const struct xfs_bmbt_irec *rep),
+ TP_ARGS(ip, got, rep),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
@@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
__field(xfs_fileoff_t, startoff)
__field(xfs_filblks_t, blockcount)
__field(xfs_exntst_t, state)
+ __field(xfs_fileoff_t, new_startoff)
__field(xfs_fsblock_t, new_startblock)
__field(xfs_extlen_t, new_blockcount)
+ __field(xfs_exntst_t, new_state)
),
TP_fast_assign(
__entry->dev = ip->i_mount->m_super->s_dev;
__entry->ino = I_INO(ip);
- __entry->startoff = irec->br_startoff;
- __entry->startblock = irec->br_startblock;
- __entry->blockcount = irec->br_blockcount;
- __entry->state = irec->br_state;
- __entry->new_startblock = new_startblock;
- __entry->new_blockcount = new_blockcount;
+ __entry->startoff = got->br_startoff;
+ __entry->startblock = got->br_startblock;
+ __entry->blockcount = got->br_blockcount;
+ __entry->state = got->br_state;
+ __entry->new_startoff = rep->br_startoff;
+ __entry->new_startblock = rep->br_startblock;
+ __entry->new_blockcount = rep->br_blockcount;
+ __entry->new_state = rep->br_state;
),
- TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
+ TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino,
__entry->startoff,
__entry->startblock,
__entry->blockcount,
__entry->state,
+ __entry->new_startoff,
__entry->new_startblock,
- __entry->new_blockcount)
+ __entry->new_blockcount,
+ __entry->new_state)
);
TRACE_EVENT(xrep_cow_free_staging,
diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c
index 0075b6d5a1b5ff..ca3405a26b641c 100644
--- a/fs/xfs/scrub/cow_repair.c
+++ b/fs/xfs/scrub/cow_repair.c
@@ -80,12 +80,6 @@ struct xrep_cow {
unsigned int next_bno;
};
-/* CoW staging extent. */
-struct xrep_cow_extent {
- xfs_fsblock_t fsbno;
- xfs_extlen_t len;
-};
-
/*
* Mark the part of the file range that corresponds to the given physical
* space. Caller must ensure that the physical range is within xc->irec.
@@ -401,22 +395,21 @@ xrep_cow_find_bad_rt(
STATIC int
xrep_cow_alloc(
struct xfs_scrub *sc,
- xfs_extlen_t maxlen,
- struct xrep_cow_extent *repl)
+ struct xfs_bmbt_irec *del)
{
struct xfs_alloc_arg args = {
.tp = sc->tp,
.mp = sc->mp,
.oinfo = XFS_RMAP_OINFO_SKIP_UPDATE,
.minlen = 1,
- .maxlen = maxlen,
+ .maxlen = del->br_blockcount,
.prod = 1,
.resv = XFS_AG_RESV_NONE,
.datatype = XFS_ALLOC_USERDATA,
};
int error;
- error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
+ error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
if (error)
return error;
@@ -428,8 +421,8 @@ xrep_cow_alloc(
xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
- repl->fsbno = args.fsbno;
- repl->len = args.len;
+ del->br_startblock = args.fsbno;
+ del->br_blockcount = args.len;
return 0;
}
@@ -440,10 +433,12 @@ xrep_cow_alloc(
STATIC int
xrep_cow_alloc_rt(
struct xfs_scrub *sc,
- xfs_extlen_t maxlen,
- struct xrep_cow_extent *repl)
+ struct xfs_bmbt_irec *del)
{
- xfs_rtxlen_t maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
+ xfs_fsblock_t fsbno;
+ xfs_rtxlen_t maxrtx =
+ min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
+ xfs_extlen_t len;
int error;
error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
@@ -451,11 +446,14 @@ xrep_cow_alloc_rt(
return error;
error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
- false, &repl->fsbno, &repl->len);
+ false, &fsbno, &len);
if (error)
return error;
- xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
+ xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
+
+ del->br_startblock = fsbno;
+ del->br_blockcount = len;
return 0;
}
@@ -469,19 +467,19 @@ static inline int
xrep_cow_find_mapping(
struct xrep_cow *xc,
struct xfs_iext_cursor *icur,
- xfs_fileoff_t startoff,
- struct xfs_bmbt_irec *got)
+ xfs_fileoff_t badoff,
+ xfs_extlen_t badlen,
+ struct xfs_bmbt_irec *got,
+ struct xfs_bmbt_irec *rep)
{
struct xfs_inode *ip = xc->sc->ip;
struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
- if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
+ if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
goto bad;
+ memcpy(rep, got, sizeof(*rep));
- if (got->br_startoff > startoff)
- goto bad;
-
- if (got->br_blockcount == 0)
+ if (got->br_startoff > badoff)
goto bad;
if (isnullstartblock(got->br_startblock))
@@ -490,6 +488,24 @@ xrep_cow_find_mapping(
if (xfs_bmap_is_written_extent(got))
goto bad;
+ if (got->br_startoff < badoff) {
+ const int64_t delta = badoff - got->br_startoff;
+
+ rep->br_blockcount -= delta;
+ rep->br_startoff += delta;
+ rep->br_startblock += delta;
+ }
+
+ if (got->br_startoff + got->br_blockcount > badoff + badlen) {
+ const int64_t delta = (got->br_startoff + got->br_blockcount) -
+ (badoff + badlen);
+
+ rep->br_blockcount -= delta;
+ }
+
+ if (got->br_blockcount == 0)
+ goto bad;
+
return 0;
bad:
ASSERT(0);
@@ -500,46 +516,92 @@ xrep_cow_find_mapping(
#define REPLACE_RIGHT_SIDE (1U << 1)
/*
- * Given a CoW fork mapping @got and a replacement mapping @repl, remap the
- * beginning of @got with the space described by @rep.
+ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
+ * described by @rep into the cow fork, pushing aside @got as necessary. @icur
+ * must point to iext tree leaf containing @got.
*/
static inline void
xrep_cow_replace_mapping(
- struct xfs_inode *ip,
- struct xfs_iext_cursor *icur,
- const struct xfs_bmbt_irec *got,
- const struct xrep_cow_extent *repl)
+ struct xfs_inode *ip,
+ struct xfs_iext_cursor *icur,
+ struct xfs_bmbt_irec *got,
+ struct xfs_bmbt_irec *rep)
{
- struct xfs_bmbt_irec new = *got; /* struct copy */
+ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+ xfs_fileoff_t rep_endoff =
+ rep->br_startoff + rep->br_blockcount;
+ xfs_fileoff_t got_endoff =
+ got->br_startoff + got->br_blockcount;
+ uint32_t state = BMAP_COWFORK;
- ASSERT(repl->len > 0);
+ ASSERT(rep->br_blockcount > 0);
ASSERT(!isnullstartblock(got->br_startblock));
+ ASSERT(got->br_startoff <= rep->br_startoff);
+ ASSERT(got_endoff >= rep_endoff);
- trace_xrep_cow_replace_mapping(ip, got, repl->fsbno, repl->len);
+ trace_xrep_cow_replace_mapping(ip, got, rep);
- if (got->br_blockcount == repl->len) {
+ if (got->br_startoff == rep->br_startoff)
+ state |= BMAP_LEFT_FILLING;
+ if (got_endoff == rep_endoff)
+ state |= BMAP_RIGHT_FILLING;
+
+ switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
+ case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+ /*
+ * Replacement matches the whole mapping, update the record.
+ */
+ xfs_iext_update_extent(ip, state, icur, rep);
+ break;
+ case BMAP_LEFT_FILLING:
+ /*
+ * Replace the first part of the mapping: Update the cursor
+ * position with the new mapping, then add a record with the
+ * tail of the old mapping.
+ */
+ got->br_startoff = rep_endoff;
+ got->br_blockcount -= rep->br_blockcount;
+ got->br_startblock += rep->br_blockcount;
+
+ xfs_iext_update_extent(ip, state, icur, rep);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, got, state);
+ break;
+ case BMAP_RIGHT_FILLING:
/*
- * The new extent is a complete replacement for the existing
- * extent. Update the COW fork record.
+ * Replacing the last part of the mapping. Shorten the current
+ * mapping then add a record with the new mapping.
*/
- new.br_startblock = repl->fsbno;
- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
- return;
+ got->br_blockcount -= rep->br_blockcount;
+
+ xfs_iext_update_extent(ip, state, icur, got);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, rep, state);
+ break;
+ case 0:
+ /*
+ * Replacing the middle of the extent. Shorten the current
+ * mapping, add a new record with the new mapping, and add a
+ * second new record with the tail of the old mapping.
+ */
+ got->br_blockcount = rep->br_startoff - got->br_startoff;
+
+ struct xfs_bmbt_irec new = {
+ .br_startoff = rep_endoff,
+ .br_blockcount = got_endoff - rep_endoff,
+ .br_state = got->br_state,
+ .br_startblock = got->br_startblock +
+ rep->br_blockcount +
+ got->br_blockcount,
+ };
+
+ xfs_iext_update_extent(ip, state, icur, got);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, rep, state);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, &new, state);
+ break;
}
-
- /*
- * The new extent can replace the beginning of the COW fork record.
- * Move the left side of @got upwards, then insert the new record.
- */
- new.br_startoff += repl->len;
- new.br_startblock += repl->len;
- new.br_blockcount -= repl->len;
- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
-
- new.br_startoff = got->br_startoff;
- new.br_startblock = repl->fsbno;
- new.br_blockcount = repl->len;
- xfs_iext_insert(ip, icur, &new, BMAP_COWFORK);
}
/*
@@ -553,33 +615,30 @@ xrep_cow_replace_range(
xfs_extlen_t *blockcount)
{
struct xfs_iext_cursor icur;
- struct xrep_cow_extent repl;
- struct xfs_bmbt_irec got;
+ struct xfs_bmbt_irec got, rep;
struct xfs_scrub *sc = xc->sc;
- xfs_fileoff_t nextoff;
- xfs_extlen_t alloc_len;
+ xfs_fsblock_t old_fsbno;
int error;
/*
- * Put the existing CoW fork mapping in @got. If @got ends before
- * @rep, truncate @rep so we only replace one extent mapping at a time.
+ * Put the existing CoW fork mapping in @got, and put in @rep the
+ * contents of @got trimmed to @startoff/@blockcount. We only want
+ * to replace the bad region, and only one mapping at a time.
*/
- error = xrep_cow_find_mapping(xc, &icur, startoff, &got);
+ error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got,
+ &rep);
if (error)
return error;
- nextoff = min(startoff + *blockcount,
- got.br_startoff + got.br_blockcount);
+ old_fsbno = rep.br_startblock;
/*
* Allocate a replacement extent. If we don't fill all the blocks,
* shorten the quantity that will be deleted in this step.
*/
- alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN,
- nextoff - startoff);
if (XFS_IS_REALTIME_INODE(sc->ip))
- error = xrep_cow_alloc_rt(sc, alloc_len, &repl);
+ error = xrep_cow_alloc_rt(sc, &rep);
else
- error = xrep_cow_alloc(sc, alloc_len, &repl);
+ error = xrep_cow_alloc(sc, &rep);
if (error)
return error;
@@ -587,7 +646,7 @@ xrep_cow_replace_range(
* Replace the old mapping with the new one, and commit the metadata
* changes made so far.
*/
- xrep_cow_replace_mapping(sc->ip, &icur, &got, &repl);
+ xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
xfs_inode_set_cowblocks_tag(sc->ip);
error = xfs_defer_finish(&sc->tp);
@@ -596,15 +655,15 @@ xrep_cow_replace_range(
/* Note the old CoW staging extents; we'll reap them all later. */
if (XFS_IS_REALTIME_INODE(sc->ip))
- error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks,
- got.br_startblock, repl.len);
+ error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno,
+ rep.br_blockcount);
else
- error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks,
- got.br_startblock, repl.len);
+ error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno,
+ rep.br_blockcount);
if (error)
return error;
- *blockcount = repl.len;
+ *blockcount = rep.br_blockcount;
return 0;
}
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob enabled
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
2026-07-10 5:21 ` [PATCH 1/8] xfs: don't replace the wrong part of the cow fork Darrick J. Wong
@ 2026-07-10 5:22 ` Darrick J. Wong
2026-07-10 5:37 ` Christoph Hellwig
2026-07-10 5:22 ` [PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c Darrick J. Wong
` (5 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:22 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
Introduce a new behavior for the cow fork repair code: if the debugging
knob is enabled, we'll pick a random subrange of each cow fork mapping
to mark as bad. This will exercise the xrep_cow_replace_mapping more
thoroughly.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/scrub/cow_repair.c | 35 +++++++++++++++++++++++++++++++----
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c
index ca3405a26b641c..8507a1185b2b45 100644
--- a/fs/xfs/scrub/cow_repair.c
+++ b/fs/xfs/scrub/cow_repair.c
@@ -224,6 +224,31 @@ xrep_cow_mark_missing_staging_rmap(
xfs_gbno_to_fsb(cur->bc_group, rec_bno), rec_len);
}
+#ifdef DEBUG
+/*
+ * Trim the start and end of the current mapping by up to 1/4 of the length
+ * and mark that as "bad" to test the cow fork repair mechanism.
+ */
+static inline int
+xrep_cow_debug_replacement(
+ struct xrep_cow *xc)
+{
+ xfs_fsblock_t fsbno = xc->irec.br_startblock;
+ xfs_extlen_t len = xc->irec.br_blockcount;
+ uint32_t trim;
+
+ /* get_random_u32_below requires a nonzero argument */
+ trim = len > 4 ? get_random_u32_below(len / 4) : 0;
+ len -= trim;
+
+ trim = len > 4 ? get_random_u32_below(len / 4) : 0;
+ fsbno += trim;
+ len -= trim;
+
+ return xrep_cow_mark_file_range(xc, fsbno, len);
+}
+#endif
+
/*
* Find any part of the CoW fork mapping that isn't a single-owner CoW staging
* extent and mark the corresponding part of the file range in the bitmap.
@@ -293,8 +318,9 @@ xrep_cow_find_bad(
* If userspace is forcing us to rebuild the CoW fork or someone turned
* on the debugging knob, replace everything in the CoW fork.
*/
- if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD) ||
- XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR))
+ if (XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR))
+ error = xrep_cow_debug_replacement(xc);
+ else if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD)
error = xrep_cow_mark_file_range(xc, xc->irec.br_startblock,
xc->irec.br_blockcount);
@@ -375,8 +401,9 @@ xrep_cow_find_bad_rt(
* turned on the debugging knob, replace everything in the
* CoW fork and then scan for staging extents in the refcountbt.
*/
- if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD) ||
- XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR))
+ if (XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR))
+ error = xrep_cow_debug_replacement(xc);
+ else if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD)
error = xrep_cow_mark_file_range(xc, xc->irec.br_startblock,
xc->irec.br_blockcount);
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
2026-07-10 5:21 ` [PATCH 1/8] xfs: don't replace the wrong part of the cow fork Darrick J. Wong
2026-07-10 5:22 ` [PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob enabled Darrick J. Wong
@ 2026-07-10 5:22 ` Darrick J. Wong
2026-07-10 5:37 ` Christoph Hellwig
2026-07-10 5:22 ` [PATCH 4/8] xfs: don't wrap around quota ids in dqiterate Darrick J. Wong
` (4 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:22 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
Move the actual details of (partially) replacing a COW fork mapping to
xfs_bmap_util.c so that all the code doing hairy operations on subsets
of bmbt_irecs are kept together.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/scrub/trace.h | 41 -------------------
fs/xfs/xfs_bmap_util.h | 4 ++
fs/xfs/xfs_trace.h | 41 +++++++++++++++++++
fs/xfs/scrub/cow_repair.c | 95 +--------------------------------------------
fs/xfs/xfs_bmap_util.c | 89 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 136 insertions(+), 134 deletions(-)
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index d48a6db5b5f308..d5d39d82749e5c 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -2671,47 +2671,6 @@ TRACE_EVENT(xrep_cow_mark_file_range,
__entry->blockcount)
);
-TRACE_EVENT(xrep_cow_replace_mapping,
- TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
- const struct xfs_bmbt_irec *rep),
- TP_ARGS(ip, got, rep),
- TP_STRUCT__entry(
- __field(dev_t, dev)
- __field(xfs_ino_t, ino)
- __field(xfs_fsblock_t, startblock)
- __field(xfs_fileoff_t, startoff)
- __field(xfs_filblks_t, blockcount)
- __field(xfs_exntst_t, state)
- __field(xfs_fileoff_t, new_startoff)
- __field(xfs_fsblock_t, new_startblock)
- __field(xfs_extlen_t, new_blockcount)
- __field(xfs_exntst_t, new_state)
- ),
- TP_fast_assign(
- __entry->dev = ip->i_mount->m_super->s_dev;
- __entry->ino = I_INO(ip);
- __entry->startoff = got->br_startoff;
- __entry->startblock = got->br_startblock;
- __entry->blockcount = got->br_blockcount;
- __entry->state = got->br_state;
- __entry->new_startoff = rep->br_startoff;
- __entry->new_startblock = rep->br_startblock;
- __entry->new_blockcount = rep->br_blockcount;
- __entry->new_state = rep->br_state;
- ),
- TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
- MAJOR(__entry->dev), MINOR(__entry->dev),
- __entry->ino,
- __entry->startoff,
- __entry->startblock,
- __entry->blockcount,
- __entry->state,
- __entry->new_startoff,
- __entry->new_startblock,
- __entry->new_blockcount,
- __entry->new_state)
-);
-
TRACE_EVENT(xrep_cow_free_staging,
TP_PROTO(const struct xfs_perag *pag, xfs_agblock_t agbno,
xfs_extlen_t blockcount),
diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h
index c477b336163040..eaaf094154b9f9 100644
--- a/fs/xfs/xfs_bmap_util.h
+++ b/fs/xfs/xfs_bmap_util.h
@@ -81,4 +81,8 @@ int xfs_bmap_count_blocks(struct xfs_trans *tp, struct xfs_inode *ip,
int xfs_flush_unmap_range(struct xfs_inode *ip, xfs_off_t offset,
xfs_off_t len);
+void xfs_bmap_replace_cow_mapping(struct xfs_inode *ip,
+ struct xfs_iext_cursor *icur, struct xfs_bmbt_irec *got,
+ struct xfs_bmbt_irec *rep);
+
#endif /* __XFS_BMAP_UTIL_H__ */
diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h
index d478693674f952..aeb89ac53bf190 100644
--- a/fs/xfs/xfs_trace.h
+++ b/fs/xfs/xfs_trace.h
@@ -6439,6 +6439,47 @@ TRACE_EVENT(xfs_verify_media_error,
__entry->error)
);
+TRACE_EVENT(xfs_bmap_replace_cow_mapping,
+ TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
+ const struct xfs_bmbt_irec *rep),
+ TP_ARGS(ip, got, rep),
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(xfs_ino_t, ino)
+ __field(xfs_fsblock_t, startblock)
+ __field(xfs_fileoff_t, startoff)
+ __field(xfs_filblks_t, blockcount)
+ __field(xfs_exntst_t, state)
+ __field(xfs_fileoff_t, new_startoff)
+ __field(xfs_fsblock_t, new_startblock)
+ __field(xfs_extlen_t, new_blockcount)
+ __field(xfs_exntst_t, new_state)
+ ),
+ TP_fast_assign(
+ __entry->dev = ip->i_mount->m_super->s_dev;
+ __entry->ino = I_INO(ip);
+ __entry->startoff = got->br_startoff;
+ __entry->startblock = got->br_startblock;
+ __entry->blockcount = got->br_blockcount;
+ __entry->state = got->br_state;
+ __entry->new_startoff = rep->br_startoff;
+ __entry->new_startblock = rep->br_startblock;
+ __entry->new_blockcount = rep->br_blockcount;
+ __entry->new_state = rep->br_state;
+ ),
+ TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino,
+ __entry->startoff,
+ __entry->startblock,
+ __entry->blockcount,
+ __entry->state,
+ __entry->new_startoff,
+ __entry->new_startblock,
+ __entry->new_blockcount,
+ __entry->new_state)
+);
+
#endif /* _TRACE_XFS_H */
#undef TRACE_INCLUDE_PATH
diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c
index 8507a1185b2b45..1153e6a603fccf 100644
--- a/fs/xfs/scrub/cow_repair.c
+++ b/fs/xfs/scrub/cow_repair.c
@@ -29,6 +29,7 @@
#include "xfs_rtalloc.h"
#include "xfs_rtbitmap.h"
#include "xfs_rtgroup.h"
+#include "xfs_bmap_util.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
@@ -539,98 +540,6 @@ xrep_cow_find_mapping(
return -EFSCORRUPTED;
}
-#define REPLACE_LEFT_SIDE (1U << 0)
-#define REPLACE_RIGHT_SIDE (1U << 1)
-
-/*
- * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
- * described by @rep into the cow fork, pushing aside @got as necessary. @icur
- * must point to iext tree leaf containing @got.
- */
-static inline void
-xrep_cow_replace_mapping(
- struct xfs_inode *ip,
- struct xfs_iext_cursor *icur,
- struct xfs_bmbt_irec *got,
- struct xfs_bmbt_irec *rep)
-{
- struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
- xfs_fileoff_t rep_endoff =
- rep->br_startoff + rep->br_blockcount;
- xfs_fileoff_t got_endoff =
- got->br_startoff + got->br_blockcount;
- uint32_t state = BMAP_COWFORK;
-
- ASSERT(rep->br_blockcount > 0);
- ASSERT(!isnullstartblock(got->br_startblock));
- ASSERT(got->br_startoff <= rep->br_startoff);
- ASSERT(got_endoff >= rep_endoff);
-
- trace_xrep_cow_replace_mapping(ip, got, rep);
-
- if (got->br_startoff == rep->br_startoff)
- state |= BMAP_LEFT_FILLING;
- if (got_endoff == rep_endoff)
- state |= BMAP_RIGHT_FILLING;
-
- switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
- case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
- /*
- * Replacement matches the whole mapping, update the record.
- */
- xfs_iext_update_extent(ip, state, icur, rep);
- break;
- case BMAP_LEFT_FILLING:
- /*
- * Replace the first part of the mapping: Update the cursor
- * position with the new mapping, then add a record with the
- * tail of the old mapping.
- */
- got->br_startoff = rep_endoff;
- got->br_blockcount -= rep->br_blockcount;
- got->br_startblock += rep->br_blockcount;
-
- xfs_iext_update_extent(ip, state, icur, rep);
- xfs_iext_next(ifp, icur);
- xfs_iext_insert(ip, icur, got, state);
- break;
- case BMAP_RIGHT_FILLING:
- /*
- * Replacing the last part of the mapping. Shorten the current
- * mapping then add a record with the new mapping.
- */
- got->br_blockcount -= rep->br_blockcount;
-
- xfs_iext_update_extent(ip, state, icur, got);
- xfs_iext_next(ifp, icur);
- xfs_iext_insert(ip, icur, rep, state);
- break;
- case 0:
- /*
- * Replacing the middle of the extent. Shorten the current
- * mapping, add a new record with the new mapping, and add a
- * second new record with the tail of the old mapping.
- */
- got->br_blockcount = rep->br_startoff - got->br_startoff;
-
- struct xfs_bmbt_irec new = {
- .br_startoff = rep_endoff,
- .br_blockcount = got_endoff - rep_endoff,
- .br_state = got->br_state,
- .br_startblock = got->br_startblock +
- rep->br_blockcount +
- got->br_blockcount,
- };
-
- xfs_iext_update_extent(ip, state, icur, got);
- xfs_iext_next(ifp, icur);
- xfs_iext_insert(ip, icur, rep, state);
- xfs_iext_next(ifp, icur);
- xfs_iext_insert(ip, icur, &new, state);
- break;
- }
-}
-
/*
* Replace the unwritten CoW staging extent backing the given file range with a
* new space extent that isn't as problematic.
@@ -673,7 +582,7 @@ xrep_cow_replace_range(
* Replace the old mapping with the new one, and commit the metadata
* changes made so far.
*/
- xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
+ xfs_bmap_replace_cow_mapping(sc->ip, &icur, &got, &rep);
xfs_inode_set_cowblocks_tag(sc->ip);
error = xfs_defer_finish(&sc->tp);
diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c
index 3b9f262f8e9128..c88b9ade7389dd 100644
--- a/fs/xfs/xfs_bmap_util.c
+++ b/fs/xfs/xfs_bmap_util.c
@@ -1744,3 +1744,92 @@ xfs_swap_extents(
xfs_trans_cancel(tp);
goto out_unlock_ilock;
}
+
+/*
+ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
+ * described by @rep into the cow fork, pushing aside @got as necessary. @icur
+ * must point to iext tree leaf containing @got.
+ */
+void
+xfs_bmap_replace_cow_mapping(
+ struct xfs_inode *ip,
+ struct xfs_iext_cursor *icur,
+ struct xfs_bmbt_irec *got,
+ struct xfs_bmbt_irec *rep)
+{
+ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+ xfs_fileoff_t rep_endoff =
+ rep->br_startoff + rep->br_blockcount;
+ xfs_fileoff_t got_endoff =
+ got->br_startoff + got->br_blockcount;
+ uint32_t state = BMAP_COWFORK;
+
+ ASSERT(rep->br_blockcount > 0);
+ ASSERT(!isnullstartblock(got->br_startblock));
+ ASSERT(got->br_startoff <= rep->br_startoff);
+ ASSERT(got_endoff >= rep_endoff);
+
+ trace_xfs_bmap_replace_cow_mapping(ip, got, rep);
+
+ if (got->br_startoff == rep->br_startoff)
+ state |= BMAP_LEFT_FILLING;
+ if (got_endoff == rep_endoff)
+ state |= BMAP_RIGHT_FILLING;
+
+ switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
+ case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+ /*
+ * Replacement matches the whole mapping, update the record.
+ */
+ xfs_iext_update_extent(ip, state, icur, rep);
+ break;
+ case BMAP_LEFT_FILLING:
+ /*
+ * Replace the first part of the mapping: Update the cursor
+ * position with the new mapping, then add a record with the
+ * tail of the old mapping.
+ */
+ got->br_startoff = rep_endoff;
+ got->br_blockcount -= rep->br_blockcount;
+ got->br_startblock += rep->br_blockcount;
+
+ xfs_iext_update_extent(ip, state, icur, rep);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, got, state);
+ break;
+ case BMAP_RIGHT_FILLING:
+ /*
+ * Replacing the last part of the mapping. Shorten the current
+ * mapping then add a record with the new mapping.
+ */
+ got->br_blockcount -= rep->br_blockcount;
+
+ xfs_iext_update_extent(ip, state, icur, got);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, rep, state);
+ break;
+ case 0:
+ /*
+ * Replacing the middle of the extent. Shorten the current
+ * mapping, add a new record with the new mapping, and add a
+ * second new record with the tail of the old mapping.
+ */
+ got->br_blockcount = rep->br_startoff - got->br_startoff;
+
+ struct xfs_bmbt_irec new = {
+ .br_startoff = rep_endoff,
+ .br_blockcount = got_endoff - rep_endoff,
+ .br_state = got->br_state,
+ .br_startblock = got->br_startblock +
+ rep->br_blockcount +
+ got->br_blockcount,
+ };
+
+ xfs_iext_update_extent(ip, state, icur, got);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, rep, state);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, &new, state);
+ break;
+ }
+}
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 4/8] xfs: don't wrap around quota ids in dqiterate
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
` (2 preceding siblings ...)
2026-07-10 5:22 ` [PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c Darrick J. Wong
@ 2026-07-10 5:22 ` Darrick J. Wong
2026-07-10 5:22 ` [PATCH 5/8] xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging Darrick J. Wong
` (3 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:22 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
LOLLM noticed that q_id is an unsigned 32-bit variable. If it happens
to be set to XFS_DQ_ID_MAX due to a filesystem that actually has a dquot
for ID_MAX, then this addition will truncate to zero and the iteration
starts over. Fix this by casting to u64.
Cc: <stable@vger.kernel.org> # v6.8
Fixes: 21d7500929c8a0 ("xfs: improve dquot iteration for scrub")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/scrub/dqiterate.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/xfs/scrub/dqiterate.c b/fs/xfs/scrub/dqiterate.c
index 10950e4bd4c3c0..079dc4e691a01a 100644
--- a/fs/xfs/scrub/dqiterate.c
+++ b/fs/xfs/scrub/dqiterate.c
@@ -205,7 +205,7 @@ xchk_dquot_iter(
if (error)
return error;
- cursor->id = dq->q_id + 1;
+ cursor->id = (uint64_t)dq->q_id + 1;
*dqpp = dq;
return 1;
}
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 5/8] xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
` (3 preceding siblings ...)
2026-07-10 5:22 ` [PATCH 4/8] xfs: don't wrap around quota ids in dqiterate Darrick J. Wong
@ 2026-07-10 5:22 ` Darrick J. Wong
2026-07-10 5:23 ` [PATCH 6/8] xfs: use the rt version of the cow staging checker Darrick J. Wong
` (2 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:22 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
LOLLM points out that we pass the wrong btree cursor here. We want the
rtrefcount btree cursor, not the non-rt one. This is fairly benign
since it only affects tracing data.
Cc: <stable@vger.kernel.org> # v6.14
Fixes: 91683bb3f264c0 ("xfs: cross-reference checks with the rt refcount btree")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/scrub/rtrefcount.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c
index 0d10ce2910c2cb..4e7c540c8d2307 100644
--- a/fs/xfs/scrub/rtrefcount.c
+++ b/fs/xfs/scrub/rtrefcount.c
@@ -607,7 +607,7 @@ xchk_xref_is_rt_cow_staging(
/* CoW lookup returned a shared extent record? */
if (rc.rc_domain != XFS_REFC_DOMAIN_COW)
- xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0);
+ xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0);
/* Must be at least as long as what was passed in */
if (rc.rc_blockcount < len)
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 6/8] xfs: use the rt version of the cow staging checker
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
` (4 preceding siblings ...)
2026-07-10 5:22 ` [PATCH 5/8] xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging Darrick J. Wong
@ 2026-07-10 5:23 ` Darrick J. Wong
2026-07-10 5:23 ` [PATCH 7/8] xfs: write the rg superblock when fixing it Darrick J. Wong
2026-07-10 5:23 ` [PATCH 8/8] xfs: grab rtrmap btree when checking rgsuper Darrick J. Wong
7 siblings, 0 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:23 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
LOLLM also noticed that xchk_rtrmapbt_xref ought to be using the rtdev
version of the "is this a cow extent?" helper function, not the datadev
one.
Cc: <stable@vger.kernel.org> # v6.14
Fixes: 91683bb3f264c0 ("xfs: cross-reference checks with the rt refcount btree")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/scrub/rtrmap.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/xfs/scrub/rtrmap.c b/fs/xfs/scrub/rtrmap.c
index 043be93c714884..b3b2cf17ba2c08 100644
--- a/fs/xfs/scrub/rtrmap.c
+++ b/fs/xfs/scrub/rtrmap.c
@@ -209,7 +209,7 @@ xchk_rtrmapbt_xref(
xfs_rgbno_to_rtb(sc->sr.rtg, irec->rm_startblock),
irec->rm_blockcount);
if (irec->rm_owner == XFS_RMAP_OWN_COW)
- xchk_xref_is_cow_staging(sc, irec->rm_startblock,
+ xchk_xref_is_rt_cow_staging(sc, irec->rm_startblock,
irec->rm_blockcount);
else
xchk_rtrmapbt_xref_rtrefc(sc, irec);
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 7/8] xfs: write the rg superblock when fixing it
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
` (5 preceding siblings ...)
2026-07-10 5:23 ` [PATCH 6/8] xfs: use the rt version of the cow staging checker Darrick J. Wong
@ 2026-07-10 5:23 ` Darrick J. Wong
2026-07-10 5:23 ` [PATCH 8/8] xfs: grab rtrmap btree when checking rgsuper Darrick J. Wong
7 siblings, 0 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:23 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
The rtgroup superblock fixer should write the rtgroup superblock.
LOLLM noticed this, oops. :/
Cc: <stable@vger.kernel.org> # v6.13
Fixes: 1433f8f9cead37 ("xfs: repair realtime group superblock")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
---
fs/xfs/scrub/rgsuper.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c
index 482f899a518a85..a52d37c33ca15a 100644
--- a/fs/xfs/scrub/rgsuper.c
+++ b/fs/xfs/scrub/rgsuper.c
@@ -80,9 +80,13 @@ int
xrep_rgsuperblock(
struct xfs_scrub *sc)
{
+ struct xfs_buf *sb_bp;
+
ASSERT(rtg_rgno(sc->sr.rtg) == 0);
xfs_log_sb(sc->tp);
+ sb_bp = xfs_trans_getsb(sc->tp);
+ xfs_log_rtsb(sc->tp, sb_bp);
return 0;
}
#endif /* CONFIG_XFS_ONLINE_REPAIR */
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 8/8] xfs: grab rtrmap btree when checking rgsuper
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
` (6 preceding siblings ...)
2026-07-10 5:23 ` [PATCH 7/8] xfs: write the rg superblock when fixing it Darrick J. Wong
@ 2026-07-10 5:23 ` Darrick J. Wong
7 siblings, 0 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-10 5:23 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
LOLLM noticed that we aren't grabbing the rtrmap btree when we check the
realtime group superblock. As a result, none of the cross-referencing
checks have ever run. Fix this.
Cc: <stable@vger.kernel.org> # v6.14
Fixes: 428e4884656db9 ("xfs: allow queued realtime intents to drain before scrubbing")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/scrub/rgsuper.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c
index a52d37c33ca15a..c92212d8eb11b0 100644
--- a/fs/xfs/scrub/rgsuper.c
+++ b/fs/xfs/scrub/rgsuper.c
@@ -23,6 +23,8 @@ int
xchk_setup_rgsuperblock(
struct xfs_scrub *sc)
{
+ if (xchk_need_intent_drain(sc))
+ xchk_fsgates_enable(sc, XCHK_FSGATES_DRAIN);
return xchk_trans_alloc(sc, 0);
}
@@ -43,6 +45,7 @@ xchk_rgsuperblock(
struct xfs_scrub *sc)
{
xfs_rgnumber_t rgno = sc->sm->sm_agno;
+ unsigned int flags;
int error;
/*
@@ -63,7 +66,12 @@ xchk_rgsuperblock(
if (!xchk_xref_process_error(sc, 0, 0, &error))
return error;
- error = xchk_rtgroup_lock(sc, &sc->sr, XFS_RTGLOCK_BITMAP_SHARED);
+ if (xfs_has_rtrmapbt(sc->mp))
+ flags = XFS_RTGLOCK_BITMAP | XFS_RTGLOCK_RMAP;
+ else
+ flags = XFS_RTGLOCK_BITMAP_SHARED;
+
+ error = xchk_rtgroup_lock(sc, &sc->sr, flags);
if (error)
return error;
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 1/8] xfs: don't replace the wrong part of the cow fork
2026-07-10 5:21 ` [PATCH 1/8] xfs: don't replace the wrong part of the cow fork Darrick J. Wong
@ 2026-07-10 5:37 ` Christoph Hellwig
0 siblings, 0 replies; 13+ messages in thread
From: Christoph Hellwig @ 2026-07-10 5:37 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: cem, hch, stable, linux-xfs
On Thu, Jul 09, 2026 at 10:21:56PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> LOLLM points out that xfs_iext_lookup_extent can return a @got where
> got->br_startoff < startoff. In this case, xrep_cow_replace_range
> replaces the entire mapping instead of just the part that had been
> marked bad in the bitmap, but advances the bitmap cursor in
> xrep_cow_replace by the amount replaced. As a result, we fail to
> replace the end of the bad range, and replace part of the good range.
>
> Fix this by rewriting the replace method to handle replacing the middle
> of a cow fork mapping. This we do by returning both the current mapping
> as @got, and the subset of the mapping that we want to replace as @rep,
> using @rep to store the results of the new allocation, and comparing
> @rep to @got to figure out the exact transformations needed.
>
> Cc: <stable@vger.kernel.org> # v6.8
> Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> Assisted-by: LOLLM # finding obvious bugs
> ---
> fs/xfs/scrub/trace.h | 28 ++++--
> fs/xfs/scrub/cow_repair.c | 203 +++++++++++++++++++++++++++++----------------
> 2 files changed, 148 insertions(+), 83 deletions(-)
>
>
> diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
> index 1b7d9e07a27d3d..d48a6db5b5f308 100644
> --- a/fs/xfs/scrub/trace.h
> +++ b/fs/xfs/scrub/trace.h
> @@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
> );
>
> TRACE_EVENT(xrep_cow_replace_mapping,
> - TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
> - xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
> - TP_ARGS(ip, irec, new_startblock, new_blockcount),
> + TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
> + const struct xfs_bmbt_irec *rep),
> + TP_ARGS(ip, got, rep),
> TP_STRUCT__entry(
> __field(dev_t, dev)
> __field(xfs_ino_t, ino)
> @@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
> __field(xfs_fileoff_t, startoff)
> __field(xfs_filblks_t, blockcount)
> __field(xfs_exntst_t, state)
> + __field(xfs_fileoff_t, new_startoff)
> __field(xfs_fsblock_t, new_startblock)
> __field(xfs_extlen_t, new_blockcount)
> + __field(xfs_exntst_t, new_state)
> ),
> TP_fast_assign(
> __entry->dev = ip->i_mount->m_super->s_dev;
> __entry->ino = I_INO(ip);
> - __entry->startoff = irec->br_startoff;
> - __entry->startblock = irec->br_startblock;
> - __entry->blockcount = irec->br_blockcount;
> - __entry->state = irec->br_state;
> - __entry->new_startblock = new_startblock;
> - __entry->new_blockcount = new_blockcount;
> + __entry->startoff = got->br_startoff;
> + __entry->startblock = got->br_startblock;
> + __entry->blockcount = got->br_blockcount;
> + __entry->state = got->br_state;
> + __entry->new_startoff = rep->br_startoff;
> + __entry->new_startblock = rep->br_startblock;
> + __entry->new_blockcount = rep->br_blockcount;
> + __entry->new_state = rep->br_state;
> ),
> - TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
> + TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
> MAJOR(__entry->dev), MINOR(__entry->dev),
> __entry->ino,
> __entry->startoff,
> __entry->startblock,
> __entry->blockcount,
> __entry->state,
> + __entry->new_startoff,
> __entry->new_startblock,
> - __entry->new_blockcount)
> + __entry->new_blockcount,
> + __entry->new_state)
> );
>
> TRACE_EVENT(xrep_cow_free_staging,
> diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c
> index 0075b6d5a1b5ff..ca3405a26b641c 100644
> --- a/fs/xfs/scrub/cow_repair.c
> +++ b/fs/xfs/scrub/cow_repair.c
> @@ -80,12 +80,6 @@ struct xrep_cow {
> unsigned int next_bno;
> };
>
> -/* CoW staging extent. */
> -struct xrep_cow_extent {
> - xfs_fsblock_t fsbno;
> - xfs_extlen_t len;
> -};
> -
> /*
> * Mark the part of the file range that corresponds to the given physical
> * space. Caller must ensure that the physical range is within xc->irec.
> @@ -401,22 +395,21 @@ xrep_cow_find_bad_rt(
> STATIC int
> xrep_cow_alloc(
> struct xfs_scrub *sc,
> - xfs_extlen_t maxlen,
> - struct xrep_cow_extent *repl)
> + struct xfs_bmbt_irec *del)
> {
> struct xfs_alloc_arg args = {
> .tp = sc->tp,
> .mp = sc->mp,
> .oinfo = XFS_RMAP_OINFO_SKIP_UPDATE,
> .minlen = 1,
> - .maxlen = maxlen,
> + .maxlen = del->br_blockcount,
> .prod = 1,
> .resv = XFS_AG_RESV_NONE,
> .datatype = XFS_ALLOC_USERDATA,
> };
> int error;
>
> - error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
> + error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
> if (error)
> return error;
>
> @@ -428,8 +421,8 @@ xrep_cow_alloc(
>
> xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
>
> - repl->fsbno = args.fsbno;
> - repl->len = args.len;
> + del->br_startblock = args.fsbno;
> + del->br_blockcount = args.len;
> return 0;
> }
>
> @@ -440,10 +433,12 @@ xrep_cow_alloc(
> STATIC int
> xrep_cow_alloc_rt(
> struct xfs_scrub *sc,
> - xfs_extlen_t maxlen,
> - struct xrep_cow_extent *repl)
> + struct xfs_bmbt_irec *del)
> {
> - xfs_rtxlen_t maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
> + xfs_fsblock_t fsbno;
> + xfs_rtxlen_t maxrtx =
> + min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
> + xfs_extlen_t len;
> int error;
>
> error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
> @@ -451,11 +446,14 @@ xrep_cow_alloc_rt(
> return error;
>
> error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
> - false, &repl->fsbno, &repl->len);
> + false, &fsbno, &len);
> if (error)
> return error;
>
> - xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
> + xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
> +
> + del->br_startblock = fsbno;
> + del->br_blockcount = len;
> return 0;
> }
>
> @@ -469,19 +467,19 @@ static inline int
> xrep_cow_find_mapping(
> struct xrep_cow *xc,
> struct xfs_iext_cursor *icur,
> - xfs_fileoff_t startoff,
> - struct xfs_bmbt_irec *got)
> + xfs_fileoff_t badoff,
> + xfs_extlen_t badlen,
> + struct xfs_bmbt_irec *got,
> + struct xfs_bmbt_irec *rep)
> {
> struct xfs_inode *ip = xc->sc->ip;
> struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
>
> - if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
> + if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
> goto bad;
> + memcpy(rep, got, sizeof(*rep));
>
> - if (got->br_startoff > startoff)
> - goto bad;
> -
> - if (got->br_blockcount == 0)
> + if (got->br_startoff > badoff)
> goto bad;
>
> if (isnullstartblock(got->br_startblock))
> @@ -490,6 +488,24 @@ xrep_cow_find_mapping(
> if (xfs_bmap_is_written_extent(got))
> goto bad;
>
> + if (got->br_startoff < badoff) {
> + const int64_t delta = badoff - got->br_startoff;
> +
> + rep->br_blockcount -= delta;
> + rep->br_startoff += delta;
> + rep->br_startblock += delta;
> + }
> +
> + if (got->br_startoff + got->br_blockcount > badoff + badlen) {
> + const int64_t delta = (got->br_startoff + got->br_blockcount) -
> + (badoff + badlen);
> +
> + rep->br_blockcount -= delta;
Overly long line here. Although I'm not even sure what the point is in
this local delta variable that is only used once anyay.
Otherwise looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob enabled
2026-07-10 5:22 ` [PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob enabled Darrick J. Wong
@ 2026-07-10 5:37 ` Christoph Hellwig
0 siblings, 0 replies; 13+ messages in thread
From: Christoph Hellwig @ 2026-07-10 5:37 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: cem, hch, linux-xfs
On Thu, Jul 09, 2026 at 10:22:12PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Introduce a new behavior for the cow fork repair code: if the debugging
> knob is enabled, we'll pick a random subrange of each cow fork mapping
> to mark as bad. This will exercise the xrep_cow_replace_mapping more
> thoroughly.
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c
2026-07-10 5:22 ` [PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c Darrick J. Wong
@ 2026-07-10 5:37 ` Christoph Hellwig
0 siblings, 0 replies; 13+ messages in thread
From: Christoph Hellwig @ 2026-07-10 5:37 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: cem, hch, linux-xfs
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/8] xfs: don't replace the wrong part of the cow fork
2026-07-14 6:03 [PATCHSET v3 1/2] xfs: LLM-inspired bug fixes, part 1 Darrick J. Wong
@ 2026-07-14 6:04 ` Darrick J. Wong
0 siblings, 0 replies; 13+ messages in thread
From: Darrick J. Wong @ 2026-07-14 6:04 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
LOLLM points out that xfs_iext_lookup_extent can return a @got where
got->br_startoff < startoff. In this case, xrep_cow_replace_range
replaces the entire mapping instead of just the part that had been
marked bad in the bitmap, but advances the bitmap cursor in
xrep_cow_replace by the amount replaced. As a result, we fail to
replace the end of the bad range, and replace part of the good range.
Fix this by rewriting the replace method to handle replacing the middle
of a cow fork mapping. This we do by returning both the current mapping
as @got, and the subset of the mapping that we want to replace as @rep,
using @rep to store the results of the new allocation, and comparing
@rep to @got to figure out the exact transformations needed.
Cc: <stable@vger.kernel.org> # v6.8
Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/scrub/trace.h | 28 ++++--
fs/xfs/scrub/cow_repair.c | 203 +++++++++++++++++++++++++++++----------------
2 files changed, 148 insertions(+), 83 deletions(-)
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index 1b7d9e07a27d3d..d48a6db5b5f308 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
);
TRACE_EVENT(xrep_cow_replace_mapping,
- TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
- xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
- TP_ARGS(ip, irec, new_startblock, new_blockcount),
+ TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
+ const struct xfs_bmbt_irec *rep),
+ TP_ARGS(ip, got, rep),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
@@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
__field(xfs_fileoff_t, startoff)
__field(xfs_filblks_t, blockcount)
__field(xfs_exntst_t, state)
+ __field(xfs_fileoff_t, new_startoff)
__field(xfs_fsblock_t, new_startblock)
__field(xfs_extlen_t, new_blockcount)
+ __field(xfs_exntst_t, new_state)
),
TP_fast_assign(
__entry->dev = ip->i_mount->m_super->s_dev;
__entry->ino = I_INO(ip);
- __entry->startoff = irec->br_startoff;
- __entry->startblock = irec->br_startblock;
- __entry->blockcount = irec->br_blockcount;
- __entry->state = irec->br_state;
- __entry->new_startblock = new_startblock;
- __entry->new_blockcount = new_blockcount;
+ __entry->startoff = got->br_startoff;
+ __entry->startblock = got->br_startblock;
+ __entry->blockcount = got->br_blockcount;
+ __entry->state = got->br_state;
+ __entry->new_startoff = rep->br_startoff;
+ __entry->new_startblock = rep->br_startblock;
+ __entry->new_blockcount = rep->br_blockcount;
+ __entry->new_state = rep->br_state;
),
- TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
+ TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino,
__entry->startoff,
__entry->startblock,
__entry->blockcount,
__entry->state,
+ __entry->new_startoff,
__entry->new_startblock,
- __entry->new_blockcount)
+ __entry->new_blockcount,
+ __entry->new_state)
);
TRACE_EVENT(xrep_cow_free_staging,
diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c
index 0075b6d5a1b5ff..ca3405a26b641c 100644
--- a/fs/xfs/scrub/cow_repair.c
+++ b/fs/xfs/scrub/cow_repair.c
@@ -80,12 +80,6 @@ struct xrep_cow {
unsigned int next_bno;
};
-/* CoW staging extent. */
-struct xrep_cow_extent {
- xfs_fsblock_t fsbno;
- xfs_extlen_t len;
-};
-
/*
* Mark the part of the file range that corresponds to the given physical
* space. Caller must ensure that the physical range is within xc->irec.
@@ -401,22 +395,21 @@ xrep_cow_find_bad_rt(
STATIC int
xrep_cow_alloc(
struct xfs_scrub *sc,
- xfs_extlen_t maxlen,
- struct xrep_cow_extent *repl)
+ struct xfs_bmbt_irec *del)
{
struct xfs_alloc_arg args = {
.tp = sc->tp,
.mp = sc->mp,
.oinfo = XFS_RMAP_OINFO_SKIP_UPDATE,
.minlen = 1,
- .maxlen = maxlen,
+ .maxlen = del->br_blockcount,
.prod = 1,
.resv = XFS_AG_RESV_NONE,
.datatype = XFS_ALLOC_USERDATA,
};
int error;
- error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
+ error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
if (error)
return error;
@@ -428,8 +421,8 @@ xrep_cow_alloc(
xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
- repl->fsbno = args.fsbno;
- repl->len = args.len;
+ del->br_startblock = args.fsbno;
+ del->br_blockcount = args.len;
return 0;
}
@@ -440,10 +433,12 @@ xrep_cow_alloc(
STATIC int
xrep_cow_alloc_rt(
struct xfs_scrub *sc,
- xfs_extlen_t maxlen,
- struct xrep_cow_extent *repl)
+ struct xfs_bmbt_irec *del)
{
- xfs_rtxlen_t maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
+ xfs_fsblock_t fsbno;
+ xfs_rtxlen_t maxrtx =
+ min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
+ xfs_extlen_t len;
int error;
error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
@@ -451,11 +446,14 @@ xrep_cow_alloc_rt(
return error;
error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
- false, &repl->fsbno, &repl->len);
+ false, &fsbno, &len);
if (error)
return error;
- xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
+ xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
+
+ del->br_startblock = fsbno;
+ del->br_blockcount = len;
return 0;
}
@@ -469,19 +467,19 @@ static inline int
xrep_cow_find_mapping(
struct xrep_cow *xc,
struct xfs_iext_cursor *icur,
- xfs_fileoff_t startoff,
- struct xfs_bmbt_irec *got)
+ xfs_fileoff_t badoff,
+ xfs_extlen_t badlen,
+ struct xfs_bmbt_irec *got,
+ struct xfs_bmbt_irec *rep)
{
struct xfs_inode *ip = xc->sc->ip;
struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
- if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
+ if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
goto bad;
+ memcpy(rep, got, sizeof(*rep));
- if (got->br_startoff > startoff)
- goto bad;
-
- if (got->br_blockcount == 0)
+ if (got->br_startoff > badoff)
goto bad;
if (isnullstartblock(got->br_startblock))
@@ -490,6 +488,24 @@ xrep_cow_find_mapping(
if (xfs_bmap_is_written_extent(got))
goto bad;
+ if (got->br_startoff < badoff) {
+ const int64_t delta = badoff - got->br_startoff;
+
+ rep->br_blockcount -= delta;
+ rep->br_startoff += delta;
+ rep->br_startblock += delta;
+ }
+
+ if (got->br_startoff + got->br_blockcount > badoff + badlen) {
+ const int64_t delta = (got->br_startoff + got->br_blockcount) -
+ (badoff + badlen);
+
+ rep->br_blockcount -= delta;
+ }
+
+ if (got->br_blockcount == 0)
+ goto bad;
+
return 0;
bad:
ASSERT(0);
@@ -500,46 +516,92 @@ xrep_cow_find_mapping(
#define REPLACE_RIGHT_SIDE (1U << 1)
/*
- * Given a CoW fork mapping @got and a replacement mapping @repl, remap the
- * beginning of @got with the space described by @rep.
+ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
+ * described by @rep into the cow fork, pushing aside @got as necessary. @icur
+ * must point to iext tree leaf containing @got.
*/
static inline void
xrep_cow_replace_mapping(
- struct xfs_inode *ip,
- struct xfs_iext_cursor *icur,
- const struct xfs_bmbt_irec *got,
- const struct xrep_cow_extent *repl)
+ struct xfs_inode *ip,
+ struct xfs_iext_cursor *icur,
+ struct xfs_bmbt_irec *got,
+ struct xfs_bmbt_irec *rep)
{
- struct xfs_bmbt_irec new = *got; /* struct copy */
+ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+ xfs_fileoff_t rep_endoff =
+ rep->br_startoff + rep->br_blockcount;
+ xfs_fileoff_t got_endoff =
+ got->br_startoff + got->br_blockcount;
+ uint32_t state = BMAP_COWFORK;
- ASSERT(repl->len > 0);
+ ASSERT(rep->br_blockcount > 0);
ASSERT(!isnullstartblock(got->br_startblock));
+ ASSERT(got->br_startoff <= rep->br_startoff);
+ ASSERT(got_endoff >= rep_endoff);
- trace_xrep_cow_replace_mapping(ip, got, repl->fsbno, repl->len);
+ trace_xrep_cow_replace_mapping(ip, got, rep);
- if (got->br_blockcount == repl->len) {
+ if (got->br_startoff == rep->br_startoff)
+ state |= BMAP_LEFT_FILLING;
+ if (got_endoff == rep_endoff)
+ state |= BMAP_RIGHT_FILLING;
+
+ switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
+ case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+ /*
+ * Replacement matches the whole mapping, update the record.
+ */
+ xfs_iext_update_extent(ip, state, icur, rep);
+ break;
+ case BMAP_LEFT_FILLING:
+ /*
+ * Replace the first part of the mapping: Update the cursor
+ * position with the new mapping, then add a record with the
+ * tail of the old mapping.
+ */
+ got->br_startoff = rep_endoff;
+ got->br_blockcount -= rep->br_blockcount;
+ got->br_startblock += rep->br_blockcount;
+
+ xfs_iext_update_extent(ip, state, icur, rep);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, got, state);
+ break;
+ case BMAP_RIGHT_FILLING:
/*
- * The new extent is a complete replacement for the existing
- * extent. Update the COW fork record.
+ * Replacing the last part of the mapping. Shorten the current
+ * mapping then add a record with the new mapping.
*/
- new.br_startblock = repl->fsbno;
- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
- return;
+ got->br_blockcount -= rep->br_blockcount;
+
+ xfs_iext_update_extent(ip, state, icur, got);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, rep, state);
+ break;
+ case 0:
+ /*
+ * Replacing the middle of the extent. Shorten the current
+ * mapping, add a new record with the new mapping, and add a
+ * second new record with the tail of the old mapping.
+ */
+ got->br_blockcount = rep->br_startoff - got->br_startoff;
+
+ struct xfs_bmbt_irec new = {
+ .br_startoff = rep_endoff,
+ .br_blockcount = got_endoff - rep_endoff,
+ .br_state = got->br_state,
+ .br_startblock = got->br_startblock +
+ rep->br_blockcount +
+ got->br_blockcount,
+ };
+
+ xfs_iext_update_extent(ip, state, icur, got);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, rep, state);
+ xfs_iext_next(ifp, icur);
+ xfs_iext_insert(ip, icur, &new, state);
+ break;
}
-
- /*
- * The new extent can replace the beginning of the COW fork record.
- * Move the left side of @got upwards, then insert the new record.
- */
- new.br_startoff += repl->len;
- new.br_startblock += repl->len;
- new.br_blockcount -= repl->len;
- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
-
- new.br_startoff = got->br_startoff;
- new.br_startblock = repl->fsbno;
- new.br_blockcount = repl->len;
- xfs_iext_insert(ip, icur, &new, BMAP_COWFORK);
}
/*
@@ -553,33 +615,30 @@ xrep_cow_replace_range(
xfs_extlen_t *blockcount)
{
struct xfs_iext_cursor icur;
- struct xrep_cow_extent repl;
- struct xfs_bmbt_irec got;
+ struct xfs_bmbt_irec got, rep;
struct xfs_scrub *sc = xc->sc;
- xfs_fileoff_t nextoff;
- xfs_extlen_t alloc_len;
+ xfs_fsblock_t old_fsbno;
int error;
/*
- * Put the existing CoW fork mapping in @got. If @got ends before
- * @rep, truncate @rep so we only replace one extent mapping at a time.
+ * Put the existing CoW fork mapping in @got, and put in @rep the
+ * contents of @got trimmed to @startoff/@blockcount. We only want
+ * to replace the bad region, and only one mapping at a time.
*/
- error = xrep_cow_find_mapping(xc, &icur, startoff, &got);
+ error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got,
+ &rep);
if (error)
return error;
- nextoff = min(startoff + *blockcount,
- got.br_startoff + got.br_blockcount);
+ old_fsbno = rep.br_startblock;
/*
* Allocate a replacement extent. If we don't fill all the blocks,
* shorten the quantity that will be deleted in this step.
*/
- alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN,
- nextoff - startoff);
if (XFS_IS_REALTIME_INODE(sc->ip))
- error = xrep_cow_alloc_rt(sc, alloc_len, &repl);
+ error = xrep_cow_alloc_rt(sc, &rep);
else
- error = xrep_cow_alloc(sc, alloc_len, &repl);
+ error = xrep_cow_alloc(sc, &rep);
if (error)
return error;
@@ -587,7 +646,7 @@ xrep_cow_replace_range(
* Replace the old mapping with the new one, and commit the metadata
* changes made so far.
*/
- xrep_cow_replace_mapping(sc->ip, &icur, &got, &repl);
+ xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
xfs_inode_set_cowblocks_tag(sc->ip);
error = xfs_defer_finish(&sc->tp);
@@ -596,15 +655,15 @@ xrep_cow_replace_range(
/* Note the old CoW staging extents; we'll reap them all later. */
if (XFS_IS_REALTIME_INODE(sc->ip))
- error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks,
- got.br_startblock, repl.len);
+ error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno,
+ rep.br_blockcount);
else
- error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks,
- got.br_startblock, repl.len);
+ error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno,
+ rep.br_blockcount);
if (error)
return error;
- *blockcount = repl.len;
+ *blockcount = rep.br_blockcount;
return 0;
}
^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-07-14 6:04 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 5:21 [PATCHSET v2] xfs: LOLLM-inspired bug fixes, part 1 Darrick J. Wong
2026-07-10 5:21 ` [PATCH 1/8] xfs: don't replace the wrong part of the cow fork Darrick J. Wong
2026-07-10 5:37 ` Christoph Hellwig
2026-07-10 5:22 ` [PATCH 2/8] xfs: make cow repair somewhat flaky when debugging knob enabled Darrick J. Wong
2026-07-10 5:37 ` Christoph Hellwig
2026-07-10 5:22 ` [PATCH 3/8] xfs: move cow_replace_mapping to xfs_bmap_util.c Darrick J. Wong
2026-07-10 5:37 ` Christoph Hellwig
2026-07-10 5:22 ` [PATCH 4/8] xfs: don't wrap around quota ids in dqiterate Darrick J. Wong
2026-07-10 5:22 ` [PATCH 5/8] xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging Darrick J. Wong
2026-07-10 5:23 ` [PATCH 6/8] xfs: use the rt version of the cow staging checker Darrick J. Wong
2026-07-10 5:23 ` [PATCH 7/8] xfs: write the rg superblock when fixing it Darrick J. Wong
2026-07-10 5:23 ` [PATCH 8/8] xfs: grab rtrmap btree when checking rgsuper Darrick J. Wong
-- strict thread matches above, loose matches on Subject: below --
2026-07-14 6:03 [PATCHSET v3 1/2] xfs: LLM-inspired bug fixes, part 1 Darrick J. Wong
2026-07-14 6:04 ` [PATCH 1/8] xfs: don't replace the wrong part of the cow fork Darrick J. Wong
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox