diff mbox

[23/25] xfs: cross-reference refcount btree during scrub

Message ID 147216856568.3108.7611118372039280000.stgit@birch.djwong.org
State Superseded, archived
Headers show

Commit Message

Darrick J. Wong Aug. 25, 2016, 11:42 p.m. UTC
During metadata btree scrub, we should cross-reference with the
reference counts.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 fs/xfs/libxfs/xfs_refcount.c |   19 ++++
 fs/xfs/libxfs/xfs_refcount.h |    3 +
 fs/xfs/xfs_scrub.c           |  184 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 206 insertions(+)
diff mbox

Patch

diff --git a/fs/xfs/libxfs/xfs_refcount.c b/fs/xfs/libxfs/xfs_refcount.c
index 9136745..af82ea3 100644
--- a/fs/xfs/libxfs/xfs_refcount.c
+++ b/fs/xfs/libxfs/xfs_refcount.c
@@ -1535,3 +1535,22 @@  xfs_refcount_free_cow_extent(
 	return __xfs_refcount_add(mp, dfops, XFS_REFCOUNT_FREE_COW,
 			fsb, len);
 }
+
+/* Is there a record covering a given extent? */
+int
+xfs_refcount_has_record(
+	struct xfs_btree_cur	*cur,
+	xfs_agblock_t		bno,
+	xfs_extlen_t		len,
+	bool			*exists)
+{
+	union xfs_btree_irec	low;
+	union xfs_btree_irec	high;
+
+	memset(&low, 0, sizeof(low));
+	low.rc.rc_startblock = bno;
+	memset(&high, 0xFF, sizeof(high));
+	high.rc.rc_startblock = bno + len - 1;
+
+	return xfs_btree_has_record(cur, &low, &high, exists);
+}
diff --git a/fs/xfs/libxfs/xfs_refcount.h b/fs/xfs/libxfs/xfs_refcount.h
index 105c246..a00400f 100644
--- a/fs/xfs/libxfs/xfs_refcount.h
+++ b/fs/xfs/libxfs/xfs_refcount.h
@@ -64,4 +64,7 @@  extern int xfs_refcount_free_cow_extent(struct xfs_mount *mp,
 		struct xfs_defer_ops *dfops, xfs_fsblock_t fsb,
 		xfs_extlen_t len);
 
+extern int xfs_refcount_has_record(struct xfs_btree_cur *cur,
+		xfs_agblock_t bno, xfs_extlen_t len, bool *exists);
+
 #endif	/* __XFS_REFCOUNT_H__ */
diff --git a/fs/xfs/xfs_scrub.c b/fs/xfs/xfs_scrub.c
index 34c23f7..ff55d8c 100644
--- a/fs/xfs/xfs_scrub.c
+++ b/fs/xfs/xfs_scrub.c
@@ -867,6 +867,7 @@  xfs_scrub_sb(
 	bool				is_freesp;
 	bool				has_inodes;
 	bool				has_rmap;
+	bool				has_refcount;
 	int				error;
 	int				err2;
 
@@ -973,6 +974,17 @@  btree_xref:
 						  XFS_BTREE_NOERROR);
 	}
 
+	/* Cross-reference with the refcountbt. */
+	if (xfs_sb_version_hasreflink(&mp->m_sb)) {
+		xcur = xfs_refcountbt_init_cursor(mp, NULL, agf_bp, agno, NULL);
+		err2 = xfs_refcount_has_record(xcur, XFS_SB_BLOCK(mp), 1,
+				&has_refcount);
+		if (!err2)
+			XFS_SCRUB_CHECK(mp, bp, "superblock", !has_refcount);
+		xfs_btree_del_cursor(xcur, err2 ? XFS_BTREE_ERROR :
+						  XFS_BTREE_NOERROR);
+	}
+
 	xfs_scrub_put_ag_headers(&agi_bp, &agf_bp);
 out:
 	xfs_buf_relse(bp);
@@ -1000,6 +1012,7 @@  xfs_scrub_agf(
 	bool				is_freesp;
 	bool				has_inodes;
 	bool				has_rmap;
+	bool				has_refcount;
 	int				error;
 	int				err2;
 
@@ -1103,6 +1116,23 @@  skip_rmap_xref:
 						  XFS_BTREE_NOERROR);
 	}
 
