| // SPDX-License-Identifier: GPL-2.0+ | 
 | /* | 
 |  * Copyright (C) 2017 Oracle.  All Rights Reserved. | 
 |  * Author: Darrick J. Wong <darrick.wong@oracle.com> | 
 |  */ | 
 | #include "xfs.h" | 
 | #include "xfs_fs.h" | 
 | #include "xfs_shared.h" | 
 | #include "xfs_format.h" | 
 | #include "xfs_trans_resv.h" | 
 | #include "xfs_mount.h" | 
 | #include "xfs_btree.h" | 
 | #include "scrub/scrub.h" | 
 | #include "scrub/common.h" | 
 | #include "scrub/btree.h" | 
 | #include "scrub/trace.h" | 
 |  | 
 | /* btree scrubbing */ | 
 |  | 
 | /* | 
 |  * Check for btree operation errors.  See the section about handling | 
 |  * operational errors in common.c. | 
 |  */ | 
 | static bool | 
 | __xchk_btree_process_error( | 
 | 	struct xfs_scrub	*sc, | 
 | 	struct xfs_btree_cur	*cur, | 
 | 	int			level, | 
 | 	int			*error, | 
 | 	__u32			errflag, | 
 | 	void			*ret_ip) | 
 | { | 
 | 	if (*error == 0) | 
 | 		return true; | 
 |  | 
 | 	switch (*error) { | 
 | 	case -EDEADLOCK: | 
 | 		/* Used to restart an op with deadlock avoidance. */ | 
 | 		trace_xchk_deadlock_retry(sc->ip, sc->sm, *error); | 
 | 		break; | 
 | 	case -EFSBADCRC: | 
 | 	case -EFSCORRUPTED: | 
 | 		/* Note the badness but don't abort. */ | 
 | 		sc->sm->sm_flags |= errflag; | 
 | 		*error = 0; | 
 | 		/* fall through */ | 
 | 	default: | 
 | 		if (cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) | 
 | 			trace_xchk_ifork_btree_op_error(sc, cur, level, | 
 | 					*error, ret_ip); | 
 | 		else | 
 | 			trace_xchk_btree_op_error(sc, cur, level, | 
 | 					*error, ret_ip); | 
 | 		break; | 
 | 	} | 
 | 	return false; | 
 | } | 
 |  | 
 | bool | 
 | xchk_btree_process_error( | 
 | 	struct xfs_scrub	*sc, | 
 | 	struct xfs_btree_cur	*cur, | 
 | 	int			level, | 
 | 	int			*error) | 
 | { | 
 | 	return __xchk_btree_process_error(sc, cur, level, error, | 
 | 			XFS_SCRUB_OFLAG_CORRUPT, __return_address); | 
 | } | 
 |  | 
 | bool | 
 | xchk_btree_xref_process_error( | 
 | 	struct xfs_scrub	*sc, | 
 | 	struct xfs_btree_cur	*cur, | 
 | 	int			level, | 
 | 	int			*error) | 
 | { | 
 | 	return __xchk_btree_process_error(sc, cur, level, error, | 
 | 			XFS_SCRUB_OFLAG_XFAIL, __return_address); | 
 | } | 
 |  | 
 | /* Record btree block corruption. */ | 
 | static void | 
 | __xchk_btree_set_corrupt( | 
 | 	struct xfs_scrub	*sc, | 
 | 	struct xfs_btree_cur	*cur, | 
 | 	int			level, | 
 | 	__u32			errflag, | 
 | 	void			*ret_ip) | 
 | { | 
 | 	sc->sm->sm_flags |= errflag; | 
 |  | 
 | 	if (cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) | 
 | 		trace_xchk_ifork_btree_error(sc, cur, level, | 
 | 				ret_ip); | 
 | 	else | 
 | 		trace_xchk_btree_error(sc, cur, level, | 
 | 				ret_ip); | 
 | } | 
 |  | 
 | void | 
 | xchk_btree_set_corrupt( | 
 | 	struct xfs_scrub	*sc, | 
 | 	struct xfs_btree_cur	*cur, | 
 | 	int			level) | 
 | { | 
 | 	__xchk_btree_set_corrupt(sc, cur, level, XFS_SCRUB_OFLAG_CORRUPT, | 
 | 			__return_address); | 
 | } | 
 |  | 
 | void | 
 | xchk_btree_xref_set_corrupt( | 
 | 	struct xfs_scrub	*sc, | 
 | 	struct xfs_btree_cur	*cur, | 
 | 	int			level) | 
 | { | 
 | 	__xchk_btree_set_corrupt(sc, cur, level, XFS_SCRUB_OFLAG_XCORRUPT, | 
 | 			__return_address); | 
 | } | 
 |  | 
 | /* | 
 |  * Make sure this record is in order and doesn't stray outside of the parent | 
 |  * keys. | 
 |  */ | 
 | STATIC void | 
 | xchk_btree_rec( | 
 | 	struct xchk_btree	*bs) | 
 | { | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	union xfs_btree_rec	*rec; | 
 | 	union xfs_btree_key	key; | 
 | 	union xfs_btree_key	hkey; | 
 | 	union xfs_btree_key	*keyp; | 
 | 	struct xfs_btree_block	*block; | 
 | 	struct xfs_btree_block	*keyblock; | 
 | 	struct xfs_buf		*bp; | 
 |  | 
 | 	block = xfs_btree_get_block(cur, 0, &bp); | 
 | 	rec = xfs_btree_rec_addr(cur, cur->bc_ptrs[0], block); | 
 |  | 
 | 	trace_xchk_btree_rec(bs->sc, cur, 0); | 
 |  | 
 | 	/* If this isn't the first record, are they in order? */ | 
 | 	if (!bs->firstrec && !cur->bc_ops->recs_inorder(cur, &bs->lastrec, rec)) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, 0); | 
 | 	bs->firstrec = false; | 
 | 	memcpy(&bs->lastrec, rec, cur->bc_ops->rec_len); | 
 |  | 
 | 	if (cur->bc_nlevels == 1) | 
 | 		return; | 
 |  | 
 | 	/* Is this at least as large as the parent low key? */ | 
 | 	cur->bc_ops->init_key_from_rec(&key, rec); | 
 | 	keyblock = xfs_btree_get_block(cur, 1, &bp); | 
 | 	keyp = xfs_btree_key_addr(cur, cur->bc_ptrs[1], keyblock); | 
 | 	if (cur->bc_ops->diff_two_keys(cur, &key, keyp) < 0) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, 1); | 
 |  | 
 | 	if (!(cur->bc_flags & XFS_BTREE_OVERLAPPING)) | 
 | 		return; | 
 |  | 
 | 	/* Is this no larger than the parent high key? */ | 
 | 	cur->bc_ops->init_high_key_from_rec(&hkey, rec); | 
 | 	keyp = xfs_btree_high_key_addr(cur, cur->bc_ptrs[1], keyblock); | 
 | 	if (cur->bc_ops->diff_two_keys(cur, keyp, &hkey) < 0) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, 1); | 
 | } | 
 |  | 
 | /* | 
 |  * Make sure this key is in order and doesn't stray outside of the parent | 
 |  * keys. | 
 |  */ | 
 | STATIC void | 
 | xchk_btree_key( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level) | 
 | { | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	union xfs_btree_key	*key; | 
 | 	union xfs_btree_key	*keyp; | 
 | 	struct xfs_btree_block	*block; | 
 | 	struct xfs_btree_block	*keyblock; | 
 | 	struct xfs_buf		*bp; | 
 |  | 
 | 	block = xfs_btree_get_block(cur, level, &bp); | 
 | 	key = xfs_btree_key_addr(cur, cur->bc_ptrs[level], block); | 
 |  | 
 | 	trace_xchk_btree_key(bs->sc, cur, level); | 
 |  | 
 | 	/* If this isn't the first key, are they in order? */ | 
 | 	if (!bs->firstkey[level] && | 
 | 	    !cur->bc_ops->keys_inorder(cur, &bs->lastkey[level], key)) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | 	bs->firstkey[level] = false; | 
 | 	memcpy(&bs->lastkey[level], key, cur->bc_ops->key_len); | 
 |  | 
 | 	if (level + 1 >= cur->bc_nlevels) | 
 | 		return; | 
 |  | 
 | 	/* Is this at least as large as the parent low key? */ | 
 | 	keyblock = xfs_btree_get_block(cur, level + 1, &bp); | 
 | 	keyp = xfs_btree_key_addr(cur, cur->bc_ptrs[level + 1], keyblock); | 
 | 	if (cur->bc_ops->diff_two_keys(cur, key, keyp) < 0) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, level); | 
 |  | 
 | 	if (!(cur->bc_flags & XFS_BTREE_OVERLAPPING)) | 
 | 		return; | 
 |  | 
 | 	/* Is this no larger than the parent high key? */ | 
 | 	key = xfs_btree_high_key_addr(cur, cur->bc_ptrs[level], block); | 
 | 	keyp = xfs_btree_high_key_addr(cur, cur->bc_ptrs[level + 1], keyblock); | 
 | 	if (cur->bc_ops->diff_two_keys(cur, keyp, key) < 0) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | } | 
 |  | 
 | /* | 
 |  * Check a btree pointer.  Returns true if it's ok to use this pointer. | 
 |  * Callers do not need to set the corrupt flag. | 
 |  */ | 
 | static bool | 
 | xchk_btree_ptr_ok( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	union xfs_btree_ptr	*ptr) | 
 | { | 
 | 	bool			res; | 
 |  | 
 | 	/* A btree rooted in an inode has no block pointer to the root. */ | 
 | 	if ((bs->cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) && | 
 | 	    level == bs->cur->bc_nlevels) | 
 | 		return true; | 
 |  | 
 | 	/* Otherwise, check the pointers. */ | 
 | 	if (bs->cur->bc_flags & XFS_BTREE_LONG_PTRS) | 
 | 		res = xfs_btree_check_lptr(bs->cur, be64_to_cpu(ptr->l), level); | 
 | 	else | 
 | 		res = xfs_btree_check_sptr(bs->cur, be32_to_cpu(ptr->s), level); | 
 | 	if (!res) | 
 | 		xchk_btree_set_corrupt(bs->sc, bs->cur, level); | 
 |  | 
 | 	return res; | 
 | } | 
 |  | 
 | /* Check that a btree block's sibling matches what we expect it. */ | 
 | STATIC int | 
 | xchk_btree_block_check_sibling( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	int			direction, | 
 | 	union xfs_btree_ptr	*sibling) | 
 | { | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	struct xfs_btree_block	*pblock; | 
 | 	struct xfs_buf		*pbp; | 
 | 	struct xfs_btree_cur	*ncur = NULL; | 
 | 	union xfs_btree_ptr	*pp; | 
 | 	int			success; | 
 | 	int			error; | 
 |  | 
 | 	error = xfs_btree_dup_cursor(cur, &ncur); | 
 | 	if (!xchk_btree_process_error(bs->sc, cur, level + 1, &error) || | 
 | 	    !ncur) | 
 | 		return error; | 
 |  | 
 | 	/* | 
 | 	 * If the pointer is null, we shouldn't be able to move the upper | 
 | 	 * level pointer anywhere. | 
 | 	 */ | 
 | 	if (xfs_btree_ptr_is_null(cur, sibling)) { | 
 | 		if (direction > 0) | 
 | 			error = xfs_btree_increment(ncur, level + 1, &success); | 
 | 		else | 
 | 			error = xfs_btree_decrement(ncur, level + 1, &success); | 
 | 		if (error == 0 && success) | 
 | 			xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | 		error = 0; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* Increment upper level pointer. */ | 
 | 	if (direction > 0) | 
 | 		error = xfs_btree_increment(ncur, level + 1, &success); | 
 | 	else | 
 | 		error = xfs_btree_decrement(ncur, level + 1, &success); | 
 | 	if (!xchk_btree_process_error(bs->sc, cur, level + 1, &error)) | 
 | 		goto out; | 
 | 	if (!success) { | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, level + 1); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* Compare upper level pointer to sibling pointer. */ | 
 | 	pblock = xfs_btree_get_block(ncur, level + 1, &pbp); | 
 | 	pp = xfs_btree_ptr_addr(ncur, ncur->bc_ptrs[level + 1], pblock); | 
 | 	if (!xchk_btree_ptr_ok(bs, level + 1, pp)) | 
 | 		goto out; | 
 | 	if (pbp) | 
 | 		xchk_buffer_recheck(bs->sc, pbp); | 
 |  | 
 | 	if (xfs_btree_diff_two_ptrs(cur, pp, sibling)) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | out: | 
 | 	xfs_btree_del_cursor(ncur, XFS_BTREE_ERROR); | 
 | 	return error; | 
 | } | 
 |  | 
 | /* Check the siblings of a btree block. */ | 
 | STATIC int | 
 | xchk_btree_block_check_siblings( | 
 | 	struct xchk_btree	*bs, | 
 | 	struct xfs_btree_block	*block) | 
 | { | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	union xfs_btree_ptr	leftsib; | 
 | 	union xfs_btree_ptr	rightsib; | 
 | 	int			level; | 
 | 	int			error = 0; | 
 |  | 
 | 	xfs_btree_get_sibling(cur, block, &leftsib, XFS_BB_LEFTSIB); | 
 | 	xfs_btree_get_sibling(cur, block, &rightsib, XFS_BB_RIGHTSIB); | 
 | 	level = xfs_btree_get_level(block); | 
 |  | 
 | 	/* Root block should never have siblings. */ | 
 | 	if (level == cur->bc_nlevels - 1) { | 
 | 		if (!xfs_btree_ptr_is_null(cur, &leftsib) || | 
 | 		    !xfs_btree_ptr_is_null(cur, &rightsib)) | 
 | 			xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Does the left & right sibling pointers match the adjacent | 
 | 	 * parent level pointers? | 
 | 	 * (These function absorbs error codes for us.) | 
 | 	 */ | 
 | 	error = xchk_btree_block_check_sibling(bs, level, -1, &leftsib); | 
 | 	if (error) | 
 | 		return error; | 
 | 	error = xchk_btree_block_check_sibling(bs, level, 1, &rightsib); | 
 | 	if (error) | 
 | 		return error; | 
 | out: | 
 | 	return error; | 
 | } | 
 |  | 
 | struct check_owner { | 
 | 	struct list_head	list; | 
 | 	xfs_daddr_t		daddr; | 
 | 	int			level; | 
 | }; | 
 |  | 
 | /* | 
 |  * Make sure this btree block isn't in the free list and that there's | 
 |  * an rmap record for it. | 
 |  */ | 
 | STATIC int | 
 | xchk_btree_check_block_owner( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	xfs_daddr_t		daddr) | 
 | { | 
 | 	xfs_agnumber_t		agno; | 
 | 	xfs_agblock_t		agbno; | 
 | 	xfs_btnum_t		btnum; | 
 | 	bool			init_sa; | 
 | 	int			error = 0; | 
 |  | 
 | 	if (!bs->cur) | 
 | 		return 0; | 
 |  | 
 | 	btnum = bs->cur->bc_btnum; | 
 | 	agno = xfs_daddr_to_agno(bs->cur->bc_mp, daddr); | 
 | 	agbno = xfs_daddr_to_agbno(bs->cur->bc_mp, daddr); | 
 |  | 
 | 	init_sa = bs->cur->bc_flags & XFS_BTREE_LONG_PTRS; | 
 | 	if (init_sa) { | 
 | 		error = xchk_ag_init(bs->sc, agno, &bs->sc->sa); | 
 | 		if (!xchk_btree_xref_process_error(bs->sc, bs->cur, | 
 | 				level, &error)) | 
 | 			return error; | 
 | 	} | 
 |  | 
 | 	xchk_xref_is_used_space(bs->sc, agbno, 1); | 
 | 	/* | 
 | 	 * The bnobt scrubber aliases bs->cur to bs->sc->sa.bno_cur, so we | 
 | 	 * have to nullify it (to shut down further block owner checks) if | 
 | 	 * self-xref encounters problems. | 
 | 	 */ | 
 | 	if (!bs->sc->sa.bno_cur && btnum == XFS_BTNUM_BNO) | 
 | 		bs->cur = NULL; | 
 |  | 
 | 	xchk_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) | 
 | 		xchk_ag_free(bs->sc, &bs->sc->sa); | 
 |  | 
 | 	return error; | 
 | } | 
 |  | 
 | /* Check the owner of a btree block. */ | 
 | STATIC int | 
 | xchk_btree_check_owner( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	struct xfs_buf		*bp) | 
 | { | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	struct check_owner	*co; | 
 |  | 
 | 	/* | 
 | 	 * In theory, xfs_btree_get_block should only give us a null buffer | 
 | 	 * pointer for the root of a root-in-inode btree type, but we need | 
 | 	 * to check defensively here in case the cursor state is also screwed | 
 | 	 * up. | 
 | 	 */ | 
 | 	if (bp == NULL) { | 
 | 		if (!(cur->bc_flags & XFS_BTREE_ROOT_IN_INODE)) | 
 | 			xchk_btree_set_corrupt(bs->sc, bs->cur, level); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * We want to cross-reference each btree block with the bnobt | 
 | 	 * and the rmapbt.  We cannot cross-reference the bnobt or | 
 | 	 * rmapbt while scanning the bnobt or rmapbt, respectively, | 
 | 	 * because we cannot alter the cursor and we'd prefer not to | 
 | 	 * duplicate cursors.  Therefore, save the buffer daddr for | 
 | 	 * later scanning. | 
 | 	 */ | 
 | 	if (cur->bc_btnum == XFS_BTNUM_BNO || cur->bc_btnum == XFS_BTNUM_RMAP) { | 
 | 		co = kmem_alloc(sizeof(struct check_owner), | 
 | 				KM_MAYFAIL); | 
 | 		if (!co) | 
 | 			return -ENOMEM; | 
 | 		co->level = level; | 
 | 		co->daddr = XFS_BUF_ADDR(bp); | 
 | 		list_add_tail(&co->list, &bs->to_check); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	return xchk_btree_check_block_owner(bs, level, XFS_BUF_ADDR(bp)); | 
 | } | 
 |  | 
 | /* | 
 |  * Check that this btree block has at least minrecs records or is one of the | 
 |  * special blocks that don't require that. | 
 |  */ | 
 | STATIC void | 
 | xchk_btree_check_minrecs( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	struct xfs_btree_block	*block) | 
 | { | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	unsigned int		root_level = cur->bc_nlevels - 1; | 
 | 	unsigned int		numrecs = be16_to_cpu(block->bb_numrecs); | 
 |  | 
 | 	/* More records than minrecs means the block is ok. */ | 
 | 	if (numrecs >= cur->bc_ops->get_minrecs(cur, level)) | 
 | 		return; | 
 |  | 
 | 	/* | 
 | 	 * For btrees rooted in the inode, it's possible that the root block | 
 | 	 * contents spilled into a regular ondisk block because there wasn't | 
 | 	 * enough space in the inode root.  The number of records in that | 
 | 	 * child block might be less than the standard minrecs, but that's ok | 
 | 	 * provided that there's only one direct child of the root. | 
 | 	 */ | 
 | 	if ((cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) && | 
 | 	    level == cur->bc_nlevels - 2) { | 
 | 		struct xfs_btree_block	*root_block; | 
 | 		struct xfs_buf		*root_bp; | 
 | 		int			root_maxrecs; | 
 |  | 
 | 		root_block = xfs_btree_get_block(cur, root_level, &root_bp); | 
 | 		root_maxrecs = cur->bc_ops->get_dmaxrecs(cur, root_level); | 
 | 		if (be16_to_cpu(root_block->bb_numrecs) != 1 || | 
 | 		    numrecs <= root_maxrecs) | 
 | 			xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Otherwise, only the root level is allowed to have fewer than minrecs | 
 | 	 * records or keyptrs. | 
 | 	 */ | 
 | 	if (level < root_level) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, level); | 
 | } | 
 |  | 
 | /* | 
 |  * Grab and scrub a btree block given a btree pointer.  Returns block | 
 |  * and buffer pointers (if applicable) if they're ok to use. | 
 |  */ | 
 | STATIC int | 
 | xchk_btree_get_block( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	union xfs_btree_ptr	*pp, | 
 | 	struct xfs_btree_block	**pblock, | 
 | 	struct xfs_buf		**pbp) | 
 | { | 
 | 	xfs_failaddr_t		failed_at; | 
 | 	int			error; | 
 |  | 
 | 	*pblock = NULL; | 
 | 	*pbp = NULL; | 
 |  | 
 | 	error = xfs_btree_lookup_get_block(bs->cur, level, pp, pblock); | 
 | 	if (!xchk_btree_process_error(bs->sc, bs->cur, level, &error) || | 
 | 	    !*pblock) | 
 | 		return error; | 
 |  | 
 | 	xfs_btree_get_block(bs->cur, level, pbp); | 
 | 	if (bs->cur->bc_flags & XFS_BTREE_LONG_PTRS) | 
 | 		failed_at = __xfs_btree_check_lblock(bs->cur, *pblock, | 
 | 				level, *pbp); | 
 | 	else | 
 | 		failed_at = __xfs_btree_check_sblock(bs->cur, *pblock, | 
 | 				 level, *pbp); | 
 | 	if (failed_at) { | 
 | 		xchk_btree_set_corrupt(bs->sc, bs->cur, level); | 
 | 		return 0; | 
 | 	} | 
 | 	if (*pbp) | 
 | 		xchk_buffer_recheck(bs->sc, *pbp); | 
 |  | 
 | 	xchk_btree_check_minrecs(bs, level, *pblock); | 
 |  | 
 | 	/* | 
 | 	 * Check the block's owner; this function absorbs error codes | 
 | 	 * for us. | 
 | 	 */ | 
 | 	error = xchk_btree_check_owner(bs, level, *pbp); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	/* | 
 | 	 * Check the block's siblings; this function absorbs error codes | 
 | 	 * for us. | 
 | 	 */ | 
 | 	return xchk_btree_block_check_siblings(bs, *pblock); | 
 | } | 
 |  | 
 | /* | 
 |  * Check that the low and high keys of this block match the keys stored | 
 |  * in the parent block. | 
 |  */ | 
 | STATIC void | 
 | xchk_btree_block_keys( | 
 | 	struct xchk_btree	*bs, | 
 | 	int			level, | 
 | 	struct xfs_btree_block	*block) | 
 | { | 
 | 	union xfs_btree_key	block_keys; | 
 | 	struct xfs_btree_cur	*cur = bs->cur; | 
 | 	union xfs_btree_key	*high_bk; | 
 | 	union xfs_btree_key	*parent_keys; | 
 | 	union xfs_btree_key	*high_pk; | 
 | 	struct xfs_btree_block	*parent_block; | 
 | 	struct xfs_buf		*bp; | 
 |  | 
 | 	if (level >= cur->bc_nlevels - 1) | 
 | 		return; | 
 |  | 
 | 	/* Calculate the keys for this block. */ | 
 | 	xfs_btree_get_keys(cur, block, &block_keys); | 
 |  | 
 | 	/* Obtain the parent's copy of the keys for this block. */ | 
 | 	parent_block = xfs_btree_get_block(cur, level + 1, &bp); | 
 | 	parent_keys = xfs_btree_key_addr(cur, cur->bc_ptrs[level + 1], | 
 | 			parent_block); | 
 |  | 
 | 	if (cur->bc_ops->diff_two_keys(cur, &block_keys, parent_keys) != 0) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, 1); | 
 |  | 
 | 	if (!(cur->bc_flags & XFS_BTREE_OVERLAPPING)) | 
 | 		return; | 
 |  | 
 | 	/* Get high keys */ | 
 | 	high_bk = xfs_btree_high_key_from_key(cur, &block_keys); | 
 | 	high_pk = xfs_btree_high_key_addr(cur, cur->bc_ptrs[level + 1], | 
 | 			parent_block); | 
 |  | 
 | 	if (cur->bc_ops->diff_two_keys(cur, high_bk, high_pk) != 0) | 
 | 		xchk_btree_set_corrupt(bs->sc, cur, 1); | 
 | } | 
 |  | 
 | /* | 
 |  * Visit all nodes and leaves of a btree.  Check that all pointers and | 
 |  * records are in order, that the keys reflect the records, and use a callback | 
 |  * so that the caller can verify individual records. | 
 |  */ | 
 | int | 
 | xchk_btree( | 
 | 	struct xfs_scrub		*sc, | 
 | 	struct xfs_btree_cur		*cur, | 
 | 	xchk_btree_rec_fn		scrub_fn, | 
 | 	const struct xfs_owner_info	*oinfo, | 
 | 	void				*private) | 
 | { | 
 | 	struct xchk_btree		bs = { | 
 | 		.cur			= cur, | 
 | 		.scrub_rec		= scrub_fn, | 
 | 		.oinfo			= oinfo, | 
 | 		.firstrec		= true, | 
 | 		.private		= private, | 
 | 		.sc			= sc, | 
 | 	}; | 
 | 	union xfs_btree_ptr		ptr; | 
 | 	union xfs_btree_ptr		*pp; | 
 | 	union xfs_btree_rec		*recp; | 
 | 	struct xfs_btree_block		*block; | 
 | 	int				level; | 
 | 	struct xfs_buf			*bp; | 
 | 	struct check_owner		*co; | 
 | 	struct check_owner		*n; | 
 | 	int				i; | 
 | 	int				error = 0; | 
 |  | 
 | 	/* Initialize scrub state */ | 
 | 	for (i = 0; i < XFS_BTREE_MAXLEVELS; i++) | 
 | 		bs.firstkey[i] = true; | 
 | 	INIT_LIST_HEAD(&bs.to_check); | 
 |  | 
 | 	/* Don't try to check a tree with a height we can't handle. */ | 
 | 	if (cur->bc_nlevels > XFS_BTREE_MAXLEVELS) { | 
 | 		xchk_btree_set_corrupt(sc, cur, 0); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Load the root of the btree.  The helper function absorbs | 
 | 	 * error codes for us. | 
 | 	 */ | 
 | 	level = cur->bc_nlevels - 1; | 
 | 	cur->bc_ops->init_ptr_from_cur(cur, &ptr); | 
 | 	if (!xchk_btree_ptr_ok(&bs, cur->bc_nlevels, &ptr)) | 
 | 		goto out; | 
 | 	error = xchk_btree_get_block(&bs, level, &ptr, &block, &bp); | 
 | 	if (error || !block) | 
 | 		goto out; | 
 |  | 
 | 	cur->bc_ptrs[level] = 1; | 
 |  | 
 | 	while (level < cur->bc_nlevels) { | 
 | 		block = xfs_btree_get_block(cur, level, &bp); | 
 |  | 
 | 		if (level == 0) { | 
 | 			/* End of leaf, pop back towards the root. */ | 
 | 			if (cur->bc_ptrs[level] > | 
 | 			    be16_to_cpu(block->bb_numrecs)) { | 
 | 				xchk_btree_block_keys(&bs, level, block); | 
 | 				if (level < cur->bc_nlevels - 1) | 
 | 					cur->bc_ptrs[level + 1]++; | 
 | 				level++; | 
 | 				continue; | 
 | 			} | 
 |  | 
 | 			/* Records in order for scrub? */ | 
 | 			xchk_btree_rec(&bs); | 
 |  | 
 | 			/* Call out to the record checker. */ | 
 | 			recp = xfs_btree_rec_addr(cur, cur->bc_ptrs[0], block); | 
 | 			error = bs.scrub_rec(&bs, recp); | 
 | 			if (error) | 
 | 				break; | 
 | 			if (xchk_should_terminate(sc, &error) || | 
 | 			    (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) | 
 | 				break; | 
 |  | 
 | 			cur->bc_ptrs[level]++; | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		/* End of node, pop back towards the root. */ | 
 | 		if (cur->bc_ptrs[level] > be16_to_cpu(block->bb_numrecs)) { | 
 | 			xchk_btree_block_keys(&bs, level, block); | 
 | 			if (level < cur->bc_nlevels - 1) | 
 | 				cur->bc_ptrs[level + 1]++; | 
 | 			level++; | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		/* Keys in order for scrub? */ | 
 | 		xchk_btree_key(&bs, level); | 
 |  | 
 | 		/* Drill another level deeper. */ | 
 | 		pp = xfs_btree_ptr_addr(cur, cur->bc_ptrs[level], block); | 
 | 		if (!xchk_btree_ptr_ok(&bs, level, pp)) { | 
 | 			cur->bc_ptrs[level]++; | 
 | 			continue; | 
 | 		} | 
 | 		level--; | 
 | 		error = xchk_btree_get_block(&bs, level, pp, &block, &bp); | 
 | 		if (error || !block) | 
 | 			goto out; | 
 |  | 
 | 		cur->bc_ptrs[level] = 1; | 
 | 	} | 
 |  | 
 | out: | 
 | 	/* Process deferred owner checks on btree blocks. */ | 
 | 	list_for_each_entry_safe(co, n, &bs.to_check, list) { | 
 | 		if (!error && bs.cur) | 
 | 			error = xchk_btree_check_block_owner(&bs, | 
 | 					co->level, co->daddr); | 
 | 		list_del(&co->list); | 
 | 		kmem_free(co); | 
 | 	} | 
 |  | 
 | 	return error; | 
 | } |