[PATCH v2] xfs_db: make sure agblocks is valid to prevent corruption

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: liuh <liuhuan01@xxxxxxxxxx>

Recently, I was testing xfstests. When I run xfs/350 case, it always generate coredump during the process.
	xfs_db -c "sb 0" -c "p agblocks" /dev/loop1

System will generate signal SIGFPE corrupt the process. And the stack as follow:
corrupt at: (*bpp)->b_pag = xfs_perag_get(btp->bt_mount, xfs_daddr_to_agno(btp->bt_mount, blkno)); in function libxfs_getbuf_flags
	#0  libxfs_getbuf_flags
	#1  libxfs_getbuf_flags
	#2  libxfs_buf_read_map
	#3  libxfs_buf_read
	#4  libxfs_mount
	#5  init
	#6  main

The coredump was caused by the corrupt superblock metadata: (mp)->m_sb.sb_agblocks, it was 0.
In this case, user cannot run in expert mode also.

So, try to get agblocks from agf/agi 0, if failed use the default geometry to calc agblocks.
The worst thing is cannot get agblocks accroding above method, then set it to 1.

Signed-off-by: liuh <liuhuan01@xxxxxxxxxx>
---
 db/Makefile |   2 +-
 db/init.c   | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 129 insertions(+), 1 deletion(-)

diff --git a/db/Makefile b/db/Makefile
index 83389376..322d5617 100644
--- a/db/Makefile
+++ b/db/Makefile
@@ -68,7 +68,7 @@ CFILES = $(HFILES:.h=.c) \
 LSRCFILES = xfs_admin.sh xfs_ncheck.sh xfs_metadump.sh
 
 LLDLIBS	= $(LIBXFS) $(LIBXLOG) $(LIBFROG) $(LIBUUID) $(LIBRT) $(LIBURCU) \
-	  $(LIBPTHREAD)
+	  $(LIBPTHREAD) $(LIBBLKID)
 LTDEPENDENCIES = $(LIBXFS) $(LIBXLOG) $(LIBFROG)
 LLDFLAGS += -static-libtool-libs
 
diff --git a/db/init.c b/db/init.c
index cea25ae5..15124ee2 100644
--- a/db/init.c
+++ b/db/init.c
@@ -38,6 +38,121 @@ usage(void)
 	exit(1);
 }
 