+	/* Cross-reference with the refcountbt. */
+	if (xfs_sb_version_hasreflink(&mp->m_sb)) {
+		xcur = xfs_refcountbt_init_cursor(mp, NULL, agf_bp, agno, NULL);
+		err2 = xfs_refcount_has_record(xcur, XFS_AGF_BLOCK(mp), 1,
+				&has_refcount);
+		if (err2)
+			goto skip_refc_xref;
+		XFS_SCRUB_CHECK(mp, agf_bp, "AGF", !has_refcount);
+		err2 = xfs_btree_count_blocks(xcur, &blocks);
+		if (!err2)
+			XFS_SCRUB_CHECK(mp, agf_bp, "AGF", blocks ==
+					be32_to_cpu(agf->agf_refcount_blocks));
+skip_refc_xref:
+		xfs_btree_del_cursor(xcur, err2 ? XFS_BTREE_ERROR :
+						  XFS_BTREE_NOERROR);
+	}
+
 	xfs_scrub_put_ag_headers(&agi_bp, &agf_bp);
 	return error;
 }
@@ -1123,6 +1153,7 @@  xfs_scrub_agfl(
 	struct xfs_btree_cur		*icur = NULL;
 	struct xfs_btree_cur		*fcur = NULL;
 	struct xfs_btree_cur		*rcur = NULL;
+	struct xfs_btree_cur		*ccur = NULL;
 	struct xfs_owner_info		oinfo;
 	xfs_agnumber_t			agno;
 	xfs_agblock_t			agbno;
@@ -1131,6 +1162,7 @@  xfs_scrub_agfl(
 	bool				is_freesp;
 	bool				has_inodes;
 	bool				has_rmap;
+	bool				has_refcount;
 	int				i;
 	int				error;
 	int				err2;
@@ -1185,6 +1217,15 @@  xfs_scrub_agfl(
 			XFS_SCRUB_CHECK(mp, agfl_bp, "AGFL", has_rmap);
 	}
 
+	/* Set up cross-reference with refcountbt. */
+	if (xfs_sb_version_hasreflink(&mp->m_sb)) {
+		ccur = xfs_refcountbt_init_cursor(mp, NULL, agf_bp, agno, NULL);
+		err2 = xfs_refcount_has_record(ccur, XFS_AGFL_BLOCK(mp), 1,
+				&has_refcount);
+		if (!err2)
+			XFS_SCRUB_CHECK(mp, agfl_bp, "AGFL", !has_refcount);
+	}
+
 	xfs_rmap_ag_owner(&oinfo, XFS_RMAP_OWN_AG);
 	agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, agfl_bp);
 	for (i = be32_to_cpu(agf->agf_flfirst);
@@ -1229,8 +1270,19 @@  xfs_scrub_agfl(
 			if (!err2)
 				XFS_SCRUB_CHECK(mp, agfl_bp, "AGFL", has_rmap);
 		}
+
+		/* Cross-reference with the refcountbt. */
+		if (ccur) {
+			err2 = xfs_refcount_has_record(ccur, agbno, 1,
+					&has_refcount);
+			if (!err2)
+				XFS_SCRUB_CHECK(mp, agfl_bp, "AGFL",
+						!has_refcount);
+		}
 	}
 
+	if (ccur)
+		xfs_btree_del_cursor(ccur, XFS_BTREE_ERROR);
 	if (rcur)
 		xfs_btree_del_cursor(rcur, XFS_BTREE_ERROR);
 	if (fcur)
@@ -1264,6 +1316,7 @@  xfs_scrub_agi(
 	bool				is_freesp;
 	bool				has_inodes;
 	bool				has_rmap;
+	bool				has_refcount;
 	int				error;
 	int				err2;
 
@@ -1334,6 +1387,17 @@  xfs_scrub_agi(
 						  XFS_BTREE_NOERROR);
 	}
 
+	/* Cross-reference with the refcountbt. */
+	if (xfs_sb_version_hasreflink(&mp->m_sb)) {
+		xcur = xfs_refcountbt_init_cursor(mp, NULL, agf_bp, agno, NULL);
+		err2 = xfs_refcount_has_record(xcur, XFS_AGI_BLOCK(mp), 1,
+				&has_refcount);
+		if (!err2)
+			XFS_SCRUB_CHECK(mp, agi_bp, "AGI", !has_refcount);
+		xfs_btree_del_cursor(xcur, err2 ? XFS_BTREE_ERROR :
+						  XFS_BTREE_NOERROR);
+	}
+
 	xfs_scrub_put_ag_headers(&agi_bp, &agf_bp);
 	return error;
 }
