Commit b1bdab25 authored by Dave Chinner's avatar Dave Chinner Committed by Dave Chinner
Browse files

Merge tag 'scrub-detect-rmapbt-gaps-6.4_2023-04-11' of...

Merge tag 'scrub-detect-rmapbt-gaps-6.4_2023-04-11' of git://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux

 into guilt/xfs-for-next

xfs: detect incorrect gaps in rmap btree [v24.5]

Following in the theme of the last two patchsets, this one strengthens
the rmap btree record checking so that scrub can count the number of
space records that map to a given owner and that do not map to a given
owner.  This enables us to determine exclusive ownership of space that
can't be shared.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarDave Chinner <david@fromorbit.com>
parents f1121b99 30f8ee5e
Loading
Loading
Loading
Loading
+133 −59
Original line number Original line Diff line number Diff line
@@ -2735,65 +2735,141 @@ xfs_rmap_has_records(
	return xfs_btree_has_records(cur, &low, &high, &mask, outcome);
	return xfs_btree_has_records(cur, &low, &high, &mask, outcome);
}
}


/*
struct xfs_rmap_ownercount {
 * Is there a record for this owner completely covering a given physical
	/* Owner that we're looking for. */
 * extent?  If so, *has_rmap will be set to true.  If there is no record
	struct xfs_rmap_irec	good;
 * or the record only covers part of the range, we set *has_rmap to false.

 * This function doesn't perform range lookups or offset checks, so it is
	/* rmap search keys */
 * not suitable for checking data fork blocks.
	struct xfs_rmap_irec	low;
 */
	struct xfs_rmap_irec	high;
int

xfs_rmap_record_exists(
	struct xfs_rmap_matches	*results;
	struct xfs_btree_cur		*cur,

	/* Stop early if we find a nonmatch? */
	bool			stop_on_nonmatch;
};

/* Does this rmap represent space that can have multiple owners? */
static inline bool
xfs_rmap_shareable(
	struct xfs_mount		*mp,
	const struct xfs_rmap_irec	*rmap)
{
	if (!xfs_has_reflink(mp))
		return false;
	if (XFS_RMAP_NON_INODE_OWNER(rmap->rm_owner))
		return false;
	if (rmap->rm_flags & (XFS_RMAP_ATTR_FORK |
			      XFS_RMAP_BMBT_BLOCK))
		return false;
	return true;
}

static inline void
xfs_rmap_ownercount_init(
	struct xfs_rmap_ownercount	*roc,
	xfs_agblock_t			bno,
	xfs_agblock_t			bno,
	xfs_extlen_t			len,
	xfs_extlen_t			len,
	const struct xfs_owner_info	*oinfo,
	const struct xfs_owner_info	*oinfo,
	bool				*has_rmap)
	struct xfs_rmap_matches		*results)
{
{
	uint64_t			owner;
	memset(roc, 0, sizeof(*roc));
	uint64_t			offset;
	roc->results = results;
	unsigned int			flags;
	int				has_record;
	struct xfs_rmap_irec		irec;
	int				error;

	xfs_owner_info_unpack(oinfo, &owner, &offset, &flags);
	ASSERT(XFS_RMAP_NON_INODE_OWNER(owner) ||
	       (flags & XFS_RMAP_BMBT_BLOCK));


	error = xfs_rmap_lookup_le(cur, bno, owner, offset, flags, &irec,
	roc->low.rm_startblock = bno;
			&has_record);
	memset(&roc->high, 0xFF, sizeof(roc->high));
	if (error)
	roc->high.rm_startblock = bno + len - 1;
		return error;
	if (!has_record) {
		*has_rmap = false;
		return 0;
	}


	*has_rmap = (irec.rm_owner == owner && irec.rm_startblock <= bno &&
	memset(results, 0, sizeof(*results));
		     irec.rm_startblock + irec.rm_blockcount >= bno + len);
	roc->good.rm_startblock = bno;
	return 0;
	roc->good.rm_blockcount = len;
	roc->good.rm_owner = oinfo->oi_owner;
	roc->good.rm_offset = oinfo->oi_offset;
	if (oinfo->oi_flags & XFS_OWNER_INFO_ATTR_FORK)
		roc->good.rm_flags |= XFS_RMAP_ATTR_FORK;
	if (oinfo->oi_flags & XFS_OWNER_INFO_BMBT_BLOCK)
		roc->good.rm_flags |= XFS_RMAP_BMBT_BLOCK;
}
}


