Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d852657c authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

xfs: cross-reference reverse-mapping btree



When scrubbing various btrees, we should cross-reference the records
with the reverse mapping btree and ensure that traversing the btree
finds the same number of blocks that the rmapbt thinks are owned by
that btree.

Signed-off-by: default avatarDarrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: default avatarDave Chinner <dchinner@redhat.com>
parent 2e6f2756
Loading
Loading
Loading
Loading
+66 −2
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@
#include "xfs_inode.h"
#include "xfs_alloc.h"
#include "xfs_ialloc.h"
#include "xfs_rmap.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
@@ -107,6 +108,7 @@ xfs_scrub_superblock_xref(
	struct xfs_scrub_context	*sc,
	struct xfs_buf			*bp)
{
	struct xfs_owner_info		oinfo;
	struct xfs_mount		*mp = sc->mp;
	xfs_agnumber_t			agno = sc->sm->sm_agno;
	xfs_agblock_t			agbno;
@@ -123,6 +125,8 @@ xfs_scrub_superblock_xref(

	xfs_scrub_xref_is_used_space(sc, agbno, 1);
	xfs_scrub_xref_is_not_inode_chunk(sc, agbno, 1);
	xfs_rmap_ag_owner(&oinfo, XFS_RMAP_OWN_FS);
	xfs_scrub_xref_is_owned_by(sc, agbno, 1, &oinfo);

	/* scrub teardown will take care of sc->sa for us */
}
@@ -487,11 +491,58 @@ xfs_scrub_agf_xref_cntbt(
		xfs_scrub_block_xref_set_corrupt(sc, sc->sa.agf_bp);
}

/* Check the btree block counts in the AGF against the btrees. */
STATIC void
xfs_scrub_agf_xref_btreeblks(
	struct xfs_scrub_context	*sc)
{
	struct xfs_agf			*agf = XFS_BUF_TO_AGF(sc->sa.agf_bp);
	struct xfs_mount		*mp = sc->mp;
	xfs_agblock_t			blocks;
	xfs_agblock_t			btreeblks;
	int				error;

	/* Check agf_rmap_blocks; set up for agf_btreeblks check */
	if (sc->sa.rmap_cur) {
		error = xfs_btree_count_blocks(sc->sa.rmap_cur, &blocks);
		if (!xfs_scrub_should_check_xref(sc, &error, &sc->sa.rmap_cur))
			return;
		btreeblks = blocks - 1;
		if (blocks != be32_to_cpu(agf->agf_rmap_blocks))
			xfs_scrub_block_xref_set_corrupt(sc, sc->sa.agf_bp);
	} else {
		btreeblks = 0;
	}

	/*
	 * No rmap cursor; we can't xref if we have the rmapbt feature.
	 * We also can't do it if we're missing the free space btree cursors.
	 */
	if ((xfs_sb_version_hasrmapbt(&mp->m_sb) && !sc->sa.rmap_cur) ||
	    !sc->sa.bno_cur || !sc->sa.cnt_cur)
		return;

	/* Check agf_btreeblks */
	error = xfs_btree_count_blocks(sc->sa.bno_cur, &blocks);
	if (!xfs_scrub_should_check_xref(sc, &error, &sc->sa.bno_cur))
		return;
	btreeblks += blocks - 1;

	error = xfs_btree_count_blocks(sc->sa.cnt_cur, &blocks);
	if (!xfs_scrub_should_check_xref(sc, &error, &sc->sa.cnt_cur))
		return;
	btreeblks += blocks - 1;

