[PATCH 113/119] xfs: scrub btree records and pointers while querying

Darrick J. Wong
2016-06-17T01:30:13Z

Create a function that walks a btree, checking the integrity of each
btree block (headers, keys, records) and calling back to the caller
to perform further checks on the records.

v2: Prefix function names with xfs_

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
fs/xfs/Makefile | 1
fs/xfs/libxfs/xfs_alloc.c | 33 +++
fs/xfs/libxfs/xfs_alloc.h | 3
fs/xfs/libxfs/xfs_btree.c | 12 +
fs/xfs/libxfs/xfs_btree.h | 15 +-
fs/xfs/libxfs/xfs_format.h | 2
fs/xfs/libxfs/xfs_rmap.c | 39 ++++
fs/xfs/libxfs/xfs_rmap_btree.h | 3
fs/xfs/libxfs/xfs_scrub.c | 396 ++++++++++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_scrub.h | 76 ++++++++
10 files changed, 571 insertions(+), 9 deletions(-)
create mode 100644 fs/xfs/libxfs/xfs_scrub.c
create mode 100644 fs/xfs/libxfs/xfs_scrub.h

diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 56c384b..8942390 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -58,6 +58,7 @@ xfs-y += $(addprefix libxfs/, \
xfs_refcount.o \
xfs_refcount_btree.o \
xfs_sb.o \
+ xfs_scrub.o \
xfs_symlink_remote.o \
xfs_trans_resv.o \
)
diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c
index 188c359a..6fc1981 100644
--- a/fs/xfs/libxfs/xfs_alloc.c
+++ b/fs/xfs/libxfs/xfs_alloc.c
@@ -2924,3 +2924,36 @@ err:
xfs_trans_brelse(tp, agbp);
return error;
}
+
+/* Is there a record covering a given extent? */
+int
+xfs_alloc_record_exists(
+ struct xfs_btree_cur *cur,
+ xfs_agblock_t bno,
+ xfs_extlen_t len,
+ bool *is_freesp)
+{
+ int stat;
+ xfs_agblock_t fbno;
+ xfs_extlen_t flen;
+ int error;
+
+ error = xfs_alloc_lookup_le(cur, bno, len, &stat);
+ if (error)
+ return error;
+ if (!stat) {
+ *is_freesp = false;
+ return 0;
+ }
+
+ error = xfs_alloc_get_rec(cur, &fbno, &flen, &stat);
+ if (error)
+ return error;
+ if (!stat) {
+ *is_freesp = false;
+ return 0;
+ }
+
+ *is_freesp = (fbno <= bno && fbno + flen >= bno + len);
+ return 0;
+}
diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h
index 9f6373a4..4f2ce38 100644
--- a/fs/xfs/libxfs/xfs_alloc.h
+++ b/fs/xfs/libxfs/xfs_alloc.h
@@ -210,4 +210,7 @@ int xfs_free_extent_fix_freelist(struct xfs_trans *tp, xfs_agnumber_t agno,

xfs_extlen_t xfs_prealloc_blocks(struct xfs_mount *mp);

+int xfs_alloc_record_exists(struct xfs_btree_cur *cur, xfs_agblock_t bno,
+ xfs_extlen_t len, bool *is_freesp);
+
#endif /* __XFS_ALLOC_H__ */
diff --git a/fs/xfs/libxfs/xfs_btree.c b/fs/xfs/libxfs/xfs_btree.c
index 9c84184..5260085 100644
--- a/fs/xfs/libxfs/xfs_btree.c
+++ b/fs/xfs/libxfs/xfs_btree.c
@@ -549,7 +549,7 @@ xfs_btree_ptr_offset(
/*
* Return a pointer to the n-th record in the btree block.
*/
-STATIC union xfs_btree_rec *
+union xfs_btree_rec *
xfs_btree_rec_addr(
struct xfs_btree_cur *cur,
int n,
@@ -562,7 +562,7 @@ xfs_btree_rec_addr(
/*
* Return a pointer to the n-th key in the btree block.
*/
-STATIC union xfs_btree_key *
+union xfs_btree_key *
xfs_btree_key_addr(
struct xfs_btree_cur *cur,
int n,
@@ -575,7 +575,7 @@ xfs_btree_key_addr(
/*
* Return a pointer to the n-th high key in the btree block.
*/
-STATIC union xfs_btree_key *
+union xfs_btree_key *
xfs_btree_high_key_addr(
struct xfs_btree_cur *cur,
int n,
@@ -588,7 +588,7 @@ xfs_btree_high_key_addr(
/*
* Return a pointer to the n-th block pointer in the btree block.
*/
-STATIC union xfs_btree_ptr *
+union xfs_btree_ptr *
xfs_btree_ptr_addr(
struct xfs_btree_cur *cur,
int n,
@@ -622,7 +622,7 @@ xfs_btree_get_iroot(
* Retrieve the block pointer from the cursor at the given level.
* This may be an inode btree root or from a buffer.
*/
-STATIC struct xfs_btree_block * /* generic btree block pointer */
+struct xfs_btree_block * /* generic btree block pointer */
xfs_btree_get_block(
struct xfs_btree_cur *cur, /* btree cursor */
int level, /* level in btree */
@@ -1733,7 +1733,7 @@ error0:
return error;
}

-STATIC int
+int
xfs_btree_lookup_get_block(
struct xfs_btree_cur *cur, /* btree cursor */
int level, /* level in the btree */
diff --git a/fs/xfs/libxfs/xfs_btree.h b/fs/xfs/libxfs/xfs_btree.h
index dbf299f..6f22cb0 100644
--- a/fs/xfs/libxfs/xfs_btree.h
+++ b/fs/xfs/libxfs/xfs_btree.h
@@ -194,7 +194,6 @@ struct xfs_btree_ops {

const struct xfs_buf_ops *buf_ops;

-#if defined(DEBUG) || defined(XFS_WARN)
/* check that k1 is lower than k2 */
int (*keys_inorder)(struct xfs_btree_cur *cur,
union xfs_btree_key *k1,
@@ -204,7 +203,6 @@ struct xfs_btree_ops {
int (*recs_inorder)(struct xfs_btree_cur *cur,
union xfs_btree_rec *r1,
union xfs_btree_rec *r2);
-#endif
};

/* btree ops flags */
@@ -537,4 +535,17 @@ int xfs_btree_visit_blocks(struct xfs_btree_cur *cur,

int xfs_btree_count_blocks(struct xfs_btree_cur *cur, xfs_extlen_t *blocks);

+union xfs_btree_rec *xfs_btree_rec_addr(struct xfs_btree_cur *cur, int n,
+ struct xfs_btree_block *block);
+union xfs_btree_key *xfs_btree_key_addr(struct xfs_btree_cur *cur, int n,
+ struct xfs_btree_block *block);
+union xfs_btree_key *xfs_btree_high_key_addr(struct xfs_btree_cur *cur, int n,
+ struct xfs_btree_block *block);
+union xfs_btree_ptr *xfs_btree_ptr_addr(struct xfs_btree_cur *cur, int n,
+ struct xfs_btree_block *block);
+int xfs_btree_lookup_get_block(struct xfs_btree_cur *cur, int level,
+ union xfs_btree_ptr *pp, struct xfs_btree_block **blkp);
+struct xfs_btree_block *xfs_btree_get_block(struct xfs_btree_cur *cur,
+ int level, struct xfs_buf **bpp);
+
#endif /* __XFS_BTREE_H__ */
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index 211a8b5..6ea8a84 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -518,7 +518,7 @@ static inline int xfs_sb_version_hasftype(struct xfs_sb *sbp)
(sbp->sb_features2 & XFS_SB_VERSION2_FTYPE));
}

-static inline int xfs_sb_version_hasfinobt(xfs_sb_t *sbp)
+static inline bool xfs_sb_version_hasfinobt(xfs_sb_t *sbp)
{
return (XFS_SB_VERSION_NUM(sbp) == XFS_SB_VERSION_5) &&
(sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_FINOBT);
diff --git a/fs/xfs/libxfs/xfs_rmap.c b/fs/xfs/libxfs/xfs_rmap.c
index 29d08fc..e7673ec 100644
--- a/fs/xfs/libxfs/xfs_rmap.c
+++ b/fs/xfs/libxfs/xfs_rmap.c
@@ -2328,3 +2328,42 @@ xfs_rmap_free_defer(

return __xfs_rmap_add(mp, dfops, &ri);
}
+
+/* Is there a record covering a given extent? */
+int
+xfs_rmap_record_exists(
+ struct xfs_btree_cur *cur,
+ xfs_agblock_t bno,
+ xfs_extlen_t len,
+ struct xfs_owner_info *oinfo,
+ bool *has_rmap)
+{
+ uint64_t owner;
+ uint64_t offset;
+ unsigned int flags;
+ int stat;
+ struct xfs_rmap_irec irec;
+ int error;
+
+ xfs_owner_info_unpack(oinfo, &owner, &offset, &flags);
+
+ error = xfs_rmap_lookup_le(cur, bno, len, owner, offset, flags, &stat);
+ if (error)
+ return error;
+ if (!stat) {
+ *has_rmap = false;
+ return 0;
+ }
+
+ error = xfs_rmap_get_rec(cur, &irec, &stat);
+ if (error)
+ return error;
+ if (!stat) {
+ *has_rmap = false;
+ return 0;
+ }
+
+ *has_rmap = (irec.rm_startblock <= bno &&
+ irec.rm_startblock + irec.rm_blockcount >= bno + len);
+ return 0;
+}
diff --git a/fs/xfs/libxfs/xfs_rmap_btree.h b/fs/xfs/libxfs/xfs_rmap_btree.h
index 5baa81f..2f072c8 100644
--- a/fs/xfs/libxfs/xfs_rmap_btree.h
+++ b/fs/xfs/libxfs/xfs_rmap_btree.h
@@ -144,4 +144,7 @@ extern xfs_extlen_t xfs_rmapbt_max_size(struct xfs_mount *mp);
extern int xfs_rmapbt_calc_reserves(struct xfs_mount *mp,
xfs_agnumber_t agno, xfs_extlen_t *ask, xfs_extlen_t *used);

+extern int xfs_rmap_record_exists(struct xfs_btree_cur *cur, xfs_agblock_t bno,
+ xfs_extlen_t len, struct xfs_owner_info *oinfo, bool *has_rmap);
+
#endif /* __XFS_RMAP_BTREE_H__ */
diff --git a/fs/xfs/libxfs/xfs_scrub.c b/fs/xfs/libxfs/xfs_scrub.c
new file mode 100644
index 0000000..d43d5c5
--- /dev/null
+++ b/fs/xfs/libxfs/xfs_scrub.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2016 Oracle. All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#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_defer.h"
+#include "xfs_btree.h"
+#include "xfs_bit.h"
+#include "xfs_alloc.h"
+#include "xfs_bmap.h"
+#include "xfs_ialloc.h"
+#include "xfs_refcount.h"
+#include "xfs_alloc_btree.h"
+#include "xfs_rmap_btree.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_scrub.h"
+
+static const char * const btree_types[] = {
+ [XFS_BTNUM_BNO] = "bnobt",
+ [XFS_BTNUM_CNT] = "cntbt",
+ [XFS_BTNUM_RMAP] = "rmapbt",
+ [XFS_BTNUM_BMAP] = "bmapbt",
+ [XFS_BTNUM_INO] = "inobt",
+ [XFS_BTNUM_FINO] = "finobt",
+ [XFS_BTNUM_REFC] = "refcountbt",
+};
+
+/* Report a scrub corruption in dmesg. */
+void
+xfs_btree_scrub_error(
+ struct xfs_btree_cur *cur,
+ int level,
+ const char *file,
+ int line,
+ const char *check)
+{
+ char buf[16];
+ xfs_fsblock_t fsbno;
+
+ if (cur->bc_ptrs[level] >= 1)
+ snprintf(buf, 16, " ptr %d", cur->bc_ptrs[level]);
+ else
+ buf[0] = 0;
+
+ fsbno = XFS_DADDR_TO_FSB(cur->bc_mp, cur->bc_bufs[level]->b_bn);
+ xfs_alert(cur->bc_mp, "scrub: %s btree corruption in block %u/%u%s: %s, file: %s, line: %d",
+ btree_types[cur->bc_btnum],
+ XFS_FSB_TO_AGNO(cur->bc_mp, fsbno),
+ XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno),
+ buf, check, file, line);
+}
+
+/* AG metadata scrubbing */
+
+/*
+ * Make sure this record is in order and doesn't stray outside of the parent
+ * keys.
+ */
+static int
+xfs_btree_scrub_rec(
+ struct xfs_btree_scrub *bs)
+{
+ struct xfs_btree_cur *cur = bs->cur;
+ union xfs_btree_rec *rec;
+ union xfs_btree_key key;
+ union xfs_btree_key *keyp;
+ struct xfs_btree_block *block;
+ struct xfs_btree_block *keyblock;
+
+ block = XFS_BUF_TO_BLOCK(cur->bc_bufs[0]);
+ rec = xfs_btree_rec_addr(cur, cur->bc_ptrs[0], block);
+
+ /* If this isn't the first record, are they in order? */
+ XFS_BTREC_SCRUB_CHECK(bs, bs->firstrec ||
+ cur->bc_ops->recs_inorder(cur, &bs->lastrec, rec));
+ bs->firstrec = false;
+ bs->lastrec = *rec;
+
+ if (cur->bc_nlevels == 1)
+ return 0;
+
+ /* Is this at least as large as the parent low key? */
+ cur->bc_ops->init_key_from_rec(&key, rec);
+ keyblock = XFS_BUF_TO_BLOCK(cur->bc_bufs[1]);
+ keyp = xfs_btree_key_addr(cur, cur->bc_ptrs[1], keyblock);
+
+ XFS_BTKEY_SCRUB_CHECK(bs, 0,
+ cur->bc_ops->diff_two_keys(cur, keyp, &key) >= 0);
+
+ if (!(cur->bc_ops->flags & XFS_BTREE_OPS_OVERLAPPING))
+ return 0;
+
+ /* Is this no larger than the parent high key? */
+ keyp = xfs_btree_high_key_addr(cur, cur->bc_ptrs[1], keyblock);
+
+ XFS_BTKEY_SCRUB_CHECK(bs, 0,
+ cur->bc_ops->diff_two_keys(cur, &key, keyp) >= 0);
+
+ return 0;
+}
+
+/*
+ * Make sure this key is in order and doesn't stray outside of the parent
+ * keys.
+ */
+static int
+xfs_btree_scrub_key(
+ struct xfs_btree_scrub *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;
+
+ block = XFS_BUF_TO_BLOCK(cur->bc_bufs[level]);
+ key = xfs_btree_key_addr(cur, cur->bc_ptrs[level], block);
+
+ /* If this isn't the first key, are they in order? */
+ XFS_BTKEY_SCRUB_CHECK(bs, level, bs->firstkey[level] ||
+ cur->bc_ops->keys_inorder(cur, &bs->lastkey[level],
+ key));
+ bs->firstkey[level] = false;
+ bs->lastkey[level] = *key;
+
+ if (level + 1 >= cur->bc_nlevels)
+ return 0;
+
+ /* Is this at least as large as the parent low key? */
+ keyblock = XFS_BUF_TO_BLOCK(cur->bc_bufs[level + 1]);
+ keyp = xfs_btree_key_addr(cur, cur->bc_ptrs[level + 1], keyblock);
+
+ XFS_BTKEY_SCRUB_CHECK(bs, level,
+ cur->bc_ops->diff_two_keys(cur, keyp, key) >= 0);
+
+ if (!(cur->bc_ops->flags & XFS_BTREE_OPS_OVERLAPPING))
+ return 0;
+
+ /* 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);
+
+ XFS_BTKEY_SCRUB_CHECK(bs, level,
+ cur->bc_ops->diff_two_keys(cur, key, keyp) >= 0);
+
+ return 0;
+}
+
+struct check_owner {
+ struct list_head list;
+ xfs_agblock_t bno;
+};
+
+/*
+ * Make sure this btree block isn't in the free list and that there's
+ * an rmap record for it.
+ */
+static int
+xfs_btree_block_check_owner(
+ struct xfs_btree_scrub *bs,
+ xfs_agblock_t bno)
+{
+ bool has_rmap;
+ bool is_freesp;
+ int error;
+
+ /* Check that this block isn't free */
+ error = xfs_alloc_record_exists(bs->bno_cur, bno, 1, &is_freesp);
+ if (error)
+ goto err;
+ XFS_BTREC_SCRUB_CHECK(bs, !is_freesp);
+
+ if (!bs->rmap_cur)
+ return 0;
+
+ /* Check that there's an rmap record for this */
+ error = xfs_rmap_record_exists(bs->rmap_cur, bno, 1, &bs->oinfo,
+ &has_rmap);
+ if (error)
+ goto err;
+ XFS_BTREC_SCRUB_CHECK(bs, has_rmap);
+err:
+ return error;
+}
+
+/* Check the owner of a btree block. */
+static int
+xfs_btree_scrub_check_owner(
+ struct xfs_btree_scrub *bs,
+ struct xfs_buf *bp)
+{
+ struct xfs_btree_cur *cur = bs->cur;
+ xfs_agblock_t bno;
+ xfs_fsblock_t fsbno;
+ struct check_owner *co;
+
+ fsbno = XFS_DADDR_TO_FSB(cur->bc_mp, bp->b_bn);
+ bno = XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno);
+
+ /* Do we need to defer this one? */
+ if ((!bs->rmap_cur && xfs_sb_version_hasrmapbt(&cur->bc_mp->m_sb)) ||
+ !bs->bno_cur) {
+ co = kmem_alloc(sizeof(struct check_owner), KM_SLEEP | KM_NOFS);
+ co->bno = bno;
+ list_add_tail(&co->list, &bs->to_check);
+ return 0;
+ }
+
+ return xfs_btree_block_check_owner(bs, bno);
+}
+
+/*
+ * 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. The callback is the same
+ * as the one for xfs_btree_query_range, so therefore this function also
+ * returns XFS_BTREE_QUERY_RANGE_ABORT, zero, or a negative error code.
+ */
+int
+xfs_btree_scrub(
+ struct xfs_btree_scrub *bs)
+{
+ struct xfs_btree_cur *cur = bs->cur;
+ 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;
+ int i;
+ struct check_owner *co, *n;
+ int error;
+
+ /* Finish filling out the scrub state */
+ bs->error = 0;
+ bs->firstrec = true;
+ for (i = 0; i < XFS_BTREE_MAXLEVELS; i++)
+ bs->firstkey[i] = true;
+ bs->bno_cur = bs->rmap_cur = NULL;
+ INIT_LIST_HEAD(&bs->to_check);
+ if (bs->cur->bc_btnum != XFS_BTNUM_BNO)
+ bs->bno_cur = xfs_allocbt_init_cursor(cur->bc_mp, NULL,
+ bs->agf_bp, bs->cur->bc_private.a.agno,
+ XFS_BTNUM_BNO);
+ if (bs->cur->bc_btnum != XFS_BTNUM_RMAP &&
+ xfs_sb_version_hasrmapbt(&cur->bc_mp->m_sb))
+ bs->rmap_cur = xfs_rmapbt_init_cursor(cur->bc_mp, NULL,
+ bs->agf_bp, bs->cur->bc_private.a.agno);
+
+ /* Load the root of the btree. */
+ level = cur->bc_nlevels - 1;
+ cur->bc_ops->init_ptr_from_cur(cur, &ptr);
+ error = xfs_btree_lookup_get_block(cur, level, &ptr, &block);
+ if (error)
+ goto out;
+
+ xfs_btree_get_block(cur, level, &bp);
+ error = xfs_btree_check_block(cur, block, level, bp);
+ if (error)
+ goto out;
+ error = xfs_btree_scrub_check_owner(bs, bp);
+ if (error)
+ goto out;
+
+ cur->bc_ptrs[level] = 1;
+
+ while (level < cur->bc_nlevels) {
+ block = XFS_BUF_TO_BLOCK(cur->bc_bufs[level]);
+
+ if (level == 0) {
+ /* End of leaf, pop back towards the root. */
+ if (cur->bc_ptrs[level] >
+ be16_to_cpu(block->bb_numrecs)) {
+ if (level < cur->bc_nlevels - 1)
+ cur->bc_ptrs[level + 1]++;
+ level++;
+ continue;
+ }
+
+ /* Records in order for scrub? */
+ error = xfs_btree_scrub_rec(bs);
+ if (error)
+ goto out;
+
+ recp = xfs_btree_rec_addr(cur, cur->bc_ptrs[0], block);
+ error = bs->scrub_rec(bs, recp);
+ if (error < 0 ||
+ error == XFS_BTREE_QUERY_RANGE_ABORT)
+ 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)) {
+ if (level < cur->bc_nlevels - 1)
+ cur->bc_ptrs[level + 1]++;
+ level++;
+ continue;
+ }
+
+ /* Keys in order for scrub? */
+ error = xfs_btree_scrub_key(bs, level);
+ if (error)
+ goto out;
+
+ /* Drill another level deeper. */
+ pp = xfs_btree_ptr_addr(cur, cur->bc_ptrs[level], block);
+ level--;
+ error = xfs_btree_lookup_get_block(cur, level, pp,
+ &block);
+ if (error)
+ goto out;
+
+ xfs_btree_get_block(cur, level, &bp);
+ error = xfs_btree_check_block(cur, block, level, bp);
+ if (error)
+ goto out;
+
+ error = xfs_btree_scrub_check_owner(bs, bp);
+ if (error)
+ goto out;
+
+ cur->bc_ptrs[level] = 1;
+ }
+
+out:
+ /*
+ * If we don't end this function with the cursor pointing at a record
+ * block, a subsequent non-error cursor deletion will not release
+ * node-level buffers, causing a buffer leak. This is quite possible
+ * with a zero-results range query, so release the buffers if we
+ * failed to return any results.
+ */
+ if (cur->bc_bufs[0] == NULL) {
+ for (i = 0; i < cur->bc_nlevels; i++) {
+ if (cur->bc_bufs[i]) {
+ xfs_trans_brelse(cur->bc_tp, cur->bc_bufs[i]);
+ cur->bc_bufs[i] = NULL;
+ cur->bc_ptrs[i] = 0;
+ cur->bc_ra[i] = 0;
+ }
+ }
+ }
+
+ /* Check the deferred stuff */
+ if (!error) {
+ if (bs->cur->bc_btnum == XFS_BTNUM_BNO)
+ bs->bno_cur = bs->cur;
+ else if (bs->cur->bc_btnum == XFS_BTNUM_RMAP)
+ bs->rmap_cur = bs->cur;
+ list_for_each_entry(co, &bs->to_check, list) {
+ error = xfs_btree_block_check_owner(bs, co->bno);
+ if (error)
+ break;
+ }
+ }
+ list_for_each_entry_safe(co, n, &bs->to_check, list) {
+ list_del(&co->list);
+ kmem_free(co);
+ }
+
+ if (bs->bno_cur && bs->bno_cur != bs->cur)
+ xfs_btree_del_cursor(bs->bno_cur, XFS_BTREE_ERROR);
+ if (bs->rmap_cur && bs->rmap_cur != bs->cur)
+ xfs_btree_del_cursor(bs->rmap_cur, XFS_BTREE_ERROR);
+
+ if (error || bs->error)
+ xfs_alert(cur->bc_mp,
+ "Corruption detected. Unmount and run xfs_repair.");
+
+ return error;
+}
diff --git a/fs/xfs/libxfs/xfs_scrub.h b/fs/xfs/libxfs/xfs_scrub.h
new file mode 100644
index 0000000..af80a9d
--- /dev/null
+++ b/fs/xfs/libxfs/xfs_scrub.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 Oracle. All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef __XFS_SCRUB_H__
+#define __XFS_SCRUB_H__
+
+/* btree scrub */
+struct xfs_btree_scrub;
+
+typedef int (*xfs_btree_scrub_rec_fn)(
+ struct xfs_btree_scrub *bs,
+ union xfs_btree_rec *rec);
+
+struct xfs_btree_scrub {
+ /* caller-provided scrub state */
+ struct xfs_btree_cur *cur;
+ xfs_btree_scrub_rec_fn scrub_rec;
+ struct xfs_buf *agi_bp;
+ struct xfs_buf *agf_bp;
+ struct xfs_buf *agfl_bp;
+ struct xfs_owner_info oinfo;
+
+ /* internal scrub state */
+ union xfs_btree_rec lastrec;
+ bool firstrec;
+ union xfs_btree_key lastkey[XFS_BTREE_MAXLEVELS];
+ bool firstkey[XFS_BTREE_MAXLEVELS];
+ struct xfs_btree_cur *rmap_cur;
+ struct xfs_btree_cur *bno_cur;
+ struct list_head to_check;
+ int error;
+};
+
+int xfs_btree_scrub(struct xfs_btree_scrub *bs);
+void xfs_btree_scrub_error(struct xfs_btree_cur *cur, int level,
+ const char *file, int line, const char *check);
+#define XFS_BTREC_SCRUB_CHECK(bs, fs_ok) \
+ if (!(fs_ok)) { \
+ xfs_btree_scrub_error((bs)->cur, 0, __FILE__, __LINE__, #fs_ok); \
+ (bs)->error = -EFSCORRUPTED; \
+ }
+#define XFS_BTREC_SCRUB_GOTO(bs, fs_ok, label) \
+ if (!(fs_ok)) { \
+ xfs_btree_scrub_error((bs)->cur, 0, __FILE__, __LINE__, #fs_ok); \
+ (bs)->error = -EFSCORRUPTED; \
+ goto label; \
+ }
+#define XFS_BTKEY_SCRUB_CHECK(bs, level, fs_ok) \
+ if (!(fs_ok)) { \
+ xfs_btree_scrub_error((bs)->cur, (level), __FILE__, __LINE__, #fs_ok); \
+ (bs)->error = -EFSCORRUPTED; \
+ }
+#define XFS_BTKEY_SCRUB_GOTO(bs, level, fs_ok, label) \
+ if (!(fs_ok)) { \
+ xfs_btree_scrub_error((bs)->cur, 0, __FILE__, __LINE__, #fs_ok); \
+ (bs)->error = -EFSCORRUPTED; \
+ goto label; \
+ }
+
+#endif /* __XFS_SCRUB_H__ */