@@ -1355,6 +1419,7 @@  xfs_scrub_allocbt_helper(
 	xfs_extlen_t			len;
 	bool				has_rmap;
 	bool				has_inodes;
+	bool				has_refcount;
 	int				has_otherrec;
 	int				error = 0;
 	int				err2;
@@ -1415,6 +1480,14 @@  skip_freesp_xref:
 			XFS_BTREC_SCRUB_CHECK(bs, !has_rmap);
 	}
 
+	/* Cross-reference with the refcountbt. */
+	if (bs->refc_cur) {
+		err2 = xfs_refcount_has_record(bs->refc_cur, bno, len,
+				&has_refcount);
+		if (!err2)
+			XFS_BTREC_SCRUB_CHECK(bs, !has_refcount);
+	}
+
 	return error;
 }
 
@@ -1488,6 +1561,7 @@  xfs_scrub_iallocbt_helper(
 	bool				is_freesp;
 	bool				has_inodes;
 	bool				has_rmap;
+	bool				has_refcount;
 	int				holecount;
 	int				i;
 	int				error = 0;
@@ -1547,6 +1621,14 @@  xfs_scrub_iallocbt_helper(
 				XFS_BTREC_SCRUB_CHECK(bs, has_rmap);
 		}
 
+		/* Cross-reference with the refcountbt. */
+		if (bs->refc_cur) {
+			err2 = xfs_refcount_has_record(bs->refc_cur, bno,
+					len, &has_refcount);
+			if (!err2)
+				XFS_BTREC_SCRUB_CHECK(bs, !has_refcount);
+		}
+
 		goto out;
 	}
 
@@ -1604,6 +1686,14 @@  xfs_scrub_iallocbt_helper(
 			if (!err2)
 				XFS_BTREC_SCRUB_CHECK(bs, has_rmap);
 		}
+
+		/* Cross-reference with the refcountbt. */
+		if (bs->refc_cur) {
+			err2 = xfs_refcount_has_record(bs->refc_cur, bno,
+					len, &has_refcount);
+			if (!err2)
+				XFS_BTREC_SCRUB_CHECK(bs, !has_refcount);
+		}
 	}
 
 	XFS_BTREC_SCRUB_CHECK(bs, holecount <= XFS_INODES_PER_CHUNK);
@@ -1674,13 +1764,17 @@  xfs_scrub_rmapbt_helper(
 	struct xfs_mount		*mp = bs->cur->bc_mp;
 	struct xfs_agf			*agf;
 	struct xfs_rmap_irec		irec;
+	struct xfs_refcount_irec	crec;
 	xfs_agblock_t			eoag;
+	xfs_agblock_t			fbno;
+	xfs_extlen_t			flen;
 	bool				is_freesp;
 	bool				non_inode;
 	bool				is_unwritten;
 	bool				is_bmbt;
 	bool				is_attr;
 	bool				has_inodes;
+	int				has_refcount;
 	int				error = 0;
 	int				err2;
 
@@ -1741,6 +1835,45 @@  xfs_scrub_rmapbt_helper(
 					!has_inodes);
 	}
 