struct xfs_rmap_key_state {
/* Figure out if this is a match for the owner. */
	uint64_t			owner;
	uint64_t			offset;
	unsigned int			flags;
};

/* For each rmap given, figure out if it doesn't match the key we want. */
STATIC int
STATIC int
xfs_rmap_has_other_keys_helper(
xfs_rmap_count_owners_helper(
	struct xfs_btree_cur		*cur,
	struct xfs_btree_cur		*cur,
	const struct xfs_rmap_irec	*rec,
	const struct xfs_rmap_irec	*rec,
	void				*priv)
	void				*priv)
{
{
	struct xfs_rmap_key_state	*rks = priv;
	struct xfs_rmap_ownercount	*roc = priv;
	struct xfs_rmap_irec		check = *rec;
	unsigned int			keyflags;
	bool				filedata;
	int64_t				delta;

	filedata = !XFS_RMAP_NON_INODE_OWNER(check.rm_owner) &&
		   !(check.rm_flags & XFS_RMAP_BMBT_BLOCK);

	/* Trim the part of check that comes before the comparison range. */
	delta = (int64_t)roc->good.rm_startblock - check.rm_startblock;
	if (delta > 0) {
		check.rm_startblock += delta;
		check.rm_blockcount -= delta;
		if (filedata)
			check.rm_offset += delta;
	}

	/* Trim the part of check that comes after the comparison range. */
	delta = (check.rm_startblock + check.rm_blockcount) -
		(roc->good.rm_startblock + roc->good.rm_blockcount);
	if (delta > 0)
		check.rm_blockcount -= delta;

	/* Don't care about unwritten status for establishing ownership. */
	keyflags = check.rm_flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK);

	if (check.rm_startblock	== roc->good.rm_startblock &&
	    check.rm_blockcount	== roc->good.rm_blockcount &&
	    check.rm_owner	== roc->good.rm_owner &&
	    check.rm_offset	== roc->good.rm_offset &&
	    keyflags		== roc->good.rm_flags) {
		roc->results->matches++;
	} else {
		roc->results->non_owner_matches++;
		if (xfs_rmap_shareable(cur->bc_mp, &roc->good) ^
		    xfs_rmap_shareable(cur->bc_mp, &check))
			roc->results->bad_non_owner_matches++;
	}


	if (rks->owner == rec->rm_owner && rks->offset == rec->rm_offset &&
	if (roc->results->non_owner_matches && roc->stop_on_nonmatch)
	    ((rks->flags & rec->rm_flags) & XFS_RMAP_KEY_FLAGS) == rks->flags)
		return 0;
		return -ECANCELED;
		return -ECANCELED;

	return 0;
}

/* Count the number of owners and non-owners of this range of blocks. */
int
xfs_rmap_count_owners(
	struct xfs_btree_cur		*cur,
	xfs_agblock_t			bno,
	xfs_extlen_t			len,
	const struct xfs_owner_info	*oinfo,
	struct xfs_rmap_matches		*results)
{
	struct xfs_rmap_ownercount	roc;
	int				error;

	xfs_rmap_ownercount_init(&roc, bno, len, oinfo, results);
	error = xfs_rmap_query_range(cur, &roc.low, &roc.high,
			xfs_rmap_count_owners_helper, &roc);
	if (error)
		return error;

	/*
	 * There can't be any non-owner rmaps that conflict with the given
	 * owner if we didn't find any rmaps matching the owner.
	 */
	if (!results->matches)
		results->bad_non_owner_matches = 0;

	return 0;
}
}


