Commit 01822a74 authored by Dave Chinner's avatar Dave Chinner Committed by Dave Chinner
Browse files

Merge tag 'btree-complain-bad-records-6.4_2023-04-11' of...

Merge tag 'btree-complain-bad-records-6.4_2023-04-11' of git://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux

 into guilt/xfs-for-next

xfs: standardize btree record checking code [v24.5]

While I was cleaning things up for 6.1, I noticed that the btree
_query_range and _query_all functions don't perform the same checking
that the _get_rec functions perform.  In fact, they don't perform /any/
sanity checking, which means that callers aren't warned about impossible
records.

Therefore, hoist the record validation and complaint logging code into
separate functions, and call them from any place where we convert an
ondisk record into an incore record.  For online scrub, we can replace
checking code with a call to the record checking functions in libxfs,
thereby reducing the size of the codebase.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarDave Chinner <david@fromorbit.com>
parents b634abac 6a3bd8fc
Loading
Loading
Loading
Loading
+60 −22
Original line number Diff line number Diff line
@@ -233,6 +233,52 @@ xfs_alloc_update(
	return xfs_btree_update(cur, &rec);
}

/* Convert the ondisk btree record to its incore representation. */
void
xfs_alloc_btrec_to_irec(
	const union xfs_btree_rec	*rec,
	struct xfs_alloc_rec_incore	*irec)
{
	irec->ar_startblock = be32_to_cpu(rec->alloc.ar_startblock);
	irec->ar_blockcount = be32_to_cpu(rec->alloc.ar_blockcount);
}

/* Simple checks for free space records. */
xfs_failaddr_t
xfs_alloc_check_irec(
	struct xfs_btree_cur		*cur,
	const struct xfs_alloc_rec_incore *irec)
{
	struct xfs_perag		*pag = cur->bc_ag.pag;

	if (irec->ar_blockcount == 0)
		return __this_address;

	/* check for valid extent range, including overflow */
	if (!xfs_verify_agbext(pag, irec->ar_startblock, irec->ar_blockcount))
		return __this_address;

	return NULL;
}

static inline int
xfs_alloc_complain_bad_rec(
	struct xfs_btree_cur		*cur,
	xfs_failaddr_t			fa,
	const struct xfs_alloc_rec_incore *irec)
{
	struct xfs_mount		*mp = cur->bc_mp;

	xfs_warn(mp,
		"%s Freespace BTree record corruption in AG %d detected at %pS!",
		cur->bc_btnum == XFS_BTNUM_BNO ? "Block" : "Size",
		cur->bc_ag.pag->pag_agno, fa);
	xfs_warn(mp,
		"start block 0x%x block count 0x%x", irec->ar_startblock,
		irec->ar_blockcount);
	return -EFSCORRUPTED;
}

/*
 * Get the data from the pointed-to record.
 */
@@ -243,35 +289,23 @@ xfs_alloc_get_rec(
	xfs_extlen_t		*len,	/* output: length of extent */
	int			*stat)	/* output: success/failure */
{
	struct xfs_mount	*mp = cur->bc_mp;
	struct xfs_perag	*pag = cur->bc_ag.pag;
	struct xfs_alloc_rec_incore irec;
	union xfs_btree_rec	*rec;
	xfs_failaddr_t		fa;
	int			error;

	error = xfs_btree_get_rec(cur, &rec, stat);
	if (error || !(*stat))
		return error;

	*bno = be32_to_cpu(rec->alloc.ar_startblock);
	*len = be32_to_cpu(rec->alloc.ar_blockcount);

	if (*len == 0)
		goto out_bad_rec;

	/* check for valid extent range, including overflow */
	if (!xfs_verify_agbext(pag, *bno, *len))
		goto out_bad_rec;
	xfs_alloc_btrec_to_irec(rec, &irec);
	fa = xfs_alloc_check_irec(cur, &irec);
	if (fa)
		return xfs_alloc_complain_bad_rec(cur, fa, &irec);

	*bno = irec.ar_startblock;
	*len = irec.ar_blockcount;
	return 0;

out_bad_rec:
	xfs_warn(mp,
		"%s Freespace BTree record corruption in AG %d detected!",
		cur->bc_btnum == XFS_BTNUM_BNO ? "Block" : "Size",
		pag->pag_agno);
	xfs_warn(mp,
		"start block 0x%x block count 0x%x", *bno, *len);
	return -EFSCORRUPTED;
}

