diff mbox series

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

Message ID 20240929085317.31545-1-liuhuan01@kylinos.cn (mailing list archive)
State Not Applicable, archived
Headers show
Series [v3] xfs_db: make sure agblocks is valid to prevent corruption | expand

Commit Message

liuh Sept. 29, 2024, 8:53 a.m. UTC
From: liuh <liuhuan01@kylinos.cn>

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 agf/agi 0 length match, use it as agblocks.
If failed use the default geometry to calc agblocks.

Signed-off-by: liuh <liuhuan01@kylinos.cn>
---
 db/Makefile |   2 +-
 db/init.c   | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 143 insertions(+), 1 deletion(-)
diff mbox series

Patch

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..167bc777 100644
--- a/db/init.c
+++ b/db/init.c
@@ -38,6 +38,138 @@  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;
+
+	fprintf(stderr, "Attempting to guess AG length from device geometry. This may not work.\n");
+
+	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);
+
+	if (*agsize >= XFS_MIN_AG_BLOCKS && *agsize <= XFS_MAX_AG_BLOCKS)
+		fprintf(stderr, "Guessed AG length is %lu blocks.\n", *agsize);
+}
+
+static xfs_agblock_t
+xfs_get_agblock_from_agf(struct xfs_mount *mp)
+{
+	xfs_agblock_t agblocks = NULLAGBLOCK;
+	int error;
+	struct xfs_buf *bp;
+	struct xfs_agf *agf;
+
+	error = -libxfs_buf_read_uncached(mp->m_ddev_targp,
+			XFS_AGF_DADDR(mp), 1, 0, &bp, NULL);
+	if (error) {
+		fprintf(stderr, "AGF 0 length recovery failed\n");
+		return NULLAGBLOCK;
+	}
+
+	agf = bp->b_addr;
+	if (be32_to_cpu(agf->agf_magicnum) == XFS_AGF_MAGIC)
+		agblocks = be32_to_cpu(agf->agf_length);
+
+	libxfs_buf_relse(bp);
+
+	if (agblocks != NULLAGBLOCK)
+		fprintf(stderr, "AGF 0 length %u blocks found.\n", agblocks);
+	else
+		fprintf(stderr, "AGF 0 length recovery failed.\n");
+
+	return agblocks;
+}
+
+static xfs_agblock_t
+xfs_get_agblock_from_agi(struct xfs_mount *mp)
+{
+	xfs_agblock_t agblocks = NULLAGBLOCK;
+	int error;
+	struct xfs_buf *bp;
+	struct xfs_agi *agi;
+
+	error = -libxfs_buf_read_uncached(mp->m_ddev_targp,
+			XFS_AGI_DADDR(mp), 1, 0, &bp, NULL);
+	if (error) {
+		fprintf(stderr, "AGI 0 length recovery failed\n");
+		return NULLAGBLOCK;
+	}
+
+
+	agi = bp->b_addr;
+	if (be32_to_cpu(agi->agi_magicnum) == XFS_AGI_MAGIC)
+		agblocks = be32_to_cpu(agi->agi_length);
+
+	libxfs_buf_relse(bp);
+
+	if (agblocks != NULLAGBLOCK)
+		fprintf(stderr, "AGI 0 length %u blocks found.\n", agblocks);
+	else
+		fprintf(stderr, "AGI 0 length recovery failed.\n");
+
+	return agblocks;
+}
+
+/*
+ * Try to get it from agf/agi length when primary superblock agblocks damaged.
+ * If agf matchs agi length, use it as agblocks, otherwise use the default geometry
+ * to calc agblocks
+ */
+static xfs_agblock_t
+xfs_try_get_agblocks(struct xfs_mount *mp, struct libxfs_init *x)
+{
+	xfs_agblock_t agblocks = NULLAGBLOCK;
+	xfs_agblock_t agblocks_agf, agblocks_agi;
+	uint64_t agsize, agcount;
+
+	fprintf(stderr, "Attempting recovery from AGF/AGI 0 metadata...\n");
+
+	agblocks_agf = xfs_get_agblock_from_agf(mp);
+	agblocks_agi = xfs_get_agblock_from_agi(mp);
+
+	if (agblocks_agf == agblocks_agi && agblocks_agf >= XFS_MIN_AG_BLOCKS && agblocks_agf <= XFS_MAX_AG_BLOCKS) {
+		fprintf(stderr, "AGF/AGI 0 length matches.\n");
+		fprintf(stderr, "Using %u blocks for superblock agblocks\n", agblocks_agf);
+		return agblocks_agf;
+	}
+
+	/* use default geometry to calc agblocks/agcount */
+	xfs_guess_default_ag_geometry(&agsize, &agcount, x);
+
+	/* choose the agblocks among agf/agi length and agsize */
+	if (agblocks_agf == agsize && agsize >= XFS_MIN_AG_BLOCKS && agsize <= XFS_MAX_AG_BLOCKS) {
+		fprintf(stderr, "Guessed AG matchs AGF length\n");
+		agblocks = agsize;
+	} else if (agblocks_agi == agsize && agsize >= XFS_MIN_AG_BLOCKS && agsize <= XFS_MAX_AG_BLOCKS) {
+		fprintf(stderr, "Guessed AG matchs AGI length\n");
+		agblocks = agsize;
+	} else if (agsize >= XFS_MIN_AG_BLOCKS && agsize <= XFS_MAX_AG_BLOCKS) {
+		fprintf(stderr, "Guessed AG does not match AGF/AGI 0 length.\n");
+		agblocks =  agsize;
+	} else {
+		fprintf(stderr, "_(%s: device too small to hold a valid XFS filesystem)", progname);
+		exit(1);
+	}
+
+	fprintf(stderr, "Using %u blocks for superblock agblocks.\n", agblocks);
+
+	return agblocks;
+}
+
 static void
 init(
 	int		argc,
@@ -129,6 +261,16 @@  init(
 		}
 	}
 
+	/* If sb_agblocks was damaged, try to get agblocks */
+	if (sbp->sb_agblocks < XFS_MIN_AG_BLOCKS || sbp->sb_agblocks > XFS_MAX_AG_BLOCKS) {
+		xfs_agblock_t agblocks;
+
+		fprintf(stderr, "Out of bounds superblock agblocks (%u) found.\n", sbp->sb_agblocks);
+
+		agblocks = xfs_try_get_agblocks(&xmount, &x);
+		sbp->sb_agblocks = agblocks;
+	}
+
 	agcount = sbp->sb_agcount;
 	mp = libxfs_mount(&xmount, sbp, &x, LIBXFS_MOUNT_DEBUGGER);
 	if (!mp) {