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

xfs: replace xfs_btree_has_record with a general keyspace scanner



The current implementation of xfs_btree_has_record returns true if it
finds /any/ record within the given range.  Unfortunately, that's not
sufficient for scrub.  We want to be able to tell if a range of keyspace
for a btree is devoid of records, is totally mapped to records, or is
somewhere in between.  By forcing this to be a boolean, we conflated
sparseness and fullness, which caused scrub to return incorrect results.
Fix the API so that we can tell the caller which of those three is the
current state.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarDave Chinner <dchinner@redhat.com>
parent bd7e7951
Loading
Loading
Loading
Loading
+7 −4
Original line number Diff line number Diff line
@@ -3745,13 +3745,16 @@ xfs_alloc_query_all(
	return xfs_btree_query_all(cur, xfs_alloc_query_range_helper, &query);
}

/* Is there a record covering a given extent? */
/*
 * Scan part of the keyspace of the free space and tell us if the area has no
 * records, is fully mapped by records, or is partially filled.
 */
int
xfs_alloc_has_record(
xfs_alloc_has_records(
	struct xfs_btree_cur	*cur,
	xfs_agblock_t		bno,
	xfs_extlen_t		len,
	bool			*exists)
	enum xbtree_recpacking	*outcome)
{
	union xfs_btree_irec	low;
	union xfs_btree_irec	high;
@@ -3761,7 +3764,7 @@ xfs_alloc_has_record(
	memset(&high, 0xFF, sizeof(high));
	high.a.ar_startblock = bno + len - 1;

	return xfs_btree_has_record(cur, &low, &high, exists);
	return xfs_btree_has_records(cur, &low, &high, outcome);
}

/*
+2 −2
Original line number Diff line number Diff line
@@ -213,8 +213,8 @@ int xfs_alloc_query_range(struct xfs_btree_cur *cur,
int xfs_alloc_query_all(struct xfs_btree_cur *cur, xfs_alloc_query_range_fn fn,
		void *priv);

int xfs_alloc_has_record(struct xfs_btree_cur *cur, xfs_agblock_t bno,
		xfs_extlen_t len, bool *exist);
int xfs_alloc_has_records(struct xfs_btree_cur *cur, xfs_agblock_t bno,
		xfs_extlen_t len, enum xbtree_recpacking *outcome);

typedef int (*xfs_agfl_walk_fn)(struct xfs_mount *mp, xfs_agblock_t bno,
		void *priv);
+12 −0
Original line number Diff line number Diff line
@@ -423,6 +423,16 @@ xfs_cntbt_recs_inorder(
		 be32_to_cpu(r2->alloc.ar_startblock));
}

STATIC enum xbtree_key_contig
xfs_allocbt_keys_contiguous(
	struct xfs_btree_cur		*cur,
	const union xfs_btree_key	*key1,
	const union xfs_btree_key	*key2)
{
	return xbtree_key_contig(be32_to_cpu(key1->alloc.ar_startblock),
				 be32_to_cpu(key2->alloc.ar_startblock));
}

static const struct xfs_btree_ops xfs_bnobt_ops = {
	.rec_len		= sizeof(xfs_alloc_rec_t),
	.key_len		= sizeof(xfs_alloc_key_t),
@@ -443,6 +453,7 @@ static const struct xfs_btree_ops xfs_bnobt_ops = {
	.diff_two_keys		= xfs_bnobt_diff_two_keys,
	.keys_inorder		= xfs_bnobt_keys_inorder,
	.recs_inorder		= xfs_bnobt_recs_inorder,
	.keys_contiguous	= xfs_allocbt_keys_contiguous,
};

static const struct xfs_btree_ops xfs_cntbt_ops = {
@@ -465,6 +476,7 @@ static const struct xfs_btree_ops xfs_cntbt_ops = {
	.diff_two_keys		= xfs_cntbt_diff_two_keys,
	.keys_inorder		= xfs_cntbt_keys_inorder,
	.recs_inorder		= xfs_cntbt_recs_inorder,
	.keys_contiguous	= NULL, /* not needed right now */
};

/* Allocate most of a new allocation btree cursor. */
+11 −0
Original line number Diff line number Diff line
@@ -500,6 +500,16 @@ xfs_bmbt_recs_inorder(
		xfs_bmbt_disk_get_startoff(&r2->bmbt);
}

STATIC enum xbtree_key_contig
xfs_bmbt_keys_contiguous(
	struct xfs_btree_cur		*cur,
	const union xfs_btree_key	*key1,
	const union xfs_btree_key	*key2)
{
	return xbtree_key_contig(be64_to_cpu(key1->bmbt.br_startoff),
				 be64_to_cpu(key2->bmbt.br_startoff));
}