/*
@@ -3664,9 +3698,13 @@ xfs_alloc_query_range_helper(
{
	struct xfs_alloc_query_range_info	*query = priv;
	struct xfs_alloc_rec_incore		irec;
	xfs_failaddr_t				fa;

	xfs_alloc_btrec_to_irec(rec, &irec);
	fa = xfs_alloc_check_irec(cur, &irec);
	if (fa)
		return xfs_alloc_complain_bad_rec(cur, fa, &irec);

	irec.ar_startblock = be32_to_cpu(rec->alloc.ar_startblock);
	irec.ar_blockcount = be32_to_cpu(rec->alloc.ar_blockcount);
	return query->fn(cur, &irec, query->priv);
}

+6 −0
Original line number Diff line number Diff line
@@ -181,6 +181,12 @@ xfs_alloc_get_rec(
	xfs_extlen_t		*len,	/* output: length of extent */
	int			*stat);	/* output: success/failure */

union xfs_btree_rec;
void xfs_alloc_btrec_to_irec(const union xfs_btree_rec *rec,
		struct xfs_alloc_rec_incore *irec);
xfs_failaddr_t xfs_alloc_check_irec(struct xfs_btree_cur *cur,
		const struct xfs_alloc_rec_incore *irec);

int xfs_read_agf(struct xfs_perag *pag, struct xfs_trans *tp, int flags,
		struct xfs_buf **agfbpp);
