[PATCH 1/6] xfs: don't commit sunit/swidth updates to disk if that would cause repair failures

Darrick J. Wong
2019-12-04T17:04:37Z

From: Darrick J. Wong <darrick.wong@oracle.com>

Alex Lyakas reported[1] that mounting an xfs filesystem with new sunit
and swidth values could cause xfs_repair to fail loudly. The problem
here is that repair calculates the where mkfs should have allocated the
root inode, based on the superblock geometry. The allocation decisions
depend on sunit, which means that we really can't go updating sunit if
it would lead to a subsequent repair failure on an otherwise correct
filesystem.

Port the computation code from xfs_repair and teach mount to avoid the
ondisk update if it would cause problems for repair. We allow the mount
to proceed (and new allocations will reflect this new geometry) because
we've never screened this kind of thing before.

[1] https://lore.kernel.org/linux-xfs/20191125130744.GA44777@bfoster/T/#m00f9594b511e076e2fcdd489d78bc30216d72a7d

Reported-by: Alex Lyakas <alex@zadara.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
libxfs/xfs_ialloc.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++
libxfs/xfs_ialloc.h | 1 +
2 files changed, 82 insertions(+)

diff --git a/libxfs/xfs_ialloc.c b/libxfs/xfs_ialloc.c
index f039b287..1d56d764 100644
--- a/libxfs/xfs_ialloc.c
+++ b/libxfs/xfs_ialloc.c
@@ -2848,3 +2848,84 @@ xfs_ialloc_setup_geometry(
else
igeo->ialloc_align = 0;
}
+
+/*
+ * Compute the location of the root directory inode that is laid out by mkfs.
+ * The @sunit parameter will be copied from the superblock if it is negative.
+ */
+xfs_ino_t
+xfs_ialloc_calc_rootino(
+ struct xfs_mount *mp,
+ int sunit)
+{
+ struct xfs_ino_geometry *igeo = M_IGEO(mp);
+ xfs_agino_t first_agino;
+ xfs_agblock_t first_bno;
+
+ if (sunit < 0)
+ sunit = mp->m_sb.sb_unit;
+
+ /*
+ * Pre-calculate the geometry of ag 0. We know what it looks like
+ * because we know what mkfs does: 2 allocation btree roots (by block
+ * and by size), the inode allocation btree root, the free inode
+ * allocation btree root (if enabled) and some number of blocks to
+ * prefill the agfl.
+ *
+ * Because the current shape of the btrees may differ from the current
+ * shape, we open code the mkfs freelist block count here. mkfs creates
+ * single level trees, so the calculation is pretty straight forward for
+ * the trees that use the AGFL.
+ */
+
+ /* free space by block btree root comes after the ag headers */
+ first_bno = howmany(4 * mp->m_sb.sb_sectsize, mp->m_sb.sb_blocksize);
+
+ /* free space by length btree root */
+ first_bno += 1;
+
+ /* inode btree root */
+ first_bno += 1;
+
+ /* agfl */
+ first_bno += (2 * min_t(xfs_agblock_t, 2, mp->m_ag_maxlevels)) + 1;
+
+ if (xfs_sb_version_hasfinobt(&mp->m_sb))
+ first_bno++;
+
+ if (xfs_sb_version_hasrmapbt(&mp->m_sb)) {
+ first_bno++;
+ /* agfl blocks */
+ first_bno += min_t(xfs_agblock_t, 2, mp->m_rmap_maxlevels);
+ }
+
+ if (xfs_sb_version_hasreflink(&mp->m_sb))
+ first_bno++;
+
+ /*
+ * If the log is allocated in the first allocation group we need to
+ * add the number of blocks used by the log to the above calculation.
+ *
+ * This can happens with filesystems that only have a single
+ * allocation group, or very odd geometries created by old mkfs
+ * versions on very small filesystems.
+ */
+ if (mp->m_sb.sb_logstart &&
+ XFS_FSB_TO_AGNO(mp, mp->m_sb.sb_logstart) == 0)
+ first_bno += mp->m_sb.sb_logblocks;
+
+ /*
+ * ditto the location of the first inode chunks in the fs ('/')
+ */
+ if (xfs_sb_version_hasdalign(&mp->m_sb) && igeo->ialloc_align > 0) {
+ first_agino = XFS_AGB_TO_AGINO(mp, roundup(first_bno, sunit));
+ } else if (xfs_sb_version_hasalign(&mp->m_sb) &&
+ mp->m_sb.sb_inoalignmt > 1) {
+ first_agino = XFS_AGB_TO_AGINO(mp,
+ roundup(first_bno, mp->m_sb.sb_inoalignmt));
+ } else {
+ first_agino = XFS_AGB_TO_AGINO(mp, first_bno);
+ }
+
+ return XFS_AGINO_TO_INO(mp, 0, first_agino);
+}
diff --git a/libxfs/xfs_ialloc.h b/libxfs/xfs_ialloc.h
index 323592d5..72b3468b 100644
--- a/libxfs/xfs_ialloc.h
+++ b/libxfs/xfs_ialloc.h
@@ -152,5 +152,6 @@ int xfs_inobt_insert_rec(struct xfs_btree_cur *cur, uint16_t holemask,

int xfs_ialloc_cluster_alignment(struct xfs_mount *mp);
void xfs_ialloc_setup_geometry(struct xfs_mount *mp);
+xfs_ino_t xfs_ialloc_calc_rootino(struct xfs_mount *mp, int sunit);

#endif /* __XFS_IALLOC_H__ */