static const struct xfs_btree_ops xfs_bmbt_ops = {
	.rec_len		= sizeof(xfs_bmbt_rec_t),
	.key_len		= sizeof(xfs_bmbt_key_t),
@@ -520,6 +530,7 @@ static const struct xfs_btree_ops xfs_bmbt_ops = {
	.buf_ops		= &xfs_bmbt_buf_ops,
	.keys_inorder		= xfs_bmbt_keys_inorder,
	.recs_inorder		= xfs_bmbt_recs_inorder,
	.keys_contiguous	= xfs_bmbt_keys_contiguous,
};

/*
+95 −13
Original line number Diff line number Diff line
@@ -5025,34 +5025,116 @@ xfs_btree_diff_two_ptrs(
	return (int64_t)be32_to_cpu(a->s) - be32_to_cpu(b->s);
}

/* If there's an extent, we're done. */
struct xfs_btree_has_records {
	/* Keys for the start and end of the range we want to know about. */
	union xfs_btree_key		start_key;
	union xfs_btree_key		end_key;

	/* Highest record key we've seen so far. */
	union xfs_btree_key		high_key;

	enum xbtree_recpacking		outcome;
};

STATIC int
xfs_btree_has_record_helper(
xfs_btree_has_records_helper(
	struct xfs_btree_cur		*cur,
	const union xfs_btree_rec	*rec,
	void				*priv)
{
	union xfs_btree_key		rec_key;
	union xfs_btree_key		rec_high_key;
	struct xfs_btree_has_records	*info = priv;
	enum xbtree_key_contig		key_contig;

	cur->bc_ops->init_key_from_rec(&rec_key, rec);

	if (info->outcome == XBTREE_RECPACKING_EMPTY) {
		info->outcome = XBTREE_RECPACKING_SPARSE;

		/*
		 * If the first record we find does not overlap the start key,
		 * then there is a hole at the start of the search range.
		 * Classify this as sparse and stop immediately.
		 */
		if (xfs_btree_keycmp_lt(cur, &info->start_key, &rec_key))
			return -ECANCELED;
	} else {
		/*
		 * If a subsequent record does not overlap with the any record
		 * we've seen so far, there is a hole in the middle of the
		 * search range.  Classify this as sparse and stop.
		 * If the keys overlap and this btree does not allow overlap,
		 * signal corruption.
		 */
		key_contig = cur->bc_ops->keys_contiguous(cur, &info->high_key,
					&rec_key);
		if (key_contig == XBTREE_KEY_OVERLAP &&
				!(cur->bc_flags & XFS_BTREE_OVERLAPPING))
			return -EFSCORRUPTED;
		if (key_contig == XBTREE_KEY_GAP)
			return -ECANCELED;
	}

/* Is there a record covering a given range of keys? */
	/*
	 * If high_key(rec) is larger than any other high key we've seen,
	 * remember it for later.
	 */
	cur->bc_ops->init_high_key_from_rec(&rec_high_key, rec);
	if (xfs_btree_keycmp_gt(cur, &rec_high_key, &info->high_key))
		info->high_key = rec_high_key; /* struct copy */

	return 0;
}

/*
 * Scan part of the keyspace of a btree and tell us if that keyspace does not
 * map to any records; is fully mapped to records; or is partially mapped to
 * records.  This is the btree record equivalent to determining if a file is
 * sparse.
 */
int
xfs_btree_has_record(
xfs_btree_has_records(
	struct xfs_btree_cur		*cur,
	const union xfs_btree_irec	*low,
	const union xfs_btree_irec	*high,
	bool				*exists)
	enum xbtree_recpacking		*outcome)
{
	struct xfs_btree_has_records	info = {
		.outcome		= XBTREE_RECPACKING_EMPTY,
	};
	int				error;

	error = xfs_btree_query_range(cur, low, high,
			&xfs_btree_has_record_helper, NULL);
	if (error == -ECANCELED) {
		*exists = true;
		return 0;
	/* Not all btrees support this operation. */
	if (!cur->bc_ops->keys_contiguous) {
		ASSERT(0);
		return -EOPNOTSUPP;
	}
	*exists = false;

	xfs_btree_key_from_irec(cur, &info.start_key, low);
	xfs_btree_key_from_irec(cur, &info.end_key, high);

	error = xfs_btree_query_range(cur, low, high,
			xfs_btree_has_records_helper, &info);
	if (error == -ECANCELED)
		goto out;
	if (error)
		return error;

	if (info.outcome == XBTREE_RECPACKING_EMPTY)
		goto out;

	/*
	 * If the largest high_key(rec) we saw during the walk is greater than
	 * the end of the search range, classify this as full.  Otherwise,
	 * there is a hole at the end of the search range.
	 */
	if (xfs_btree_keycmp_ge(cur, &info.high_key, &info.end_key))
		info.outcome = XBTREE_RECPACKING_FULL;

out:
	*outcome = info.outcome;
	return 0;
}

/* Are there more records in this btree? */
Loading