[PATCH 19/27] xfs_repair: check existing realtime rmapbt entries against observed rmaps

Darrick J. Wong
2025-02-06T22:54:41Z

Once we've finished collecting reverse mapping observations from the
metadata scan, check those observations against the realtime rmap btree
(particularly if we're in -n mode) to detect rtrmapbt problems.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
repair/phase4.c | 14 +++
repair/rmap.c | 226 ++++++++++++++++++++++++++++++++++++++++++-------------
repair/rmap.h | 2
3 files changed, 188 insertions(+), 54 deletions(-)

diff --git a/repair/phase4.c b/repair/phase4.c
index 728d9ed84cdc7a..29efa58af33178 100644
--- a/repair/phase4.c
+++ b/repair/phase4.c
@@ -159,6 +159,16 @@ check_rmap_btrees(
rmaps_verify_btree(wq->wq_ctx, agno);
}

+static void
+check_rtrmap_btrees(
+ struct workqueue *wq,
+ xfs_agnumber_t agno,
+ void *arg)
+{
+ rmap_add_fixed_rtgroup_rec(wq->wq_ctx, agno);
+ rtrmaps_verify_btree(wq->wq_ctx, agno);
+}
+
static void
compute_ag_refcounts(
struct workqueue*wq,
@@ -211,6 +221,10 @@ process_rmap_data(
create_work_queue(&wq, mp, platform_nproc());
for (i = 0; i < mp->m_sb.sb_agcount; i++)
queue_work(&wq, check_rmap_btrees, i, NULL);
+ if (xfs_has_rtrmapbt(mp)) {
+ for (i = 0; i < mp->m_sb.sb_rgcount; i++)
+ queue_work(&wq, check_rtrmap_btrees, i, NULL);
+ }
destroy_work_queue(&wq);

if (!xfs_has_reflink(mp))
diff --git a/repair/rmap.c b/repair/rmap.c
index 8656c8df3cbc83..a40851b4d0dc69 100644
--- a/repair/rmap.c
+++ b/repair/rmap.c
@@ -16,6 +16,7 @@
#include "libfrog/platform.h"
#include "rcbag.h"
#include "rt.h"
+#include "prefetch.h"

#undef RMAP_DEBUG

@@ -195,8 +196,6 @@ rmaps_init(

for (i = 0; i < mp->m_sb.sb_rgcount; i++)
rmaps_init_rt(mp, i, &rg_rmaps[i]);
-
- discover_rtgroup_inodes(mp);
}

/*
@@ -573,6 +572,27 @@ rmap_add_fixed_ag_rec(
}
}

+/* Add this realtime group's fixed metadata to the incore data. */
+void
+rmap_add_fixed_rtgroup_rec(
+ struct xfs_mount *mp,
+ xfs_rgnumber_t rgno)
+{
+ struct xfs_rmap_irec rmap = {
+ .rm_startblock = 0,
+ .rm_blockcount = mp->m_sb.sb_rextsize,
+ .rm_owner = XFS_RMAP_OWN_FS,
+ .rm_offset = 0,
+ .rm_flags = 0,
+ };
+
+ if (!rmap_needs_work(mp))
+ return;
+
+ if (xfs_has_rtsb(mp) && rgno == 0)
+ rmap_add_mem_rec(mp, true, rgno, &rmap);
+}
+
/*
* Copy the per-AG btree reverse-mapping data into the rmapbt.
*
@@ -1213,62 +1233,25 @@ rmap_is_good(
#undef NEXTP
#undef NEXTL

-/*
- * Compare the observed reverse mappings against what's in the ag btree.
- */
-void
-rmaps_verify_btree(
- struct xfs_mount *mp,
- xfs_agnumber_t agno)
+static int
+rmap_compare_records(
+ struct xfs_btree_cur *rm_cur,
+ struct xfs_btree_cur *bt_cur,
+ unsigned int group)
{
- struct xfs_btree_cur *rm_cur;
struct xfs_rmap_irec rm_rec;
struct xfs_rmap_irec tmp;
- struct xfs_btree_cur *bt_cur = NULL;
- struct xfs_buf *agbp = NULL;
- struct xfs_perag *pag = NULL;
int have;
int error;

- if (!xfs_has_rmapbt(mp))
- return;
- if (rmapbt_suspect) {
- if (no_modify && agno == 0)
- do_warn(_("would rebuild corrupt rmap btrees.\n"));
- return;
- }
-
- /* Create cursors to rmap structures */
- error = rmap_init_mem_cursor(mp, NULL, false, agno, &rm_cur);
- if (error) {
- do_warn(_("Not enough memory to check reverse mappings.\n"));
- return;
- }
-
- pag = libxfs_perag_get(mp, agno);
- error = -libxfs_alloc_read_agf(pag, NULL, 0, &agbp);
- if (error) {
- do_warn(_("Could not read AGF %u to check rmap btree.\n"),
- agno);
- goto err_pag;
- }
-
- /* Leave the per-ag data "uninitialized" since we rewrite it later */
- clear_bit(XFS_AGSTATE_AGF_INIT, &pag->pag_opstate);
-
- bt_cur = libxfs_rmapbt_init_cursor(mp, NULL, agbp, pag);
- if (!bt_cur) {
- do_warn(_("Not enough memory to check reverse mappings.\n"));
- goto err_agf;
- }
-
while ((error = rmap_get_mem_rec(rm_cur, &rm_rec)) == 1) {
error = rmap_lookup(bt_cur, &rm_rec, &tmp, &have);
if (error) {
do_warn(
_("Could not read reverse-mapping record for (%u/%u).\n"),
- agno, rm_rec.rm_startblock);
- goto err_cur;
+ group,
+ rm_rec.rm_startblock);
+ return error;
}

/*
@@ -1283,15 +1266,15 @@ _("Could not read reverse-mapping record for (%u/%u).\n"),
if (error) {
do_warn(
_("Could not read reverse-mapping record for (%u/%u).\n"),
- agno, rm_rec.rm_startblock);
- goto err_cur;
+ group, rm_rec.rm_startblock);
+ return error;
}
}
if (!have) {
do_warn(
_("Missing reverse-mapping record for (%u/%u) %slen %u owner %"PRId64" \
%s%soff %"PRIu64"\n"),
- agno, rm_rec.rm_startblock,
+ group, rm_rec.rm_startblock,
(rm_rec.rm_flags & XFS_RMAP_UNWRITTEN) ?
_("unwritten ") : "",
rm_rec.rm_blockcount,
@@ -1304,12 +1287,12 @@ _("Missing reverse-mapping record for (%u/%u) %slen %u owner %"PRId64" \
continue;
}

- /* Compare each refcount observation against the btree's */
+ /* Compare each rmap observation against the btree's */
if (!rmap_is_good(&rm_rec, &tmp)) {
do_warn(
_("Incorrect reverse-mapping: saw (%u/%u) %slen %u owner %"PRId64" %s%soff \
%"PRIu64"; should be (%u/%u) %slen %u owner %"PRId64" %s%soff %"PRIu64"\n"),
- agno, tmp.rm_startblock,
+ group, tmp.rm_startblock,
(tmp.rm_flags & XFS_RMAP_UNWRITTEN) ?
_("unwritten ") : "",
tmp.rm_blockcount,
@@ -1319,7 +1302,7 @@ _("Incorrect reverse-mapping: saw (%u/%u) %slen %u owner %"PRId64" %s%soff \
(tmp.rm_flags & XFS_RMAP_BMBT_BLOCK) ?
_("bmbt ") : "",
tmp.rm_offset,
- agno, rm_rec.rm_startblock,
+ group, rm_rec.rm_startblock,
(rm_rec.rm_flags & XFS_RMAP_UNWRITTEN) ?
_("unwritten ") : "",
rm_rec.rm_blockcount,
@@ -1332,8 +1315,61 @@ _("Incorrect reverse-mapping: saw (%u/%u) %slen %u owner %"PRId64" %s%soff \
}
}

+ return error;
+}
+
+/*
+ * Compare the observed reverse mappings against what's in the ag btree.
+ */
+void
+rmaps_verify_btree(
+ struct xfs_mount *mp,
+ xfs_agnumber_t agno)
+{
+ struct xfs_btree_cur *rm_cur;
+ struct xfs_btree_cur *bt_cur = NULL;
+ struct xfs_buf *agbp = NULL;
+ struct xfs_perag *pag = NULL;
+ int error;
+
+ if (!xfs_has_rmapbt(mp))
+ return;
+ if (rmapbt_suspect) {
+ if (no_modify && agno == 0)
+ do_warn(_("would rebuild corrupt rmap btrees.\n"));
+ return;
+ }
+
+ /* Create cursors to rmap structures */
+ error = rmap_init_mem_cursor(mp, NULL, false, agno, &rm_cur);
+ if (error) {
+ do_warn(_("Not enough memory to check reverse mappings.\n"));
+ return;
+ }
+
+ pag = libxfs_perag_get(mp, agno);
+ error = -libxfs_alloc_read_agf(pag, NULL, 0, &agbp);
+ if (error) {
+ do_warn(_("Could not read AGF %u to check rmap btree.\n"),
+ agno);
+ goto err_pag;
+ }
+
+ /* Leave the per-ag data "uninitialized" since we rewrite it later */
+ clear_bit(XFS_AGSTATE_AGF_INIT, &pag->pag_opstate);
+
+ bt_cur = libxfs_rmapbt_init_cursor(mp, NULL, agbp, pag);
+ if (!bt_cur) {
+ do_warn(_("Not enough memory to check reverse mappings.\n"));
+ goto err_agf;
+ }
+
+ error = rmap_compare_records(rm_cur, bt_cur, agno);
+ if (error)
+ goto err_cur;
+
err_cur:
- libxfs_btree_del_cursor(bt_cur, XFS_BTREE_NOERROR);
+ libxfs_btree_del_cursor(bt_cur, error);
err_agf:
libxfs_buf_relse(agbp);
err_pag:
@@ -1341,6 +1377,88 @@ _("Incorrect reverse-mapping: saw (%u/%u) %slen %u owner %"PRId64" %s%soff \
libxfs_btree_del_cursor(rm_cur, error);
}

+/*
+ * Compare the observed reverse mappings against what's in the rtgroup btree.
+ */
+void
+rtrmaps_verify_btree(
+ struct xfs_mount *mp,
+ xfs_rgnumber_t rgno)
+{
+ struct xfs_btree_cur *rm_cur;
+ struct xfs_btree_cur *bt_cur = NULL;
+ struct xfs_rtgroup *rtg = NULL;
+ struct xfs_inode *ip = NULL;
+ int error;
+
+ if (!xfs_has_rmapbt(mp))
+ return;
+ if (rmapbt_suspect) {
+ if (no_modify && rgno == 0)
+ do_warn(_("would rebuild corrupt rmap btrees.\n"));
+ return;
+ }
+
+ /* Create cursors to rmap structures */
+ error = rmap_init_mem_cursor(mp, NULL, true, rgno, &rm_cur);
+ if (error) {
+ do_warn(_("Not enough memory to check reverse mappings.\n"));
+ return;
+ }
+
+ rtg = libxfs_rtgroup_get(mp, rgno);
+ if (!rtg) {
+ do_warn(_("Could not load rtgroup %u.\n"), rgno);
+ goto err_rcur;
+ }
+
+ ip = rtg_rmap(rtg);
+ if (!ip) {
+ do_warn(_("Could not find rtgroup %u rmap inode.\n"), rgno);
+ goto err_rtg;
+ }
+
+ if (ip->i_df.if_format != XFS_DINODE_FMT_META_BTREE) {
+ do_warn(
+_("rtgroup %u rmap inode has wrong format 0x%x, expected 0x%x\n"),
+ rgno, ip->i_df.if_format,
+ XFS_DINODE_FMT_META_BTREE);
+ goto err_rtg;
+ }
+
+ if (ip->i_metatype != XFS_METAFILE_RTRMAP) {
+ do_warn(
+_("rtgroup %u rmap inode has wrong metatype 0x%x, expected 0x%x\n"),
+ rgno, ip->i_df.if_format,
+ XFS_METAFILE_RTRMAP);
+ goto err_rtg;
+ }
+
+ if (xfs_inode_has_attr_fork(ip) &&
+ !(xfs_has_metadir(mp) && xfs_has_parent(mp))) {
+ do_warn(
+_("rtgroup %u rmap inode should not have extended attributes\n"), rgno);
+ goto err_rtg;
+ }
+
+ bt_cur = libxfs_rtrmapbt_init_cursor(NULL, rtg);
+ if (!bt_cur) {
+ do_warn(_("Not enough memory to check reverse mappings.\n"));
+ goto err_rtg;
+ }
+
+ error = rmap_compare_records(rm_cur, bt_cur, rgno);
+ if (error)
+ goto err_cur;
+
+err_cur:
+ libxfs_btree_del_cursor(bt_cur, error);
+err_rtg:
+ libxfs_rtgroup_put(rtg);
+err_rcur:
+ libxfs_btree_del_cursor(rm_cur, error);
+}
+
/*
* Compare the key fields of two rmap records -- positive if key1 > key2,
* negative if key1 < key2, and zero if equal.
diff --git a/repair/rmap.h b/repair/rmap.h
index b5c8b4f0bef794..ebda561e59bc8f 100644
--- a/repair/rmap.h
+++ b/repair/rmap.h
@@ -21,6 +21,7 @@ void rmap_add_bmbt_rec(struct xfs_mount *mp, xfs_ino_t ino, int whichfork,
bool rmaps_are_mergeable(struct xfs_rmap_irec *r1, struct xfs_rmap_irec *r2);

void rmap_add_fixed_ag_rec(struct xfs_mount *mp, xfs_agnumber_t agno);
+void rmap_add_fixed_rtgroup_rec(struct xfs_mount *mp, xfs_rgnumber_t rgno);

int rmap_add_agbtree_mapping(struct xfs_mount *mp, xfs_agnumber_t agno,
xfs_agblock_t agbno, xfs_extlen_t len, uint64_t owner);
@@ -30,6 +31,7 @@ uint64_t rmap_record_count(struct xfs_mount *mp, bool isrt,
xfs_agnumber_t agno);
extern void rmap_avoid_check(struct xfs_mount *mp);
void rmaps_verify_btree(struct xfs_mount *mp, xfs_agnumber_t agno);
+void rtrmaps_verify_btree(struct xfs_mount *mp, xfs_rgnumber_t rgno);

extern int64_t rmap_diffkeys(struct xfs_rmap_irec *kp1,
struct xfs_rmap_irec *kp2);