/*
/*
@@ -2806,28 +2882,26 @@ xfs_rmap_has_other_keys(
	xfs_agblock_t			bno,
	xfs_agblock_t			bno,
	xfs_extlen_t			len,
	xfs_extlen_t			len,
	const struct xfs_owner_info	*oinfo,
	const struct xfs_owner_info	*oinfo,
	bool				*has_rmap)
	bool				*has_other)
{
{
	struct xfs_rmap_irec		low = {0};
	struct xfs_rmap_matches		res;
	struct xfs_rmap_irec		high;
	struct xfs_rmap_ownercount	roc;
	struct xfs_rmap_key_state	rks;
	int				error;
	int				error;


	xfs_owner_info_unpack(oinfo, &rks.owner, &rks.offset, &rks.flags);
	xfs_rmap_ownercount_init(&roc, bno, len, oinfo, &res);
	*has_rmap = false;
	roc.stop_on_nonmatch = true;

	low.rm_startblock = bno;
	memset(&high, 0xFF, sizeof(high));
	high.rm_startblock = bno + len - 1;


	error = xfs_rmap_query_range(cur, &low, &high,
	error = xfs_rmap_query_range(cur, &roc.low, &roc.high,
			xfs_rmap_has_other_keys_helper, &rks);
			xfs_rmap_count_owners_helper, &roc);
	if (error == -ECANCELED) {
	if (error == -ECANCELED) {
		*has_rmap = true;
		*has_other = true;
		return 0;
		return 0;
	}
	}

	if (error)
		return error;
		return error;

	*has_other = false;
	return 0;
}
}


const struct xfs_owner_info XFS_RMAP_OINFO_SKIP_UPDATE = {
const struct xfs_owner_info XFS_RMAP_OINFO_SKIP_UPDATE = {
+15 −3
Original line number Original line Diff line number Diff line
@@ -200,12 +200,24 @@ xfs_failaddr_t xfs_rmap_check_irec(struct xfs_btree_cur *cur,


int xfs_rmap_has_records(struct xfs_btree_cur *cur, xfs_agblock_t bno,
int xfs_rmap_has_records(struct xfs_btree_cur *cur, xfs_agblock_t bno,
		xfs_extlen_t len, enum xbtree_recpacking *outcome);
		xfs_extlen_t len, enum xbtree_recpacking *outcome);
int xfs_rmap_record_exists(struct xfs_btree_cur *cur, xfs_agblock_t bno,

struct xfs_rmap_matches {
	/* Number of owner matches. */
	unsigned long long	matches;

	/* Number of non-owner matches. */
	unsigned long long	non_owner_matches;

	/* Number of non-owner matches that conflict with the owner matches. */
	unsigned long long	bad_non_owner_matches;
};

int xfs_rmap_count_owners(struct xfs_btree_cur *cur, xfs_agblock_t bno,
		xfs_extlen_t len, const struct xfs_owner_info *oinfo,
		xfs_extlen_t len, const struct xfs_owner_info *oinfo,
		bool *has_rmap);
		struct xfs_rmap_matches *rmatch);
int xfs_rmap_has_other_keys(struct xfs_btree_cur *cur, xfs_agblock_t bno,
int xfs_rmap_has_other_keys(struct xfs_btree_cur *cur, xfs_agblock_t bno,
		xfs_extlen_t len, const struct xfs_owner_info *oinfo,
		xfs_extlen_t len, const struct xfs_owner_info *oinfo,
		bool *has_rmap);
		bool *has_other);
int xfs_rmap_map_raw(struct xfs_btree_cur *cur, struct xfs_rmap_irec *rmap);
int xfs_rmap_map_raw(struct xfs_btree_cur *cur, struct xfs_rmap_irec *rmap);


extern const struct xfs_owner_info XFS_RMAP_OINFO_SKIP_UPDATE;
extern const struct xfs_owner_info XFS_RMAP_OINFO_SKIP_UPDATE;
+5 −5
Original line number Original line Diff line number Diff line
@@ -51,7 +51,7 @@ xchk_superblock_xref(


	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);


@@ -515,7 +515,7 @@ xchk_agf_xref(
	xchk_agf_xref_freeblks(sc);
	xchk_agf_xref_freeblks(sc);
	xchk_agf_xref_cntbt(sc);
	xchk_agf_xref_cntbt(sc);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_agf_xref_btreeblks(sc);
	xchk_agf_xref_btreeblks(sc);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
@@ -644,7 +644,7 @@ xchk_agfl_block_xref(


	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_AG);
	xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_AG);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
}
}
@@ -701,7 +701,7 @@ xchk_agfl_xref(


	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);