+static void
+xfs_guess_default_ag_geometry(uint64_t *agsize, uint64_t *agcount, struct libxfs_init *x)
+{
+	struct fs_topology	ft;
+	int			blocklog;
+	uint64_t		dblocks;
+	int			multidisk;
+
+	memset(&ft, 0, sizeof(ft));
+	get_topology(x, &ft, 1);
+
+	/*
+	 * get geometry from get_topology result.
+	 * Use default block size (2^12)
+	 */
+	blocklog = 12;
+	multidisk = ft.data.swidth | ft.data.sunit;
+	dblocks = x->data.size >> (blocklog - BBSHIFT);
+	calc_default_ag_geometry(blocklog, dblocks, multidisk,
+				 agsize, agcount);
+}
+
+static xfs_agblock_t
+xfs_get_agblock_from_agf(struct xfs_mount *mp)
+{
+	xfs_agblock_t agblocks = 0;
+	int error;
+	struct xfs_buf *bp;
+	struct xfs_agf *agf;
+
+	error = -libxfs_buf_read_uncached(mp->m_ddev_targp,
+			XFS_AG_DADDR(mp, 0, XFS_AGF_DADDR(mp)),
+			XFS_FSS_TO_BB(mp, 1), 0, &bp, NULL);
+	if (error) {
+		fprintf(stderr, "xfs_get_agblock from agf-0 error %d\n", error);
+		return agblocks;
+	}
+
+	if (xfs_has_crc(mp) && !xfs_buf_verify_cksum(bp, XFS_AGF_CRC_OFF)) {
+		fprintf(stderr, "xfs_get_agblock from agf-0 badcrc\n");
+		return agblocks;
+	}
+
+	agf = bp->b_addr;
+	agblocks = be32_to_cpu(agf->agf_length);
+
+	libxfs_buf_relse(bp);
+
+	return agblocks;
+}
+
+static xfs_agblock_t
+xfs_get_agblock_from_agi(struct xfs_mount *mp)
+{
+	xfs_agblock_t agblocks = 0;
+	int error;
+	struct xfs_buf *bp;
+	struct xfs_agi *agi;
+
+	error = -libxfs_buf_read_uncached(mp->m_ddev_targp,
+			XFS_AG_DADDR(mp, 0, XFS_AGI_DADDR(mp)),
+			XFS_FSS_TO_BB(mp, 1), 0, &bp, NULL);
+	if (error) {
+		fprintf(stderr, "xfs_get_agblock from agi-0 error %d\n", error);
+		return agblocks;
+	}
+
+	if (xfs_has_crc(mp) && !xfs_buf_verify_cksum(bp, XFS_AGI_CRC_OFF)) {
+		fprintf(stderr, "xfs_get_agblock from agi-0 badcrc\n");
+		return agblocks;
+	}
+
+	agi = bp->b_addr;
+	agblocks = be32_to_cpu(agi->agi_length);
+
+	libxfs_buf_relse(bp);
+
+	return agblocks;
+}
+
+/*
+ * If sb_agblocks was damaged, try to read it from agf/agi 0.
+ * With read agf/agi fails use default geometry to calc agblocks/agcount.
+ * The worst thing is cannot get agblocks according above method, then set to 1.
+ */
+static xfs_agblock_t
+xfs_try_get_agblocks(struct xfs_mount *mp, struct libxfs_init *x)
+{
+	xfs_agblock_t agblocks;
+	uint64_t agsize, agcount;
+
+	/* firset try to get agblocks from agf-0 */
+	agblocks = xfs_get_agblock_from_agf(mp);
+	if (XFS_FSB_TO_B(mp, agblocks) >= XFS_MIN_AG_BYTES &&
+		XFS_FSB_TO_B(mp, agblocks) <= XFS_MAX_AG_BYTES)
+		return agblocks;
+
+	/* second try to get agblocks from agi-0 */
+	agblocks = xfs_get_agblock_from_agi(mp);
+	if (XFS_FSB_TO_B(mp, agblocks) >= XFS_MIN_AG_BYTES &&
+		XFS_FSB_TO_B(mp, agblocks) <= XFS_MAX_AG_BYTES)
+		return agblocks;
+
+	/* third use default geometry to calc agblocks/agcount */
+	xfs_guess_default_ag_geometry(&agsize, &agcount, x);
+
+	if (XFS_FSB_TO_B(mp, agsize) < XFS_MIN_AG_BYTES ||
+		XFS_FSB_TO_B(mp, agsize) > XFS_MAX_AG_BYTES)
+		agblocks = 1; /* the worst is set to 1 */
+	else
+		agblocks = agsize;
+
+	return agblocks;
+}
+
 static void
 init(
 	int		argc,
@@ -129,6 +244,19 @@ init(
 		}
 	}
 
+	/* If sb_agblocks was damaged, try to get agblocks */
+	if (XFS_FSB_TO_B(&xmount, sbp->sb_agblocks) < XFS_MIN_AG_BYTES ||
+		XFS_FSB_TO_B(&xmount, sbp->sb_agblocks) > XFS_MAX_AG_BYTES) {
+		xfs_agblock_t agblocks;
+		xfs_agblock_t bad_agblocks = sbp->sb_agblocks;
+
+		agblocks = xfs_try_get_agblocks(&xmount, &x);
+		sbp->sb_agblocks = agblocks;
+
+		fprintf(stderr, "wrong agblocks, try to get from agblocks %u -> %u\n",
+			bad_agblocks, sbp->sb_agblocks);
+	}
+
 	agcount = sbp->sb_agcount;
 	mp = libxfs_mount(&xmount, sbp, &x, LIBXFS_MOUNT_DEBUGGER);
 	if (!mp) {
-- 
2.43.0





[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux