[PATCH 23/27] xfs_repair: rebuild the bmap btree for realtime files

Darrick J. Wong
2025-02-06T22:55:44Z

Use the realtime rmap btree information to rebuild an inode's data fork
when appropriate.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
repair/bmap_repair.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 105 insertions(+), 4 deletions(-)

diff --git a/repair/bmap_repair.c b/repair/bmap_repair.c
index 7e7c2a39f5724b..5d1f639be81ff4 100644
--- a/repair/bmap_repair.c
+++ b/repair/bmap_repair.c
@@ -209,6 +209,101 @@ xrep_bmap_scan_ag(
return error;
}

+/* Check for any obvious errors or conflicts in the file mapping. */
+STATIC int
+xrep_bmap_check_rtfork_rmap(
+ struct repair_ctx *sc,
+ struct xfs_btree_cur *cur,
+ const struct xfs_rmap_irec *rec)
+{
+ /* xattr extents are never stored on realtime devices */
+ if (rec->rm_flags & XFS_RMAP_ATTR_FORK)
+ return EFSCORRUPTED;
+
+ /* bmbt blocks are never stored on realtime devices */
+ if (rec->rm_flags & XFS_RMAP_BMBT_BLOCK)
+ return EFSCORRUPTED;
+
+ /* Data extents for non-rt files are never stored on the rt device. */
+ if (!XFS_IS_REALTIME_INODE(sc->ip))
+ return EFSCORRUPTED;
+
+ /* Check the file offsets and physical extents. */
+ if (!xfs_verify_fileext(sc->mp, rec->rm_offset, rec->rm_blockcount))
+ return EFSCORRUPTED;
+
+ /* Check that this fits in the rt volume. */
+ if (!xfs_verify_rgbext(to_rtg(cur->bc_group), rec->rm_startblock,
+ rec->rm_blockcount))
+ return EFSCORRUPTED;
+
+ return 0;
+}
+
+/* Record realtime extents that belong to this inode's fork. */
+STATIC int
+xrep_bmap_walk_rtrmap(
+ struct xfs_btree_cur *cur,
+ const struct xfs_rmap_irec *rec,
+ void *priv)
+{
+ struct xrep_bmap *rb = priv;
+ int error = 0;
+
+ /* Skip extents which are not owned by this inode and fork. */
+ if (rec->rm_owner != rb->sc->ip->i_ino)
+ return 0;
+
+ error = xrep_bmap_check_rtfork_rmap(rb->sc, cur, rec);
+ if (error)
+ return error;
+
+ /*
+ * Record all blocks allocated to this file even if the extent isn't
+ * for the fork we're rebuilding so that we can reset di_nblocks later.
+ */
+ rb->nblocks += rec->rm_blockcount;
+
+ /* If this rmap isn't for the fork we want, we're done. */
+ if (rb->whichfork == XFS_DATA_FORK &&
+ (rec->rm_flags & XFS_RMAP_ATTR_FORK))
+ return 0;
+ if (rb->whichfork == XFS_ATTR_FORK &&
+ !(rec->rm_flags & XFS_RMAP_ATTR_FORK))
+ return 0;
+
+ return xrep_bmap_from_rmap(rb, rec->rm_offset, rec->rm_startblock,
+ rec->rm_blockcount,
+ rec->rm_flags & XFS_RMAP_UNWRITTEN);
+}
+
+/*
+ * Scan the realtime reverse mappings to build the new extent map. The rt rmap
+ * inodes must be loaded from disk explicitly here, since we have not yet
+ * validated the metadata directory tree but do not wish to throw away user
+ * data unnecessarily.
+ */
+STATIC int
+xrep_bmap_scan_rt(
+ struct xrep_bmap *rb,
+ struct xfs_rtgroup *rtg)
+{
+ struct repair_ctx *sc = rb->sc;
+ struct xfs_mount *mp = sc->mp;
+ struct xfs_inode *ip = rtg_rmap(rtg);
+ struct xfs_btree_cur *cur;
+ int error;
+
+ /* failed to load the rtdir inode? */
+ if (!xfs_has_rtrmapbt(mp) || !ip)
+ return ENOENT;
+
+ cur = libxfs_rtrmapbt_init_cursor(sc->tp, rtg);
+ error = -libxfs_rmap_query_all(cur, xrep_bmap_walk_rtrmap, rb);
+ libxfs_btree_del_cursor(cur, error);
+ return error;
+}
+
/*
* Collect block mappings for this fork of this inode and decide if we have
* enough space to rebuild. Caller is responsible for cleaning up the list if
@@ -219,8 +314,18 @@ xrep_bmap_find_mappings(
struct xrep_bmap *rb)
{
struct xfs_perag *pag = NULL;
+ struct xfs_rtgroup *rtg = NULL;
int error;

+ /* Iterate the rtrmaps for extents. */
+ while ((rtg = xfs_rtgroup_next(rb->sc->mp, rtg))) {
+ error = xrep_bmap_scan_rt(rb, rtg);
+ if (error) {
+ libxfs_rtgroup_put(rtg);
+ return error;
+ }
+ }
+
/* Iterate the rmaps for extents. */
while ((pag = xfs_perag_next(rb->sc->mp, pag))) {
error = xrep_bmap_scan_ag(rb, pag);
@@ -570,10 +675,6 @@ xrep_bmap_check_inputs(
return EINVAL;
}

- /* Don't know how to rebuild realtime data forks. */
- if (XFS_IS_REALTIME_INODE(sc->ip))
- return EOPNOTSUPP;
-
return 0;
}