int xfs_alloc_read_agf(struct xfs_perag *pag, struct xfs_trans *tp, int flags,
+30 −1
Original line number Diff line number Diff line
@@ -1083,6 +1083,34 @@ struct xfs_iread_state {
	xfs_extnum_t		loaded;
};

int
xfs_bmap_complain_bad_rec(
	struct xfs_inode		*ip,
	int				whichfork,
	xfs_failaddr_t			fa,
	const struct xfs_bmbt_irec	*irec)
{
	struct xfs_mount		*mp = ip->i_mount;
	const char			*forkname;

	switch (whichfork) {
	case XFS_DATA_FORK:	forkname = "data"; break;
	case XFS_ATTR_FORK:	forkname = "attr"; break;
	case XFS_COW_FORK:	forkname = "CoW"; break;
	default:		forkname = "???"; break;
	}

	xfs_warn(mp,
 "Bmap BTree record corruption in inode 0x%llx %s fork detected at %pS!",
				ip->i_ino, forkname, fa);
	xfs_warn(mp,
		"Offset 0x%llx, start block 0x%llx, block count 0x%llx state 0x%x",
		irec->br_startoff, irec->br_startblock, irec->br_blockcount,
		irec->br_state);

	return -EFSCORRUPTED;
}

/* Stuff every bmbt record from this block into the incore extent map. */
static int
xfs_iread_bmbt_block(
@@ -1125,7 +1153,8 @@ xfs_iread_bmbt_block(
			xfs_inode_verifier_error(ip, -EFSCORRUPTED,
					"xfs_iread_extents(2)", frp,
					sizeof(*frp), fa);
			return -EFSCORRUPTED;
			return xfs_bmap_complain_bad_rec(ip, whichfork, fa,
					&new);
		}
		xfs_iext_insert(ip, &ir->icur, &new,
				xfs_bmap_fork_to_state(whichfork));
+2 −0
Original line number Diff line number Diff line
@@ -265,6 +265,8 @@ static inline uint32_t xfs_bmap_fork_to_state(int whichfork)

xfs_failaddr_t xfs_bmap_validate_extent(struct xfs_inode *ip, int whichfork,
		struct xfs_bmbt_irec *irec);
int xfs_bmap_complain_bad_rec(struct xfs_inode *ip, int whichfork,
		xfs_failaddr_t fa, const struct xfs_bmbt_irec *irec);

int	xfs_bmapi_remap(struct xfs_trans *tp, struct xfs_inode *ip,
		xfs_fileoff_t bno, xfs_filblks_t len, xfs_fsblock_t startblock,
+52 −25
Original line number Diff line number Diff line
@@ -95,33 +95,21 @@ xfs_inobt_btrec_to_irec(
	irec->ir_free = be64_to_cpu(rec->inobt.ir_free);
}

/*
 * Get the data from the pointed-to record.
 */
int
xfs_inobt_get_rec(
/* Simple checks for inode records. */
xfs_failaddr_t
xfs_inobt_check_irec(
	struct xfs_btree_cur			*cur,
	struct xfs_inobt_rec_incore	*irec,
	int				*stat)
	const struct xfs_inobt_rec_incore	*irec)
{
	struct xfs_mount		*mp = cur->bc_mp;
	union xfs_btree_rec		*rec;
	int				error;
	uint64_t			realfree;

	error = xfs_btree_get_rec(cur, &rec, stat);
	if (error || *stat == 0)
		return error;

	xfs_inobt_btrec_to_irec(mp, rec, irec);

	if (!xfs_verify_agino(cur->bc_ag.pag, irec->ir_startino))
		goto out_bad_rec;
		return __this_address;
	if (irec->ir_count < XFS_INODES_PER_HOLEMASK_BIT ||
	    irec->ir_count > XFS_INODES_PER_CHUNK)
		goto out_bad_rec;
		return __this_address;
	if (irec->ir_freecount > XFS_INODES_PER_CHUNK)
		goto out_bad_rec;
		return __this_address;

	/* if there are no holes, return the first available offset */
	if (!xfs_inobt_issparse(irec->ir_holemask))
@@ -129,15 +117,23 @@ xfs_inobt_get_rec(
	else
		realfree = irec->ir_free & xfs_inobt_irec_to_allocmask(irec);
	if (hweight64(realfree) != irec->ir_freecount)
		goto out_bad_rec;
		return __this_address;

	return 0;
	return NULL;
}

static inline int
xfs_inobt_complain_bad_rec(
	struct xfs_btree_cur		*cur,
	xfs_failaddr_t			fa,
	const struct xfs_inobt_rec_incore *irec)
{
	struct xfs_mount		*mp = cur->bc_mp;

out_bad_rec:
	xfs_warn(mp,
		"%s Inode BTree record corruption in AG %d detected!",
		"%s Inode BTree record corruption in AG %d detected at %pS!",
		cur->bc_btnum == XFS_BTNUM_INO ? "Used" : "Free",
		cur->bc_ag.pag->pag_agno);
		cur->bc_ag.pag->pag_agno, fa);
	xfs_warn(mp,
"start inode 0x%x, count 0x%x, free 0x%x freemask 0x%llx, holemask 0x%x",
		irec->ir_startino, irec->ir_count, irec->ir_freecount,
@@ -145,6 +141,32 @@ xfs_inobt_get_rec(
	return -EFSCORRUPTED;
}

/*
 * Get the data from the pointed-to record.
 */
int
xfs_inobt_get_rec(
	struct xfs_btree_cur		*cur,
	struct xfs_inobt_rec_incore	*irec,
	int				*stat)
{
	struct xfs_mount		*mp = cur->bc_mp;
	union xfs_btree_rec		*rec;
	xfs_failaddr_t			fa;
	int				error;

	error = xfs_btree_get_rec(cur, &rec, stat);
	if (error || *stat == 0)
		return error;

	xfs_inobt_btrec_to_irec(mp, rec, irec);
	fa = xfs_inobt_check_irec(cur, irec);
	if (fa)
		return xfs_inobt_complain_bad_rec(cur, fa, irec);

	return 0;
}

/*
 * Insert a single inobt record. Cursor must already point to desired location.
 */
@@ -2688,8 +2710,13 @@ xfs_ialloc_count_inodes_rec(
{
	struct xfs_inobt_rec_incore	irec;
	struct xfs_ialloc_count_inodes	*ci = priv;
	xfs_failaddr_t			fa;

	xfs_inobt_btrec_to_irec(cur->bc_mp, rec, &irec);
	fa = xfs_inobt_check_irec(cur, &irec);
	if (fa)
		return xfs_inobt_complain_bad_rec(cur, fa, &irec);

	ci->count += irec.ir_count;
	ci->freecount += irec.ir_freecount;

Loading