@@ -857,7 +857,7 @@ xchk_agi_xref(
	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_used_space(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_xref_is_not_inode_chunk(sc, agbno, 1);
	xchk_agi_xref_icounts(sc);
	xchk_agi_xref_icounts(sc);
	xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_shared(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
	xchk_xref_is_not_cow_staging(sc, agbno, 1);
	xchk_agi_xref_fiblocks(sc);
	xchk_agi_xref_fiblocks(sc);
+13 −1
Original line number Original line Diff line number Diff line
@@ -308,6 +308,7 @@ xchk_bmap_iextent_xref(
	struct xchk_bmap_info	*info,
	struct xchk_bmap_info	*info,
	struct xfs_bmbt_irec	*irec)
	struct xfs_bmbt_irec	*irec)
{
{
	struct xfs_owner_info	oinfo;
	struct xfs_mount	*mp = info->sc->mp;
	struct xfs_mount	*mp = info->sc->mp;
	xfs_agnumber_t		agno;
	xfs_agnumber_t		agno;
	xfs_agblock_t		agbno;
	xfs_agblock_t		agbno;
@@ -328,19 +329,30 @@ xchk_bmap_iextent_xref(
	xchk_bmap_xref_rmap(info, irec, agbno);
	xchk_bmap_xref_rmap(info, irec, agbno);
	switch (info->whichfork) {
	switch (info->whichfork) {
	case XFS_DATA_FORK:
	case XFS_DATA_FORK:
		if (!xfs_is_reflink_inode(info->sc->ip))
		if (!xfs_is_reflink_inode(info->sc->ip)) {
			xfs_rmap_ino_owner(&oinfo, info->sc->ip->i_ino,
					info->whichfork, irec->br_startoff);
			xchk_xref_is_only_owned_by(info->sc, agbno,
					irec->br_blockcount, &oinfo);
			xchk_xref_is_not_shared(info->sc, agbno,
			xchk_xref_is_not_shared(info->sc, agbno,
					irec->br_blockcount);
					irec->br_blockcount);
		}
		xchk_xref_is_not_cow_staging(info->sc, agbno,
		xchk_xref_is_not_cow_staging(info->sc, agbno,
				irec->br_blockcount);
				irec->br_blockcount);
		break;
		break;
	case XFS_ATTR_FORK:
	case XFS_ATTR_FORK:
		xfs_rmap_ino_owner(&oinfo, info->sc->ip->i_ino,
				info->whichfork, irec->br_startoff);
		xchk_xref_is_only_owned_by(info->sc, agbno, irec->br_blockcount,
				&oinfo);
		xchk_xref_is_not_shared(info->sc, agbno,
		xchk_xref_is_not_shared(info->sc, agbno,
				irec->br_blockcount);
				irec->br_blockcount);
		xchk_xref_is_not_cow_staging(info->sc, agbno,
		xchk_xref_is_not_cow_staging(info->sc, agbno,
				irec->br_blockcount);
				irec->br_blockcount);
		break;
		break;
	case XFS_COW_FORK:
	case XFS_COW_FORK:
		xchk_xref_is_only_owned_by(info->sc, agbno, irec->br_blockcount,
				&XFS_RMAP_OINFO_COW);
		xchk_xref_is_cow_staging(info->sc, agbno,
		xchk_xref_is_cow_staging(info->sc, agbno,
				irec->br_blockcount);
				irec->br_blockcount);
		xchk_xref_is_not_shared(info->sc, agbno,
		xchk_xref_is_not_shared(info->sc, agbno,
+1 −1
Original line number Original line Diff line number Diff line
@@ -402,7 +402,7 @@ xchk_btree_check_block_owner(
	if (!bs->sc->sa.bno_cur && btnum == XFS_BTNUM_BNO)
	if (!bs->sc->sa.bno_cur && btnum == XFS_BTNUM_BNO)
		bs->cur = NULL;
		bs->cur = NULL;


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


Loading