Skip to content

Commit

Permalink
xfs: cross-reference reverse-mapping btree
Browse files Browse the repository at this point in the history
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: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
  • Loading branch information
djwong committed Jan 18, 2018
1 parent 2e6f275 commit d852657
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 4 deletions.
68 changes: 66 additions & 2 deletions fs/xfs/scrub/agheader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
}
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
}
Expand Down Expand Up @@ -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;
Expand All @@ -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. */
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
}
Expand Down
1 change: 1 addition & 0 deletions fs/xfs/scrub/alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
134 changes: 134 additions & 0 deletions fs/xfs/scrub/bmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions fs/xfs/scrub/btree.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
53 changes: 53 additions & 0 deletions fs/xfs/scrub/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down

0 comments on commit d852657

Please sign in to comment.