Commit 7ad9ea63 authored by Darrick J. Wong's avatar Darrick J. Wong

xfs: check the reference counts of gaps in the refcount btree

Gaps in the reference count btree are also significant -- for these
regions, there must not be any overlapping reverse mappings.  We don't
currently check this, so make the refcount scrubber more complete.
Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarDave Chinner <dchinner@redhat.com>
Reviewed-by: default avatarDave Chinner <dchinner@redhat.com>
parent 4a200a09
......@@ -332,6 +332,64 @@ xchk_refcountbt_xref(
xchk_refcountbt_xref_rmap(sc, irec);
}
struct xchk_refcbt_records {
/* The next AG block where we aren't expecting shared extents. */
xfs_agblock_t next_unshared_agbno;
/* Number of CoW blocks we expect. */
xfs_agblock_t cow_blocks;
/* Was the last record a shared or CoW staging extent? */
enum xfs_refc_domain prev_domain;
};
STATIC int
xchk_refcountbt_rmap_check_gap(
struct xfs_btree_cur *cur,
const struct xfs_rmap_irec *rec,
void *priv)
{
xfs_agblock_t *next_bno = priv;
if (*next_bno != NULLAGBLOCK && rec->rm_startblock < *next_bno)
return -ECANCELED;
*next_bno = rec->rm_startblock + rec->rm_blockcount;
return 0;
}
/*
* Make sure that a gap in the reference count records does not correspond to
* overlapping records (i.e. shared extents) in the reverse mappings.
*/
static inline void
xchk_refcountbt_xref_gaps(
struct xfs_scrub *sc,
struct xchk_refcbt_records *rrc,
xfs_agblock_t bno)
{
struct xfs_rmap_irec low;
struct xfs_rmap_irec high;
xfs_agblock_t next_bno = NULLAGBLOCK;
int error;
if (bno <= rrc->next_unshared_agbno || !sc->sa.rmap_cur ||
xchk_skip_xref(sc->sm))
return;
memset(&low, 0, sizeof(low));
low.rm_startblock = rrc->next_unshared_agbno;
memset(&high, 0xFF, sizeof(high));
high.rm_startblock = bno - 1;
error = xfs_rmap_query_range(sc->sa.rmap_cur, &low, &high,
xchk_refcountbt_rmap_check_gap, &next_bno);
if (error == -ECANCELED)
xchk_btree_xref_set_corrupt(sc, sc->sa.rmap_cur, 0);
else
xchk_should_check_xref(sc, &error, &sc->sa.rmap_cur);
}
/* Scrub a refcountbt record. */
STATIC int
xchk_refcountbt_rec(
......@@ -339,7 +397,7 @@ xchk_refcountbt_rec(
const union xfs_btree_rec *rec)
{
struct xfs_refcount_irec irec;
xfs_agblock_t *cow_blocks = bs->private;
struct xchk_refcbt_records *rrc = bs->private;
xfs_refcount_btrec_to_irec(rec, &irec);
if (xfs_refcount_check_irec(bs->cur, &irec) != NULL) {
......@@ -348,10 +406,27 @@ xchk_refcountbt_rec(
}
if (irec.rc_domain == XFS_REFC_DOMAIN_COW)
(*cow_blocks) += irec.rc_blockcount;
rrc->cow_blocks += irec.rc_blockcount;
/* Shared records always come before CoW records. */
if (irec.rc_domain == XFS_REFC_DOMAIN_SHARED &&
rrc->prev_domain == XFS_REFC_DOMAIN_COW)
xchk_btree_set_corrupt(bs->sc, bs->cur, 0);
rrc->prev_domain = irec.rc_domain;
xchk_refcountbt_xref(bs->sc, &irec);
/*
* If this is a record for a shared extent, check that all blocks
* between the previous record and this one have at most one reverse
* mapping.
*/
if (irec.rc_domain == XFS_REFC_DOMAIN_SHARED) {
xchk_refcountbt_xref_gaps(bs->sc, rrc, irec.rc_startblock);
rrc->next_unshared_agbno = irec.rc_startblock +
irec.rc_blockcount;
}
return 0;
}
......@@ -393,15 +468,25 @@ int
xchk_refcountbt(
struct xfs_scrub *sc)
{
xfs_agblock_t cow_blocks = 0;
struct xchk_refcbt_records rrc = {
.cow_blocks = 0,
.next_unshared_agbno = 0,
.prev_domain = XFS_REFC_DOMAIN_SHARED,
};
int error;
error = xchk_btree(sc, sc->sa.refc_cur, xchk_refcountbt_rec,
&XFS_RMAP_OINFO_REFC, &cow_blocks);
&XFS_RMAP_OINFO_REFC, &rrc);
if (error)
return error;
xchk_refcount_xref_rmap(sc, cow_blocks);
/*
* Check that all blocks between the last refcount > 1 record and the
* end of the AG have at most one reverse mapping.
*/
xchk_refcountbt_xref_gaps(sc, &rrc, sc->mp->m_sb.sb_agblocks);
xchk_refcount_xref_rmap(sc, rrc.cow_blocks);
return 0;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment