Re: [PATCH 3/3] xfs_repair: rebuild block mappings from rmapbt data

Bill O'Donnell
2024-03-14T17:13:25Z

On Tue, Mar 12, 2024 at 07:14:40PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Use rmap records to rebuild corrupt inode forks instead of zapping
> the whole inode if we think the rmap data is reasonably sane.
>
> Signed-off-by: Darrick J. Wong <djwong@kernel.org>

Reviewed-by: Bill O'Donnell <bodonnel@redhat.com>

> ---
> include/xfs_trans.h | 2
> libxfs/libxfs_api_defs.h | 15 +
> libxfs/trans.c | 48 +++
> repair/Makefile | 2
> repair/agbtree.c | 2
> repair/bmap_repair.c | 749 ++++++++++++++++++++++++++++++++++++++++++++++
> repair/bmap_repair.h | 13 +
> repair/bulkload.c | 205 ++++++++++++-
> repair/bulkload.h | 24 +
> repair/dinode.c | 54 +++
> repair/rmap.c | 2
> repair/rmap.h | 1
> 12 files changed, 1106 insertions(+), 11 deletions(-)
> create mode 100644 repair/bmap_repair.c
> create mode 100644 repair/bmap_repair.h
>
>
> diff --git a/include/xfs_trans.h b/include/xfs_trans.h
> index ab298ccfe556..ac82c3bc480a 100644
> --- a/include/xfs_trans.h
> +++ b/include/xfs_trans.h
> @@ -98,6 +98,8 @@ int libxfs_trans_alloc_rollable(struct xfs_mount *mp, uint blocks,
> int libxfs_trans_alloc_empty(struct xfs_mount *mp, struct xfs_trans **tpp);
> int libxfs_trans_commit(struct xfs_trans *);
> void libxfs_trans_cancel(struct xfs_trans *);
> +int libxfs_trans_reserve_more(struct xfs_trans *tp, uint blocks,
> + uint rtextents);
>
> /* cancel dfops associated with a transaction */
> void xfs_defer_cancel(struct xfs_trans *);
> diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
> index 28960317ab6b..769733ec2ee3 100644
> --- a/libxfs/libxfs_api_defs.h
> +++ b/libxfs/libxfs_api_defs.h
> @@ -32,7 +32,7 @@
> #define xfs_alloc_fix_freelist libxfs_alloc_fix_freelist
> #define xfs_alloc_min_freelist libxfs_alloc_min_freelist
> #define xfs_alloc_read_agf libxfs_alloc_read_agf
> -#define xfs_alloc_vextent libxfs_alloc_vextent
> +#define xfs_alloc_vextent_start_ag libxfs_alloc_vextent_start_ag
>
> #define xfs_ascii_ci_hashname libxfs_ascii_ci_hashname
>
> @@ -44,11 +44,18 @@
> #define xfs_attr_shortform_verify libxfs_attr_shortform_verify
>
> #define __xfs_bmap_add_free __libxfs_bmap_add_free
> +#define xfs_bmap_validate_extent libxfs_bmap_validate_extent
> #define xfs_bmapi_read libxfs_bmapi_read
> +#define xfs_bmapi_remap libxfs_bmapi_remap
> #define xfs_bmapi_write libxfs_bmapi_write
> #define xfs_bmap_last_offset libxfs_bmap_last_offset
> +#define xfs_bmbt_calc_size libxfs_bmbt_calc_size
> +#define xfs_bmbt_commit_staged_btree libxfs_bmbt_commit_staged_btree
> +#define xfs_bmbt_disk_get_startoff libxfs_bmbt_disk_get_startoff
> +#define xfs_bmbt_disk_set_all libxfs_bmbt_disk_set_all
> #define xfs_bmbt_maxlevels_ondisk libxfs_bmbt_maxlevels_ondisk
> #define xfs_bmbt_maxrecs libxfs_bmbt_maxrecs
> +#define xfs_bmbt_stage_cursor libxfs_bmbt_stage_cursor
> #define xfs_bmdr_maxrecs libxfs_bmdr_maxrecs
>
> #define xfs_btree_bload libxfs_btree_bload
> @@ -117,6 +124,7 @@
>
> #define xfs_finobt_calc_reserves libxfs_finobt_calc_reserves
> #define xfs_free_extent libxfs_free_extent
> +#define xfs_free_extent_later libxfs_free_extent_later
> #define xfs_free_perag libxfs_free_perag
> #define xfs_fs_geometry libxfs_fs_geometry
> #define xfs_highbit32 libxfs_highbit32
> @@ -127,7 +135,10 @@
> #define xfs_ialloc_read_agi libxfs_ialloc_read_agi
> #define xfs_idata_realloc libxfs_idata_realloc
> #define xfs_idestroy_fork libxfs_idestroy_fork
> +#define xfs_iext_first libxfs_iext_first
> +#define xfs_iext_insert_raw libxfs_iext_insert_raw
> #define xfs_iext_lookup_extent libxfs_iext_lookup_extent
> +#define xfs_iext_next libxfs_iext_next
> #define xfs_ifork_zap_attr libxfs_ifork_zap_attr
> #define xfs_imap_to_bp libxfs_imap_to_bp
> #define xfs_initialize_perag libxfs_initialize_perag
> @@ -174,10 +185,12 @@
> #define xfs_rmapbt_stage_cursor libxfs_rmapbt_stage_cursor
> #define xfs_rmap_compare libxfs_rmap_compare
> #define xfs_rmap_get_rec libxfs_rmap_get_rec
> +#define xfs_rmap_ino_bmbt_owner libxfs_rmap_ino_bmbt_owner
> #define xfs_rmap_irec_offset_pack libxfs_rmap_irec_offset_pack
> #define xfs_rmap_irec_offset_unpack libxfs_rmap_irec_offset_unpack
> #define xfs_rmap_lookup_le libxfs_rmap_lookup_le
> #define xfs_rmap_lookup_le_range libxfs_rmap_lookup_le_range
> +#define xfs_rmap_query_all libxfs_rmap_query_all
> #define xfs_rmap_query_range libxfs_rmap_query_range
>
> #define xfs_rtbitmap_getword libxfs_rtbitmap_getword
> diff --git a/libxfs/trans.c b/libxfs/trans.c
> index bd1186b24e62..8143a6a99f62 100644
> --- a/libxfs/trans.c
> +++ b/libxfs/trans.c
> @@ -1143,3 +1143,51 @@ libxfs_trans_alloc_inode(
> *tpp = tp;
> return 0;
> }
> +
> +/*
> + * Try to reserve more blocks for a transaction. The single use case we
> + * support is for offline repair -- use a transaction to gather data without
> + * fear of btree cycle deadlocks; calculate how many blocks we really need
> + * from that data; and only then start modifying data. This can fail due to
> + * ENOSPC, so we have to be able to cancel the transaction.
> + */
> +int
> +libxfs_trans_reserve_more(
> + struct xfs_trans *tp,
> + uint blocks,
> + uint rtextents)
> +{
> + int error = 0;
> +
> + ASSERT(!(tp->t_flags & XFS_TRANS_DIRTY));
> +
> + /*
> + * Attempt to reserve the needed disk blocks by decrementing
> + * the number needed from the number available. This will
> + * fail if the count would go below zero.
> + */
> + if (blocks > 0) {
> + if (tp->t_mountp->m_sb.sb_fdblocks < blocks)
> + return -ENOSPC;
> + tp->t_blk_res += blocks;
> + }
> +
> + /*
> + * Attempt to reserve the needed realtime extents by decrementing
> + * the number needed from the number available. This will
> + * fail if the count would go below zero.
> + */
> + if (rtextents > 0) {
> + if (tp->t_mountp->m_sb.sb_rextents < rtextents) {
> + error = -ENOSPC;
> + goto out_blocks;
> + }
> + }
> +
> + return 0;
> +out_blocks:
> + if (blocks > 0)
> + tp->t_blk_res -= blocks;
> +
> + return error;
> +}
> diff --git a/repair/Makefile b/repair/Makefile
> index 2c40e59a30fc..e5014deb0ce8 100644
> --- a/repair/Makefile
> +++ b/repair/Makefile
> @@ -16,6 +16,7 @@ HFILES = \
> avl.h \
> bulkload.h \
> bmap.h \
> + bmap_repair.h \
> btree.h \
> da_util.h \
> dinode.h \
> @@ -41,6 +42,7 @@ CFILES = \
> avl.c \
> bulkload.c \
> bmap.c \
> + bmap_repair.c \
> btree.c \
> da_util.c \
> dino_chunks.c \
> diff --git a/repair/agbtree.c b/repair/agbtree.c
> index c6f0512fe7de..38f3f7b8feac 100644
> --- a/repair/agbtree.c
> +++ b/repair/agbtree.c
> @@ -22,7 +22,7 @@ init_rebuild(
> {
> memset(btr, 0, sizeof(struct bt_rebuild));
>
> - bulkload_init_ag(&btr->newbt, sc, oinfo);
> + bulkload_init_ag(&btr->newbt, sc, oinfo, NULLFSBLOCK);
> btr->bload.max_dirty = XFS_B_TO_FSBT(sc->mp, 256U << 10); /* 256K */
> bulkload_estimate_ag_slack(sc, &btr->bload, est_agfreeblocks);
> }
> diff --git a/repair/bmap_repair.c b/repair/bmap_repair.c
> new file mode 100644
> index 000000000000..7705980621c1
> --- /dev/null
> +++ b/repair/bmap_repair.c
> @@ -0,0 +1,749 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2019-2024 Oracle. All Rights Reserved.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + */
> +#include <libxfs.h>
> +#include "btree.h"
> +#include "err_protos.h"
> +#include "libxlog.h"
> +#include "incore.h"
> +#include "globals.h"
> +#include "dinode.h"
> +#include "slab.h"
> +#include "rmap.h"
> +#include "bulkload.h"
> +#include "bmap_repair.h"
> +
> +#define min_t(type, x, y) ( ((type)(x)) > ((type)(y)) ? ((type)(y)) : ((type)(x)) )
> +
> +/*
> + * Inode Fork Block Mapping (BMBT) Repair
> + * ======================================
> + *
> + * Gather all the rmap records for the inode and fork we're fixing, reset the
> + * incore fork, then recreate the btree.
> + */
> +struct xrep_bmap {
> + /* List of new bmap records. */
> + struct xfs_slab *bmap_records;
> + struct xfs_slab_cursor *bmap_cursor;
> +
> + /* New fork. */
> + struct bulkload new_fork_info;
> + struct xfs_btree_bload bmap_bload;
> +
> + struct repair_ctx *sc;
> +
> + /* How many blocks did we find allocated to this file? */
> + xfs_rfsblock_t nblocks;
> +
> + /* How many bmbt blocks did we find for this fork? */
> + xfs_rfsblock_t old_bmbt_block_count;
> +
> + /* Which fork are we fixing? */
> + int whichfork;
> +};
> +
> +/* Remember this reverse-mapping as a series of bmap records. */
> +STATIC int
> +xrep_bmap_from_rmap(
> + struct xrep_bmap *rb,
> + xfs_fileoff_t startoff,
> + xfs_fsblock_t startblock,
> + xfs_filblks_t blockcount,
> + bool unwritten)
> +{
> + struct xfs_bmbt_rec rbe;
> + struct xfs_bmbt_irec irec;
> + int error = 0;
> +
> + irec.br_startoff = startoff;
> + irec.br_startblock = startblock;
> + irec.br_state = unwritten ? XFS_EXT_UNWRITTEN : XFS_EXT_NORM;
> +
> + do {
> + xfs_failaddr_t fa;
> +
> + irec.br_blockcount = min_t(xfs_filblks_t, blockcount,
> + XFS_MAX_BMBT_EXTLEN);
> +
> + fa = libxfs_bmap_validate_extent(rb->sc->ip, rb->whichfork,
> + &irec);
> + if (fa)
> + return -EFSCORRUPTED;
> +
> + libxfs_bmbt_disk_set_all(&rbe, &irec);
> +
> + error = slab_add(rb->bmap_records, &rbe);
> + if (error)
> + return error;
> +
> + irec.br_startblock += irec.br_blockcount;
> + irec.br_startoff += irec.br_blockcount;
> + blockcount -= irec.br_blockcount;
> + } while (blockcount > 0);
> +
> + return 0;
> +}
> +
> +/* Check for any obvious errors or conflicts in the file mapping. */
> +STATIC int
> +xrep_bmap_check_fork_rmap(
> + struct xrep_bmap *rb,
> + struct xfs_btree_cur *cur,
> + const struct xfs_rmap_irec *rec)
> +{
> + struct repair_ctx *sc = rb->sc;
> +
> + /*
> + * Data extents for rt files are never stored on the data device, but
> + * everything else (xattrs, bmbt blocks) can be.
> + */
> + if (XFS_IS_REALTIME_INODE(sc->ip) &&
> + !(rec->rm_flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK)))
> + return EFSCORRUPTED;
> +
> + /* Check that this is within the AG. */
> + if (!xfs_verify_agbext(cur->bc_ag.pag, rec->rm_startblock,
> + rec->rm_blockcount))
> + return EFSCORRUPTED;
> +
> + /* No contradictory flags. */
> + if ((rec->rm_flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK)) &&
> + (rec->rm_flags & XFS_RMAP_UNWRITTEN))
> + return EFSCORRUPTED;
> +
> + /* Check the file offset range. */
> + if (!(rec->rm_flags & XFS_RMAP_BMBT_BLOCK) &&
> + !xfs_verify_fileext(sc->mp, rec->rm_offset, rec->rm_blockcount))
> + return EFSCORRUPTED;
> +
> + return 0;
> +}
> +
> +/* Record extents that belong to this inode's fork. */
> +STATIC int
> +xrep_bmap_walk_rmap(
> + struct xfs_btree_cur *cur,
> + const struct xfs_rmap_irec *rec,
> + void *priv)
> +{
> + struct xrep_bmap *rb = priv;
> + struct xfs_mount *mp = cur->bc_mp;
> + xfs_fsblock_t fsbno;
> + int error;
> +
> + /* 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_fork_rmap(rb, 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;
> +
> + fsbno = XFS_AGB_TO_FSB(mp, cur->bc_ag.pag->pag_agno,
> + rec->rm_startblock);
> +
> + if (rec->rm_flags & XFS_RMAP_BMBT_BLOCK) {
> + rb->old_bmbt_block_count += rec->rm_blockcount;
> + return 0;
> + }
> +
> + return xrep_bmap_from_rmap(rb, rec->rm_offset, fsbno,
> + rec->rm_blockcount,
> + rec->rm_flags & XFS_RMAP_UNWRITTEN);
> +}
> +
> +/* Compare two bmap extents. */
> +static int
> +xrep_bmap_extent_cmp(
> + const void *a,
> + const void *b)
> +{
> + xfs_fileoff_t ao;
> + xfs_fileoff_t bo;
> +
> + ao = libxfs_bmbt_disk_get_startoff((struct xfs_bmbt_rec *)a);
> + bo = libxfs_bmbt_disk_get_startoff((struct xfs_bmbt_rec *)b);
> +
> + if (ao > bo)
> + return 1;
> + else if (ao < bo)
> + return -1;
> + return 0;
> +}
> +
> +/* Scan one AG for reverse mappings that we can turn into extent maps. */
> +STATIC int
> +xrep_bmap_scan_ag(
> + struct xrep_bmap *rb,
> + struct xfs_perag *pag)
> +{
> + struct repair_ctx *sc = rb->sc;
> + struct xfs_mount *mp = sc->mp;
> + struct xfs_buf *agf_bp = NULL;
> + struct xfs_btree_cur *cur;
> + int error;
> +
> + error = -libxfs_alloc_read_agf(pag, sc->tp, 0, &agf_bp);
> + if (error)
> + return error;
> + if (!agf_bp)
> + return ENOMEM;
> + cur = libxfs_rmapbt_init_cursor(mp, sc->tp, agf_bp, pag);
> + error = -libxfs_rmap_query_all(cur, xrep_bmap_walk_rmap, rb);
> + libxfs_btree_del_cursor(cur, error);
> + libxfs_trans_brelse(sc->tp, agf_bp);
> + 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
> + * anything goes wrong.
> + */
> +STATIC int
> +xrep_bmap_find_mappings(
> + struct xrep_bmap *rb)
> +{
> + struct xfs_perag *pag;
> + xfs_agnumber_t agno;
> + int error;
> +
> + /* Iterate the rmaps for extents. */
> + for_each_perag(rb->sc->mp, agno, pag) {
> + error = xrep_bmap_scan_ag(rb, pag);
> + if (error) {
> + libxfs_perag_put(pag);
> + return error;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/* Retrieve bmap data for bulk load. */
> +STATIC int
> +xrep_bmap_get_records(
> + struct xfs_btree_cur *cur,
> + unsigned int idx,
> + struct xfs_btree_block *block,
> + unsigned int nr_wanted,
> + void *priv)
> +{
> + struct xfs_bmbt_rec *rec;
> + struct xfs_bmbt_irec *irec = &cur->bc_rec.b;
> + struct xrep_bmap *rb = priv;
> + union xfs_btree_rec *block_rec;
> + unsigned int loaded;
> +
> + for (loaded = 0; loaded < nr_wanted; loaded++, idx++) {
> + rec = pop_slab_cursor(rb->bmap_cursor);
> + libxfs_bmbt_disk_get_all(rec, irec);
> +
> + block_rec = libxfs_btree_rec_addr(cur, idx, block);
> + cur->bc_ops->init_rec_from_cur(cur, block_rec);
> + }
> +
> + return loaded;
> +}
> +
> +/* Feed one of the new btree blocks to the bulk loader. */
> +STATIC int
> +xrep_bmap_claim_block(
> + struct xfs_btree_cur *cur,
> + union xfs_btree_ptr *ptr,
> + void *priv)
> +{
> + struct xrep_bmap *rb = priv;
> +
> + return bulkload_claim_block(cur, &rb->new_fork_info, ptr);
> +}
> +
> +/* Figure out how much space we need to create the incore btree root block. */
> +STATIC size_t
> +xrep_bmap_iroot_size(
> + struct xfs_btree_cur *cur,
> + unsigned int level,
> + unsigned int nr_this_level,
> + void *priv)
> +{
> + ASSERT(level > 0);
> +
> + return XFS_BMAP_BROOT_SPACE_CALC(cur->bc_mp, nr_this_level);
> +}
> +
> +/* Update the inode counters. */
> +STATIC int
> +xrep_bmap_reset_counters(
> + struct xrep_bmap *rb)
> +{
> + struct repair_ctx *sc = rb->sc;
> + struct xbtree_ifakeroot *ifake = &rb->new_fork_info.ifake;
> + int64_t delta;
> +
> + /*
> + * Update the inode block counts to reflect the extents we found in the
> + * rmapbt.
> + */
> + delta = ifake->if_blocks - rb->old_bmbt_block_count;
> + sc->ip->i_nblocks = rb->nblocks + delta;
> + libxfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
> +
> + /* Quotas don't exist so we're done. */
> + return 0;
> +}
> +
> +/*
> + * Ensure that the inode being repaired is ready to handle a certain number of
> + * extents, or return EFSCORRUPTED. Caller must hold the ILOCK of the inode
> + * being repaired and have joined it to the scrub transaction.
> + */
> +static int
> +xrep_ino_ensure_extent_count(
> + struct repair_ctx *sc,
> + int whichfork,
> + xfs_extnum_t nextents)
> +{
> + xfs_extnum_t max_extents;
> + bool large_extcount;
> +
> + large_extcount = xfs_inode_has_large_extent_counts(sc->ip);
> + max_extents = xfs_iext_max_nextents(large_extcount, whichfork);
> + if (nextents <= max_extents)
> + return 0;
> + if (large_extcount)
> + return EFSCORRUPTED;
> + if (!xfs_has_large_extent_counts(sc->mp))
> + return EFSCORRUPTED;
> +
> + max_extents = xfs_iext_max_nextents(true, whichfork);
> + if (nextents > max_extents)
> + return EFSCORRUPTED;
> +
> + sc->ip->i_diflags2 |= XFS_DIFLAG2_NREXT64;
> + libxfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
> + return 0;
> +}
> +
> +/*
> + * Create a new iext tree and load it with block mappings. If the inode is
> + * in extents format, that's all we need to do to commit the new mappings.
> + * If it is in btree format, this takes care of preloading the incore tree.
> + */
> +STATIC int
> +xrep_bmap_extents_load(
> + struct xrep_bmap *rb,
> + struct xfs_btree_cur *bmap_cur,
> + uint64_t nextents)
> +{
> + struct xfs_iext_cursor icur;
> + struct xbtree_ifakeroot *ifake = &rb->new_fork_info.ifake;
> + struct xfs_ifork *ifp = ifake->if_fork;
> + unsigned int i;
> + int error;
> +
> + ASSERT(ifp->if_bytes == 0);
> +
> + error = init_slab_cursor(rb->bmap_records, xrep_bmap_extent_cmp,
> + &rb->bmap_cursor);
> + if (error)
> + return error;
> +
> + /* Add all the mappings to the incore extent tree. */
> + libxfs_iext_first(ifp, &icur);
> + for (i = 0; i < nextents; i++) {
> + struct xfs_bmbt_rec *rec;
> +
> + rec = pop_slab_cursor(rb->bmap_cursor);
> + libxfs_bmbt_disk_get_all(rec, &bmap_cur->bc_rec.b);
> + libxfs_iext_insert_raw(ifp, &icur, &bmap_cur->bc_rec.b);
> + ifp->if_nextents++;
> + libxfs_iext_next(ifp, &icur);
> + }
> + free_slab_cursor(&rb->bmap_cursor);
> +
> + return xrep_ino_ensure_extent_count(rb->sc, rb->whichfork,
> + ifp->if_nextents);
> +}
> +
> +/*
> + * Reserve new btree blocks, bulk load the bmap records into the ondisk btree,
> + * and load the incore extent tree.
> + */
> +STATIC int
> +xrep_bmap_btree_load(
> + struct xrep_bmap *rb,
> + struct xfs_btree_cur *bmap_cur,
> + uint64_t nextents)
> +{
> + struct repair_ctx *sc = rb->sc;
> + int error;
> +
> + rb->bmap_bload.get_records = xrep_bmap_get_records;
> + rb->bmap_bload.claim_block = xrep_bmap_claim_block;
> + rb->bmap_bload.iroot_size = xrep_bmap_iroot_size;
> + rb->bmap_bload.max_dirty = XFS_B_TO_FSBT(sc->mp, 256U << 10); /* 256K */
> +
> + /*
> + * Always make the btree as small as possible, since we might need the
> + * space to rebuild the space metadata btrees in later phases.
> + */
> + rb->bmap_bload.leaf_slack = 0;
> + rb->bmap_bload.node_slack = 0;
> +
> + /* Compute how many blocks we'll need. */
> + error = -libxfs_btree_bload_compute_geometry(bmap_cur, &rb->bmap_bload,
> + nextents);
> + if (error)
> + return error;
> +
> + /*
> + * Guess how many blocks we're going to need to rebuild an entire bmap
> + * from the number of extents we found, and pump up our transaction to
> + * have sufficient block reservation.
> + */
> + error = -libxfs_trans_reserve_more(sc->tp, rb->bmap_bload.nr_blocks, 0);
> + if (error)
> + return error;
> +
> + /* Reserve the space we'll need for the new btree. */
> + error = bulkload_alloc_file_blocks(&rb->new_fork_info,
> + rb->bmap_bload.nr_blocks);
> + if (error)
> + return error;
> +
> + /* Add all observed bmap records. */
> + error = init_slab_cursor(rb->bmap_records, xrep_bmap_extent_cmp,
> + &rb->bmap_cursor);
> + if (error)
> + return error;
> + error = -libxfs_btree_bload(bmap_cur, &rb->bmap_bload, rb);
> + free_slab_cursor(&rb->bmap_cursor);
> + if (error)
> + return error;
> +
> + /*
> + * Load the new bmap records into the new incore extent tree to
> + * preserve delalloc reservations for regular files. The directory
> + * code loads the extent tree during xfs_dir_open and assumes
> + * thereafter that it remains loaded, so we must not violate that
> + * assumption.
> + */
> + return xrep_bmap_extents_load(rb, bmap_cur, nextents);
> +}
> +
> +/*
> + * Use the collected bmap information to stage a new bmap fork. If this is
> + * successful we'll return with the new fork information logged to the repair
> + * transaction but not yet committed.
> + */
> +STATIC int
> +xrep_bmap_build_new_fork(
> + struct xrep_bmap *rb)
> +{
> + struct xfs_owner_info oinfo;
> + struct repair_ctx *sc = rb->sc;
> + struct xfs_btree_cur *bmap_cur;
> + struct xbtree_ifakeroot *ifake = &rb->new_fork_info.ifake;
> + uint64_t nextents;
> + int error;
> +
> + /*
> + * Sort the bmap extents by startblock to avoid btree splits when we
> + * rebuild the bmbt btree.
> + */
> + qsort_slab(rb->bmap_records, xrep_bmap_extent_cmp);
> +
> + /*
> + * Prepare to construct the new fork by initializing the new btree
> + * structure and creating a fake ifork in the ifakeroot structure.
> + */
> + libxfs_rmap_ino_bmbt_owner(&oinfo, sc->ip->i_ino, rb->whichfork);
> + bulkload_init_inode(&rb->new_fork_info, sc, rb->whichfork, &oinfo);
> + bmap_cur = libxfs_bmbt_stage_cursor(sc->mp, sc->ip, ifake);
> +
> + /*
> + * Figure out the size and format of the new fork, then fill it with
> + * all the bmap records we've found. Join the inode to the transaction
> + * so that we can roll the transaction while holding the inode locked.
> + */
> + libxfs_trans_ijoin(sc->tp, sc->ip, 0);
> + nextents = slab_count(rb->bmap_records);
> + if (nextents <= XFS_IFORK_MAXEXT(sc->ip, rb->whichfork)) {
> + ifake->if_fork->if_format = XFS_DINODE_FMT_EXTENTS;
> + error = xrep_bmap_extents_load(rb, bmap_cur, nextents);
> + } else {
> + ifake->if_fork->if_format = XFS_DINODE_FMT_BTREE;
> + error = xrep_bmap_btree_load(rb, bmap_cur, nextents);
> + }
> + if (error)
> + goto err_cur;
> +
> + /*
> + * Install the new fork in the inode. After this point the old mapping
> + * data are no longer accessible and the new tree is live. We delete
> + * the cursor immediately after committing the staged root because the
> + * staged fork might be in extents format.
> + */
> + libxfs_bmbt_commit_staged_btree(bmap_cur, sc->tp, rb->whichfork);
> + libxfs_btree_del_cursor(bmap_cur, 0);
> +
> + /* Reset the inode counters now that we've changed the fork. */
> + error = xrep_bmap_reset_counters(rb);
> + if (error)
> + goto err_newbt;
> +
> + /* Dispose of any unused blocks and the accounting infomation. */
> + error = bulkload_commit(&rb->new_fork_info);
> + if (error)
> + return error;
> +
> + return -libxfs_trans_roll_inode(&sc->tp, sc->ip);
> +err_cur:
> + if (bmap_cur)
> + libxfs_btree_del_cursor(bmap_cur, error);
> +err_newbt:
> + bulkload_cancel(&rb->new_fork_info);
> + return error;
> +}
> +
> +/* Check for garbage inputs. Returns ECANCELED if there's nothing to do. */
> +STATIC int
> +xrep_bmap_check_inputs(
> + struct repair_ctx *sc,
> + int whichfork)
> +{
> + struct xfs_ifork *ifp = xfs_ifork_ptr(sc->ip, whichfork);
> +
> + ASSERT(whichfork == XFS_DATA_FORK || whichfork == XFS_ATTR_FORK);
> +
> + if (!xfs_has_rmapbt(sc->mp))
> + return EOPNOTSUPP;
> +
> + /* No fork means nothing to rebuild. */
> + if (!ifp)
> + return ECANCELED;
> +
> + /*
> + * We only know how to repair extent mappings, which is to say that we
> + * only support extents and btree fork format. Repairs to a local
> + * format fork require a higher level repair function, so we do not
> + * have any work to do here.
> + */
> + switch (ifp->if_format) {
> + case XFS_DINODE_FMT_DEV:
> + case XFS_DINODE_FMT_LOCAL:
> + case XFS_DINODE_FMT_UUID:
> + return ECANCELED;
> + case XFS_DINODE_FMT_EXTENTS:
> + case XFS_DINODE_FMT_BTREE:
> + break;
> + default:
> + return EFSCORRUPTED;
> + }
> +
> + if (whichfork == XFS_ATTR_FORK)
> + return 0;
> +
> + /* Only files, symlinks, and directories get to have data forks. */
> + switch (VFS_I(sc->ip)->i_mode & S_IFMT) {
> + case S_IFREG:
> + case S_IFDIR:
> + case S_IFLNK:
> + /* ok */
> + break;
> + default:
> + return EINVAL;
> + }
> +
> + /* Don't know how to rebuild realtime data forks. */
> + if (XFS_IS_REALTIME_INODE(sc->ip))
> + return EOPNOTSUPP;
> +
> + return 0;
> +}
> +
> +/* Repair an inode fork. */
> +STATIC int
> +xrep_bmap(
> + struct repair_ctx *sc,
> + int whichfork)
> +{
> + struct xrep_bmap *rb;
> + int error = 0;
> +
> + error = xrep_bmap_check_inputs(sc, whichfork);
> + if (error == ECANCELED)
> + return 0;
> + if (error)
> + return error;
> +
> + rb = kmem_zalloc(sizeof(struct xrep_bmap), KM_NOFS | KM_MAYFAIL);
> + if (!rb)
> + return ENOMEM;
> + rb->sc = sc;
> + rb->whichfork = whichfork;
> +
> + /* Set up some storage */
> + error = init_slab(&rb->bmap_records, sizeof(struct xfs_bmbt_rec));
> + if (error)
> + goto out_rb;
> +
> + /* Collect all reverse mappings for this fork's extents. */
> + error = xrep_bmap_find_mappings(rb);
> + if (error)
> + goto out_bitmap;
> +
> + /* Rebuild the bmap information. */
> + error = xrep_bmap_build_new_fork(rb);
> +
> + /*
> + * We don't need to free the old bmbt blocks because we're rebuilding
> + * all the space metadata later.
> + */
> +
> +out_bitmap:
> + free_slab(&rb->bmap_records);
> +out_rb:
> + kmem_free(rb);
> + return error;
> +}
> +
> +/* Rebuild some inode's bmap. */
> +int
> +rebuild_bmap(
> + struct xfs_mount *mp,
> + xfs_ino_t ino,
> + int whichfork,
> + unsigned long nr_extents,
> + struct xfs_buf **ino_bpp,
> + struct xfs_dinode **dinop,
> + int *dirty)
> +{
> + struct repair_ctx sc = {
> + .mp = mp,
> + };
> + const struct xfs_buf_ops *bp_ops;
> + unsigned long boffset;
> + unsigned long long resblks;
> + xfs_daddr_t bp_bn;
> + int bp_length;
> + int error, err2;
> +
> + bp_bn = xfs_buf_daddr(*ino_bpp);
> + bp_length = (*ino_bpp)->b_length;
> + bp_ops = (*ino_bpp)->b_ops;
> + boffset = (char *)(*dinop) - (char *)(*ino_bpp)->b_addr;
> +
> + /*
> + * Bail out if the inode didn't think it had extents. Otherwise, zap
> + * it back to a zero-extents fork so that we can rebuild it.
> + */
> + switch (whichfork) {
> + case XFS_DATA_FORK:
> + if ((*dinop)->di_nextents == 0)
> + return 0;
> + (*dinop)->di_format = XFS_DINODE_FMT_EXTENTS;
> + (*dinop)->di_nextents = 0;
> + libxfs_dinode_calc_crc(mp, *dinop);
> + *dirty = 1;
> + break;
> + case XFS_ATTR_FORK:
> + if ((*dinop)->di_anextents == 0)
> + return 0;
> + (*dinop)->di_aformat = XFS_DINODE_FMT_EXTENTS;
> + (*dinop)->di_anextents = 0;
> + libxfs_dinode_calc_crc(mp, *dinop);
> + *dirty = 1;
> + break;
> + default:
> + return EINVAL;
> + }
> +
> + resblks = libxfs_bmbt_calc_size(mp, nr_extents);
> + error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, resblks, 0,
> + 0, &sc.tp);
> + if (error)
> + return error;
> +
> + /*
> + * Repair magic: the caller passed us the inode cluster buffer for the
> + * inode. The _iget call grabs the buffer to load the incore inode, so
> + * the buffer must be attached to the transaction to avoid recursing
> + * the buffer lock.
> + *
> + * Unfortunately, the _iget call drops the buffer once the inode is
> + * loaded, so if we've made any changes we have to log the buffer, hold
> + * it, and roll the transaction. This persists the caller's changes
> + * and maintains our ownership of the cluster buffer.
> + */
> + libxfs_trans_bjoin(sc.tp, *ino_bpp);
> + if (*dirty) {
> + unsigned int end = BBTOB((*ino_bpp)->b_length) - 1;
> +
> + libxfs_trans_log_buf(sc.tp, *ino_bpp, 0, end);
> + *dirty = 0;
> +
> + libxfs_trans_bhold(sc.tp, *ino_bpp);
> + error = -libxfs_trans_roll(&sc.tp);
> + libxfs_trans_bjoin(sc.tp, *ino_bpp);
> + if (error)
> + goto out_cancel;
> + }
> +
> + /* Grab the inode and fix the bmbt. */
> + error = -libxfs_iget(mp, sc.tp, ino, 0, &sc.ip);
> + if (error)
> + goto out_cancel;
> + error = xrep_bmap(&sc, whichfork);
> + if (error)
> + libxfs_trans_cancel(sc.tp);
> + else
> + error = -libxfs_trans_commit(sc.tp);
> +
> + /*
> + * Rebuilding the inode fork rolled the transaction, so we need to
> + * re-grab the inode cluster buffer and dinode pointer for the caller.
> + */
> + err2 = -libxfs_imap_to_bp(mp, NULL, &sc.ip->i_imap, ino_bpp);
> + if (err2)
> + do_error(
> + _("Unable to re-grab inode cluster buffer after failed repair of inode %llu, error %d.\n"),
> + (unsigned long long)ino, err2);
> + *dinop = xfs_buf_offset(*ino_bpp, sc.ip->i_imap.im_boffset);
> + libxfs_irele(sc.ip);
> +
> + return error;
> +
> +out_cancel:
> + libxfs_trans_cancel(sc.tp);
> +
> + /*
> + * Try to regrab the old buffer so we have something to return to the
> + * caller.
> + */
> + err2 = -libxfs_trans_read_buf(mp, NULL, mp->m_ddev_targp, bp_bn,
> + bp_length, 0, ino_bpp, bp_ops);
> + if (err2)
> + do_error(
> + _("Unable to re-grab inode cluster buffer after failed repair of inode %llu, error %d.\n"),
> + (unsigned long long)ino, err2);
> + *dinop = xfs_buf_offset(*ino_bpp, boffset);
> + return error;
> +}
> diff --git a/repair/bmap_repair.h b/repair/bmap_repair.h
> new file mode 100644
> index 000000000000..6d55359490a0
> --- /dev/null
> +++ b/repair/bmap_repair.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (c) 2019-2024 Oracle. All Rights Reserved.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + */
> +#ifndef REBUILD_H_
> +#define REBUILD_H_
> +
> +int rebuild_bmap(struct xfs_mount *mp, xfs_ino_t ino, int whichfork,
> + unsigned long nr_extents, struct xfs_buf **ino_bpp,
> + struct xfs_dinode **dinop, int *dirty);
> +
> +#endif /* REBUILD_H_ */
> diff --git a/repair/bulkload.c b/repair/bulkload.c
> index 18158c397f56..a97839f549dd 100644
> --- a/repair/bulkload.c
> +++ b/repair/bulkload.c
> @@ -14,14 +14,29 @@ void
> bulkload_init_ag(
> struct bulkload *bkl,
> struct repair_ctx *sc,
> - const struct xfs_owner_info *oinfo)
> + const struct xfs_owner_info *oinfo,
> + xfs_fsblock_t alloc_hint)
> {
> memset(bkl, 0, sizeof(struct bulkload));
> bkl->sc = sc;
> bkl->oinfo = *oinfo; /* structure copy */
> + bkl->alloc_hint = alloc_hint;
> INIT_LIST_HEAD(&bkl->resv_list);
> }
>
> +/* Initialize accounting resources for staging a new inode fork btree. */
> +void
> +bulkload_init_inode(
> + struct bulkload *bkl,
> + struct repair_ctx *sc,
> + int whichfork,
> + const struct xfs_owner_info *oinfo)
> +{
> + bulkload_init_ag(bkl, sc, oinfo, XFS_INO_TO_FSB(sc->mp, sc->ip->i_ino));
> + bkl->ifake.if_fork = kmem_cache_zalloc(xfs_ifork_cache, 0);
> + bkl->ifake.if_fork_size = xfs_inode_fork_size(sc->ip, whichfork);
> +}
> +
> /* Designate specific blocks to be used to build our new btree. */
> static int
> bulkload_add_blocks(
> @@ -71,17 +86,199 @@ bulkload_add_extent(
> return bulkload_add_blocks(bkl, pag, &args);
> }
>
> +/* Don't let our allocation hint take us beyond EOFS */
> +static inline void
> +bulkload_validate_file_alloc_hint(
> + struct bulkload *bkl)
> +{
> + struct repair_ctx *sc = bkl->sc;
> +
> + if (libxfs_verify_fsbno(sc->mp, bkl->alloc_hint))
> + return;
> +
> + bkl->alloc_hint = XFS_AGB_TO_FSB(sc->mp, 0, XFS_AGFL_BLOCK(sc->mp) + 1);
> +}
> +
> +/* Allocate disk space for our new file-based btree. */
> +int
> +bulkload_alloc_file_blocks(
> + struct bulkload *bkl,
> + uint64_t nr_blocks)
> +{
> + struct repair_ctx *sc = bkl->sc;
> + struct xfs_mount *mp = sc->mp;
> + int error = 0;
> +
> + while (nr_blocks > 0) {
> + struct xfs_alloc_arg args = {
> + .tp = sc->tp,
> + .mp = mp,
> + .oinfo = bkl->oinfo,
> + .minlen = 1,
> + .maxlen = nr_blocks,
> + .prod = 1,
> + .resv = XFS_AG_RESV_NONE,
> + };
> + struct xfs_perag *pag;
> + xfs_agnumber_t agno;
> +
> + bulkload_validate_file_alloc_hint(bkl);
> +
> + error = -libxfs_alloc_vextent_start_ag(&args, bkl->alloc_hint);
> + if (error)
> + return error;
> + if (args.fsbno == NULLFSBLOCK)
> + return ENOSPC;
> +
> + agno = XFS_FSB_TO_AGNO(mp, args.fsbno);
> +
> + pag = libxfs_perag_get(mp, agno);
> + if (!pag) {
> + ASSERT(0);
> + return -EFSCORRUPTED;
> + }
> +
> + error = bulkload_add_blocks(bkl, pag, &args);
> + libxfs_perag_put(pag);
> + if (error)
> + return error;
> +
> + nr_blocks -= args.len;
> + bkl->alloc_hint = args.fsbno + args.len;
> +
> + error = -libxfs_defer_finish(&sc->tp);
> + if (error)
> + return error;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Free the unused part of a space extent that was reserved for a new ondisk
> + * structure. Returns the number of EFIs logged or a negative errno.
> + */
> +static inline int
> +bulkload_free_extent(
> + struct bulkload *bkl,
> + struct bulkload_resv *resv,
> + bool btree_committed)
> +{
> + struct repair_ctx *sc = bkl->sc;
> + xfs_agblock_t free_agbno = resv->agbno;
> + xfs_extlen_t free_aglen = resv->len;
> + xfs_fsblock_t fsbno;
> + int error;
> +
> + if (!btree_committed || resv->used == 0) {
> + /*
> + * If we're not committing a new btree or we didn't use the
> + * space reservation, free the entire space extent.
> + */
> + goto free;
> + }
> +
> + /*
> + * We used space and committed the btree. Remove the written blocks
> + * from the reservation and possibly log a new EFI to free any unused
> + * reservation space.
> + */
> + free_agbno += resv->used;
> + free_aglen -= resv->used;
> +
> + if (free_aglen == 0)
> + return 0;
> +
> +free:
> + /*
> + * Use EFIs to free the reservations. We don't need to use EFIs here
> + * like the kernel, but we'll do it to keep the code matched.
> + */
> + fsbno = XFS_AGB_TO_FSB(sc->mp, resv->pag->pag_agno, free_agbno);
> + error = -libxfs_free_extent_later(sc->tp, fsbno, free_aglen,
> + &bkl->oinfo, XFS_AG_RESV_NONE, true);
> + if (error)
> + return error;
> +
> + return 1;
> +}
> +
> /* Free all the accounting info and disk space we reserved for a new btree. */
> -void
> -bulkload_commit(
> - struct bulkload *bkl)
> +static int
> +bulkload_free(
> + struct bulkload *bkl,
> + bool btree_committed)
> {
> + struct repair_ctx *sc = bkl->sc;
> struct bulkload_resv *resv, *n;
> + unsigned int freed = 0;
> + int error = 0;
>
> list_for_each_entry_safe(resv, n, &bkl->resv_list, list) {
> + int ret;
> +
> + ret = bulkload_free_extent(bkl, resv, btree_committed);
> list_del(&resv->list);
> + libxfs_perag_put(resv->pag);
> kfree(resv);
> +
> + if (ret < 0) {
> + error = ret;
> + goto junkit;
> + }
> +
> + freed += ret;
> + if (freed >= XREP_MAX_ITRUNCATE_EFIS) {
> + error = -libxfs_defer_finish(&sc->tp);
> + if (error)
> + goto junkit;
> + freed = 0;
> + }
> }
> +
> + if (freed)
> + error = -libxfs_defer_finish(&sc->tp);
> +junkit:
> + /*
> + * If we still have reservations attached to @newbt, cleanup must have
> + * failed and the filesystem is about to go down. Clean up the incore
> + * reservations.
> + */
> + list_for_each_entry_safe(resv, n, &bkl->resv_list, list) {
> + list_del(&resv->list);
> + libxfs_perag_put(resv->pag);
> + kfree(resv);
> + }
> +
> + if (sc->ip) {
> + kmem_cache_free(xfs_ifork_cache, bkl->ifake.if_fork);
> + bkl->ifake.if_fork = NULL;
> + }
> +
> + return error;
> +}
> +
> +/*
> + * Free all the accounting info and unused disk space allocations after
> + * committing a new btree.
> + */
> +int
> +bulkload_commit(
> + struct bulkload *bkl)
> +{
> + return bulkload_free(bkl, true);
> +}
> +
> +/*
> + * Free all the accounting info and all of the disk space we reserved for a new
> + * btree that we're not going to commit. We want to try to roll things back
> + * cleanly for things like ENOSPC midway through allocation.
> + */
> +void
> +bulkload_cancel(
> + struct bulkload *bkl)
> +{
> + bulkload_free(bkl, false);
> }
>
> /* Feed one of the reserved btree blocks to the bulk loader. */
> diff --git a/repair/bulkload.h b/repair/bulkload.h
> index f4790e3b3de6..a88aafaa678a 100644
> --- a/repair/bulkload.h
> +++ b/repair/bulkload.h
> @@ -8,9 +8,17 @@
>
> extern int bload_leaf_slack;
> extern int bload_node_slack;
> +/*
> + * This is the maximum number of deferred extent freeing item extents (EFIs)
> + * that we'll attach to a transaction without rolling the transaction to avoid
> + * overrunning a tr_itruncate reservation.
> + */
> +#define XREP_MAX_ITRUNCATE_EFIS (128)
>
> struct repair_ctx {
> struct xfs_mount *mp;
> + struct xfs_inode *ip;
> + struct xfs_trans *tp;
> };
>
> struct bulkload_resv {
> @@ -36,7 +44,10 @@ struct bulkload {
> struct list_head resv_list;
>
> /* Fake root for new btree. */
> - struct xbtree_afakeroot afake;
> + union {
> + struct xbtree_afakeroot afake;
> + struct xbtree_ifakeroot ifake;
> + };
>
> /* rmap owner of these blocks */
> struct xfs_owner_info oinfo;
> @@ -44,6 +55,9 @@ struct bulkload {
> /* The last reservation we allocated from. */
> struct bulkload_resv *last_resv;
>
> + /* Hint as to where we should allocate blocks. */
> + xfs_fsblock_t alloc_hint;
> +
> /* Number of blocks reserved via resv_list. */
> unsigned int nr_reserved;
> };
> @@ -52,12 +66,16 @@ struct bulkload {
> list_for_each_entry_safe((resv), (n), &(bkl)->resv_list, list)
>
> void bulkload_init_ag(struct bulkload *bkl, struct repair_ctx *sc,
> - const struct xfs_owner_info *oinfo);
> + const struct xfs_owner_info *oinfo, xfs_fsblock_t alloc_hint);
> +void bulkload_init_inode(struct bulkload *bkl, struct repair_ctx *sc,
> + int whichfork, const struct xfs_owner_info *oinfo);
> int bulkload_claim_block(struct xfs_btree_cur *cur, struct bulkload *bkl,
> union xfs_btree_ptr *ptr);
> int bulkload_add_extent(struct bulkload *bkl, struct xfs_perag *pag,
> xfs_agblock_t agbno, xfs_extlen_t len);
> -void bulkload_commit(struct bulkload *bkl);
> +int bulkload_alloc_file_blocks(struct bulkload *bkl, uint64_t nr_blocks);
> +void bulkload_cancel(struct bulkload *bkl);
> +int bulkload_commit(struct bulkload *bkl);
> void bulkload_estimate_ag_slack(struct repair_ctx *sc,
> struct xfs_btree_bload *bload, unsigned int free);
>
> diff --git a/repair/dinode.c b/repair/dinode.c
> index a18af3ff7772..b8f5bf4e550e 100644
> --- a/repair/dinode.c
> +++ b/repair/dinode.c
> @@ -20,6 +20,7 @@
> #include "threads.h"
> #include "slab.h"
> #include "rmap.h"
> +#include "bmap_repair.h"
>
> /*
> * gettext lookups for translations of strings use mutexes internally to
> @@ -1909,7 +1910,9 @@ process_inode_data_fork(
> xfs_ino_t lino = XFS_AGINO_TO_INO(mp, agno, ino);
> int err = 0;
> xfs_extnum_t nex, max_nex;
> + int try_rebuild = -1; /* don't know yet */
>
> +retry:
> /*
> * extent count on disk is only valid for positive values. The kernel
> * uses negative values in memory. hence if we see negative numbers
> @@ -1938,11 +1941,15 @@ process_inode_data_fork(
> *totblocks = 0;
> break;
> case XFS_DINODE_FMT_EXTENTS:
> + if (!rmapbt_suspect && try_rebuild == -1)
> + try_rebuild = 1;
> err = process_exinode(mp, agno, ino, dino, type, dirty,
> totblocks, nextents, dblkmap, XFS_DATA_FORK,
> check_dups);
> break;
> case XFS_DINODE_FMT_BTREE:
> + if (!rmapbt_suspect && try_rebuild == -1)
> + try_rebuild = 1;
> err = process_btinode(mp, agno, ino, dino, type, dirty,
> totblocks, nextents, dblkmap, XFS_DATA_FORK,
> check_dups);
> @@ -1958,8 +1965,28 @@ process_inode_data_fork(
> if (err) {
> do_warn(_("bad data fork in inode %" PRIu64 "\n"), lino);
> if (!no_modify) {
> + if (try_rebuild == 1) {
> + do_warn(
> +_("rebuilding inode %"PRIu64" data fork\n"),
> + lino);
> + try_rebuild = 0;
> + err = rebuild_bmap(mp, lino, XFS_DATA_FORK,
> + be32_to_cpu(dino->di_nextents),
> + ino_bpp, dinop, dirty);
> + dino = *dinop;
> + if (!err)
> + goto retry;
> + do_warn(
> +_("inode %"PRIu64" data fork rebuild failed, error %d, clearing\n"),
> + lino, err);
> + }
> clear_dinode(mp, dino, lino);
> *dirty += 1;
> + ASSERT(*dirty > 0);
> + } else if (try_rebuild == 1) {
> + do_warn(
> +_("would have tried to rebuild inode %"PRIu64" data fork\n"),
> + lino);
> }
> return 1;
> }
> @@ -2025,7 +2052,9 @@ process_inode_attr_fork(
> struct blkmap *ablkmap = NULL;
> int repair = 0;
> int err;
> + int try_rebuild = -1; /* don't know yet */
>
> +retry:
> if (!dino->di_forkoff) {
> *anextents = 0;
> if (dino->di_aformat != XFS_DINODE_FMT_EXTENTS) {
> @@ -2052,6 +2081,8 @@ process_inode_attr_fork(
> err = process_lclinode(mp, agno, ino, dino, XFS_ATTR_FORK);
> break;
> case XFS_DINODE_FMT_EXTENTS:
> + if (!rmapbt_suspect && try_rebuild == -1)
> + try_rebuild = 1;
> ablkmap = blkmap_alloc(*anextents, XFS_ATTR_FORK);
> *anextents = 0;
> err = process_exinode(mp, agno, ino, dino, type, dirty,
> @@ -2059,6 +2090,8 @@ process_inode_attr_fork(
> XFS_ATTR_FORK, check_dups);
> break;
> case XFS_DINODE_FMT_BTREE:
> + if (!rmapbt_suspect && try_rebuild == -1)
> + try_rebuild = 1;
> ablkmap = blkmap_alloc(*anextents, XFS_ATTR_FORK);
> *anextents = 0;
> err = process_btinode(mp, agno, ino, dino, type, dirty,
> @@ -2084,10 +2117,29 @@ process_inode_attr_fork(
> do_warn(_("bad attribute fork in inode %" PRIu64 "\n"), lino);
>
> if (!no_modify) {
> + if (try_rebuild == 1) {
> + do_warn(
> +_("rebuilding inode %"PRIu64" attr fork\n"),
> + lino);
> + try_rebuild = 0;
> + err = rebuild_bmap(mp, lino, XFS_ATTR_FORK,
> + be16_to_cpu(dino->di_anextents),
> + ino_bpp, dinop, dirty);
> + dino = *dinop;
> + if (!err)
> + goto retry;
> + do_warn(
> +_("inode %"PRIu64" attr fork rebuild failed, error %d"),
> + lino, err);
> + }
> do_warn(_(", clearing attr fork\n"));
> *dirty += clear_dinode_attr(mp, dino, lino);
> ASSERT(*dirty > 0);
> - } else {
> + } else if (try_rebuild) {
> + do_warn(
> +_("would have tried to rebuild inode %"PRIu64" attr fork or cleared it\n"),
> + lino);
> + } else {
> do_warn(_(", would clear attr fork\n"));
> }
>
> diff --git a/repair/rmap.c b/repair/rmap.c
> index 6bb77e082492..a2291c7b3b01 100644
> --- a/repair/rmap.c
> +++ b/repair/rmap.c
> @@ -33,7 +33,7 @@ struct xfs_ag_rmap {
> };
>
> static struct xfs_ag_rmap *ag_rmaps;
> -static bool rmapbt_suspect;
> +bool rmapbt_suspect;
> static bool refcbt_suspect;
>
> static inline int rmap_compare(const void *a, const void *b)
> diff --git a/repair/rmap.h b/repair/rmap.h
> index 6004e9f68b63..1dad2f5890a4 100644
> --- a/repair/rmap.h
> +++ b/repair/rmap.h
> @@ -7,6 +7,7 @@
> #define RMAP_H_
>
> extern bool collect_rmaps;
> +extern bool rmapbt_suspect;
>
> extern bool rmap_needs_work(struct xfs_mount *);
>
>
>