+	/* Cross-reference with the refcount btree. */
+	if (bs->refc_cur) {
+		if (irec.rm_owner == XFS_RMAP_OWN_COW) {
+			/* Check this CoW staging extent. */
+			err2 = xfs_refcount_lookup_le(bs->refc_cur,
+					irec.rm_startblock, &has_refcount);
+			if (err2)
+				goto skip_refc_xref;
+			XFS_BTREC_SCRUB_GOTO(bs, has_refcount, skip_refc_xref);
+
+			err2 = xfs_refcount_get_rec(bs->refc_cur, &crec,
+					&has_refcount);
+			if (err2)
+				goto skip_refc_xref;
+			XFS_BTREC_SCRUB_GOTO(bs, has_refcount, skip_refc_xref);
+			XFS_BTREC_SCRUB_CHECK(bs, crec.rc_startblock <=
+					irec.rm_startblock);
+			XFS_BTREC_SCRUB_CHECK(bs, crec.rc_startblock +
+					crec.rc_blockcount >
+					crec.rc_startblock);
+			XFS_BTREC_SCRUB_CHECK(bs, crec.rc_startblock +
+					crec.rc_blockcount >=
+					irec.rm_startblock +
+					irec.rm_blockcount);
+			XFS_BTREC_SCRUB_CHECK(bs,
+					crec.rc_refcount == 1);
+		} else {
+			/* If this is shared, the inode flag must be set. */
+			err2 = xfs_refcount_find_shared(bs->refc_cur,
+					irec.rm_startblock, irec.rm_blockcount,
+					&fbno, &flen, false);
+			if (!err2)
+				XFS_BTREC_SCRUB_CHECK(bs, flen == 0 ||
+						(!non_inode && !is_attr &&
+						 !is_bmbt && !is_unwritten));
+		}
+skip_refc_xref:;
+	}
+
 	return error;
 }
 
@@ -2164,12 +2297,16 @@  xfs_scrub_bmap_extent(
 	xfs_agnumber_t			agno;
 	xfs_fsblock_t			bno;
 	struct xfs_rmap_irec		rmap;
+	struct xfs_refcount_irec	crec;
 	uint64_t			owner;
 	xfs_fileoff_t			offset;
+	xfs_agblock_t			fbno;
+	xfs_extlen_t			flen;
 	bool				is_freesp;
 	bool				has_inodes;
 	unsigned int			rflags;
 	int				has_rmap;
+	int				has_refcount;
 	int				error = 0;
 	int				err2 = 0;
 
@@ -2348,6 +2485,53 @@  skip_rmap_xref:
 						  XFS_BTREE_NOERROR);
 	}
 
+	/*
+	 * If this is a non-shared file on a reflink filesystem,
+	 * check the refcountbt to see if the flag is wrong.
+	 */
+	if (xfs_sb_version_hasreflink(&mp->m_sb) && !info->is_rt) {
+		xcur = xfs_refcountbt_init_cursor(mp, NULL, agf_bp, agno, NULL);
+
+		if (info->whichfork == XFS_COW_FORK) {
+			/* Check this CoW staging extent. */
+			err2 = xfs_refcount_lookup_le(xcur, bno, &has_refcount);
+			if (err2)
+				goto skip_refc_xref;
+			XFS_INO_SCRUB_GOTO(ip, NULL, info->type, has_refcount,
+					skip_refc_xref);
+
+			err2 = xfs_refcount_get_rec(xcur, &crec, &has_refcount);
+			if (err2)
+				goto skip_refc_xref;
+			XFS_INO_SCRUB_GOTO(ip, NULL, info->type, has_refcount,
+					skip_refc_xref);
+
+			XFS_INO_SCRUB_CHECK(ip, NULL, info->type,
+					crec.rc_startblock <= bno);
+			XFS_INO_SCRUB_CHECK(ip, NULL, info->type,
+					crec.rc_startblock +
+					crec.rc_blockcount >
+					crec.rc_startblock);
+			XFS_INO_SCRUB_CHECK(ip, NULL, info->type,
+					crec.rc_startblock +
+					crec.rc_blockcount >=
+					bno + irec->br_blockcount);
+			XFS_INO_SCRUB_CHECK(ip, NULL, info->type,
+					crec.rc_refcount == 1);
+		} else {
+			/* If this is shared, the inode flag must be set. */
+			err2 = xfs_refcount_find_shared(xcur, bno,
+					irec->br_blockcount, &fbno, &flen,
+					false);
+			if (!err2)
+				XFS_INO_SCRUB_CHECK(ip, bp, info->type,
+						flen == 0 ||
+						xfs_is_reflink_inode(ip));
+		}
+skip_refc_xref:
+		xfs_btree_del_cursor(xcur, XFS_BTREE_NOERROR);
+	}
+
 	xfs_scrub_put_ag_headers(&agi_bp, &agf_bp);
 out:
 	info->lastoff = irec->br_startoff + irec->br_blockcount;