	if (btreeblks != be32_to_cpu(agf->agf_btreeblks))
		xfs_scrub_block_xref_set_corrupt(sc, sc->sa.agf_bp);
}

/* Cross-reference with the other btrees. */
STATIC void
xfs_scrub_agf_xref(
	struct xfs_scrub_context	*sc)
{
	struct xfs_owner_info		oinfo;
	struct xfs_mount		*mp = sc->mp;
	xfs_agblock_t			agbno;
	int				error;
@@ -509,6 +560,9 @@ xfs_scrub_agf_xref(
	xfs_scrub_agf_xref_freeblks(sc);
	xfs_scrub_agf_xref_cntbt(sc);
	xfs_scrub_xref_is_not_inode_chunk(sc, agbno, 1);
	xfs_rmap_ag_owner(&oinfo, XFS_RMAP_OWN_FS);
	xfs_scrub_xref_is_owned_by(sc, agbno, 1, &oinfo);
	xfs_scrub_agf_xref_btreeblks(sc);

	/* scrub teardown will take care of sc->sa for us */
}
@@ -599,6 +653,7 @@ xfs_scrub_agf(
/* AGFL */

struct xfs_scrub_agfl_info {
	struct xfs_owner_info		oinfo;
	unsigned int			sz_entries;
	unsigned int			nr_entries;
	xfs_agblock_t			*entries;
@@ -608,13 +663,15 @@ struct xfs_scrub_agfl_info {
STATIC void
xfs_scrub_agfl_block_xref(
	struct xfs_scrub_context	*sc,
	xfs_agblock_t			agbno)
	xfs_agblock_t			agbno,
	struct xfs_owner_info		*oinfo)
{
	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
		return;

	xfs_scrub_xref_is_used_space(sc, agbno, 1);
	xfs_scrub_xref_is_not_inode_chunk(sc, agbno, 1);
	xfs_scrub_xref_is_owned_by(sc, agbno, 1, oinfo);
}

/* Scrub an AGFL block. */
@@ -634,7 +691,7 @@ xfs_scrub_agfl_block(
	else
		xfs_scrub_block_set_corrupt(sc, sc->sa.agfl_bp);

	xfs_scrub_agfl_block_xref(sc, agbno);
	xfs_scrub_agfl_block_xref(sc, agbno, priv);

	return 0;
}
@@ -655,6 +712,7 @@ STATIC void
xfs_scrub_agfl_xref(
	struct xfs_scrub_context	*sc)
{
	struct xfs_owner_info		oinfo;
	struct xfs_mount		*mp = sc->mp;
	xfs_agblock_t			agbno;
	int				error;
@@ -670,6 +728,8 @@ xfs_scrub_agfl_xref(

	xfs_scrub_xref_is_used_space(sc, agbno, 1);
	xfs_scrub_xref_is_not_inode_chunk(sc, agbno, 1);
	xfs_rmap_ag_owner(&oinfo, XFS_RMAP_OWN_FS);
	xfs_scrub_xref_is_owned_by(sc, agbno, 1, &oinfo);

	/*
	 * Scrub teardown will take care of sc->sa for us.  Leave sc->sa
@@ -717,6 +777,7 @@ xfs_scrub_agfl(
	}

	/* Check the blocks in the AGFL. */
	xfs_rmap_ag_owner(&sai.oinfo, XFS_RMAP_OWN_AG);
	error = xfs_scrub_walk_agfl(sc, xfs_scrub_agfl_block, &sai);
	if (error)
		goto out_free;
@@ -770,6 +831,7 @@ STATIC void
xfs_scrub_agi_xref(
	struct xfs_scrub_context	*sc)
{
	struct xfs_owner_info		oinfo;
	struct xfs_mount		*mp = sc->mp;
	xfs_agblock_t			agbno;
	int				error;
@@ -786,6 +848,8 @@ xfs_scrub_agi_xref(
	xfs_scrub_xref_is_used_space(sc, agbno, 1);
	xfs_scrub_xref_is_not_inode_chunk(sc, agbno, 1);
	xfs_scrub_agi_xref_icounts(sc);
	xfs_rmap_ag_owner(&oinfo, XFS_RMAP_OWN_FS);
	xfs_scrub_xref_is_owned_by(sc, agbno, 1, &oinfo);

	/* scrub teardown will take care of sc->sa for us */
}
+1 −0
Original line number Diff line number Diff line
@@ -105,6 +105,7 @@ xfs_scrub_allocbt_xref(

	xfs_scrub_allocbt_xref_other(sc, agbno, len);
	xfs_scrub_xref_is_not_inode_chunk(sc, agbno, len);
	xfs_scrub_xref_has_no_owner(sc, agbno, len);
}

/* Scrub a bnobt/cntbt record. */
+134 −0
Original line number Diff line number Diff line
@@ -99,6 +99,139 @@ struct xfs_scrub_bmap_info {
	int				whichfork;
};

/* Look for a corresponding rmap for this irec. */
static inline bool
xfs_scrub_bmap_get_rmap(
	struct xfs_scrub_bmap_info	*info,
	struct xfs_bmbt_irec		*irec,
	xfs_agblock_t			agbno,
	uint64_t			owner,
	struct xfs_rmap_irec		*rmap)
{
	xfs_fileoff_t			offset;
	unsigned int			rflags = 0;
	int				has_rmap;
	int				error;

	if (info->whichfork == XFS_ATTR_FORK)
		rflags |= XFS_RMAP_ATTR_FORK;

	/*
	 * CoW staging extents are owned (on disk) by the refcountbt, so
	 * their rmaps do not have offsets.
	 */
	if (info->whichfork == XFS_COW_FORK)
		offset = 0;
	else
		offset = irec->br_startoff;

	/*
	 * If the caller thinks this could be a shared bmbt extent (IOWs,
	 * any data fork extent of a reflink inode) then we have to use the
	 * range rmap lookup to make sure we get the correct owner/offset.
	 */
	if (info->is_shared) {
		error = xfs_rmap_lookup_le_range(info->sc->sa.rmap_cur, agbno,
				owner, offset, rflags, rmap, &has_rmap);
		if (!xfs_scrub_should_check_xref(info->sc, &error,
				&info->sc->sa.rmap_cur))
			return false;
		goto out;
	}

	/*
	 * Otherwise, use the (faster) regular lookup.
	 */
	error = xfs_rmap_lookup_le(info->sc->sa.rmap_cur, agbno, 0, owner,
			offset, rflags, &has_rmap);
	if (!xfs_scrub_should_check_xref(info->sc, &error,
			&info->sc->sa.rmap_cur))
		return false;
	if (!has_rmap)
		goto out;

	error = xfs_rmap_get_rec(info->sc->sa.rmap_cur, rmap, &has_rmap);
	if (!xfs_scrub_should_check_xref(info->sc, &error,
			&info->sc->sa.rmap_cur))
		return false;

out:
	if (!has_rmap)
		xfs_scrub_fblock_xref_set_corrupt(info->sc, info->whichfork,
			irec->br_startoff);
	return has_rmap;
}

/* Make sure that we have rmapbt records for this extent. */
STATIC void
xfs_scrub_bmap_xref_rmap(
	struct xfs_scrub_bmap_info	*info,
	struct xfs_bmbt_irec		*irec,
	xfs_agblock_t			agbno)
{
	struct xfs_rmap_irec		rmap;
	unsigned long long		rmap_end;
	uint64_t			owner;

	if (!info->sc->sa.rmap_cur)
		return;

	if (info->whichfork == XFS_COW_FORK)
		owner = XFS_RMAP_OWN_COW;
	else
		owner = info->sc->ip->i_ino;

	/* Find the rmap record for this irec. */
	if (!xfs_scrub_bmap_get_rmap(info, irec, agbno, owner, &rmap))
		return;

	/* Check the rmap. */
	rmap_end = (unsigned long long)rmap.rm_startblock + rmap.rm_blockcount;
	if (rmap.rm_startblock > agbno ||
	    agbno + irec->br_blockcount > rmap_end)
		xfs_scrub_fblock_xref_set_corrupt(info->sc, info->whichfork,
				irec->br_startoff);

	/*
	 * Check the logical offsets if applicable.  CoW staging extents
	 * don't track logical offsets since the mappings only exist in
	 * memory.
	 */
	if (info->whichfork != XFS_COW_FORK) {
		rmap_end = (unsigned long long)rmap.rm_offset +
				rmap.rm_blockcount;
		if (rmap.rm_offset > irec->br_startoff ||
		    irec->br_startoff + irec->br_blockcount > rmap_end)
			xfs_scrub_fblock_xref_set_corrupt(info->sc,
					info->whichfork, irec->br_startoff);
	}

	if (rmap.rm_owner != owner)
		xfs_scrub_fblock_xref_set_corrupt(info->sc, info->whichfork,
				irec->br_startoff);

	/*
	 * Check for discrepancies between the unwritten flag in the irec and
	 * the rmap.  Note that the (in-memory) CoW fork distinguishes between
	 * unwritten and written extents, but we don't track that in the rmap
	 * records because the blocks are owned (on-disk) by the refcountbt,
	 * which doesn't track unwritten state.
	 */
	if (owner != XFS_RMAP_OWN_COW &&
	    irec->br_state == XFS_EXT_UNWRITTEN &&
	    !(rmap.rm_flags & XFS_RMAP_UNWRITTEN))
		xfs_scrub_fblock_xref_set_corrupt(info->sc, info->whichfork,
				irec->br_startoff);

	if (info->whichfork == XFS_ATTR_FORK &&
	    !(rmap.rm_flags & XFS_RMAP_ATTR_FORK))
		xfs_scrub_fblock_xref_set_corrupt(info->sc, info->whichfork,
				irec->br_startoff);
	if (rmap.rm_flags & XFS_RMAP_BMBT_BLOCK)
		xfs_scrub_fblock_xref_set_corrupt(info->sc, info->whichfork,
				irec->br_startoff);
}

/* Cross-reference a single rtdev extent record. */
STATIC void
xfs_scrub_bmap_rt_extent_xref(
@@ -139,6 +272,7 @@ xfs_scrub_bmap_extent_xref(

	xfs_scrub_xref_is_used_space(info->sc, agbno, len);
	xfs_scrub_xref_is_not_inode_chunk(info->sc, agbno, len);
	xfs_scrub_bmap_xref_rmap(info, irec, agbno);

	xfs_scrub_ag_free(info->sc, &info->sc->sa);
}
+4 −0
Original line number Diff line number Diff line
@@ -407,6 +407,10 @@ xfs_scrub_btree_check_block_owner(
	if (!bs->sc->sa.bno_cur && btnum == XFS_BTNUM_BNO)
		bs->cur = NULL;

	xfs_scrub_xref_is_owned_by(bs->sc, agbno, 1, bs->oinfo);
	if (!bs->sc->sa.rmap_cur && btnum == XFS_BTNUM_RMAP)
		bs->cur = NULL;

	if (init_sa)
		xfs_scrub_ag_free(bs->sc, &bs->sc->sa);

+53 −0
Original line number Diff line number Diff line
@@ -324,6 +324,59 @@ xfs_scrub_set_incomplete(
	trace_xfs_scrub_incomplete(sc, __return_address);
}

/*
 * rmap scrubbing -- compute the number of blocks with a given owner,
 * at least according to the reverse mapping data.
 */

struct xfs_scrub_rmap_ownedby_info {
	struct xfs_owner_info	*oinfo;
	xfs_filblks_t		*blocks;
};

STATIC int
xfs_scrub_count_rmap_ownedby_irec(
	struct xfs_btree_cur			*cur,
	struct xfs_rmap_irec			*rec,
	void					*priv)
{
	struct xfs_scrub_rmap_ownedby_info	*sroi = priv;
	bool					irec_attr;
	bool					oinfo_attr;

	irec_attr = rec->rm_flags & XFS_RMAP_ATTR_FORK;
	oinfo_attr = sroi->oinfo->oi_flags & XFS_OWNER_INFO_ATTR_FORK;

	if (rec->rm_owner != sroi->oinfo->oi_owner)
		return 0;

	if (XFS_RMAP_NON_INODE_OWNER(rec->rm_owner) || irec_attr == oinfo_attr)
		(*sroi->blocks) += rec->rm_blockcount;

	return 0;
}

/*
 * Calculate the number of blocks the rmap thinks are owned by something.
 * The caller should pass us an rmapbt cursor.
 */
int
xfs_scrub_count_rmap_ownedby_ag(
	struct xfs_scrub_context		*sc,
	struct xfs_btree_cur			*cur,
	struct xfs_owner_info			*oinfo,
	xfs_filblks_t				*blocks)
{
	struct xfs_scrub_rmap_ownedby_info	sroi;

	sroi.oinfo = oinfo;
	*blocks = 0;
	sroi.blocks = blocks;

	return xfs_rmap_query_all(cur, xfs_scrub_count_rmap_ownedby_irec,
			&sroi);
}

/*
 * AG scrubbing
 *
Loading