Skip to content

Commit 13eaec4

Browse files
committed
xfs: don't commit sunit/swidth updates to disk if that would cause repair failures
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 from xfs_repair some code that computes the location of the root inode and teach mount to skip the ondisk update if it would cause problems for repair. Along the way we'll update the documentation, provide a function for computing the minimum AGFL size instead of open-coding it, and cut down some indenting in the mount code. Note that we allow the mount to proceed (and new allocations will reflect this new geometry) because we've never screened this kind of thing before. We'll have to wait for a new future incompat feature to enforce correct behavior, alas. Note that the geometry reporting always uses the superblock values, not the incore ones, so that is what xfs_info and xfs_growfs will report. [1] https://lore.kernel.org/linux-xfs/20191125130744.GA44777@bfoster/T/#m00f9594b511e076e2fcdd489d78bc30216d72a7d Reported-by: Alex Lyakas <[email protected]> Signed-off-by: Darrick J. Wong <[email protected]> Reviewed-by: Brian Foster <[email protected]>
1 parent 4f5b1b3 commit 13eaec4

File tree

4 files changed

+130
-1
lines changed

4 files changed

+130
-1
lines changed

fs/xfs/libxfs/xfs_ialloc.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,3 +2909,67 @@ xfs_ialloc_setup_geometry(
29092909
else
29102910
igeo->ialloc_align = 0;
29112911
}
2912+
2913+
/* Compute the location of the root directory inode that is laid out by mkfs. */
2914+
xfs_ino_t
2915+
xfs_ialloc_calc_rootino(
2916+
struct xfs_mount *mp,
2917+
int sunit)
2918+
{
2919+
struct xfs_ino_geometry *igeo = M_IGEO(mp);
2920+
xfs_agblock_t first_bno;
2921+
2922+
/*
2923+
* Pre-calculate the geometry of AG 0. We know what it looks like
2924+
* because libxfs knows how to create allocation groups now.
2925+
*
2926+
* first_bno is the first block in which mkfs could possibly have
2927+
* allocated the root directory inode, once we factor in the metadata
2928+
* that mkfs formats before it. Namely, the four AG headers...
2929+
*/
2930+
first_bno = howmany(4 * mp->m_sb.sb_sectsize, mp->m_sb.sb_blocksize);
2931+
2932+
/* ...the two free space btree roots... */
2933+
first_bno += 2;
2934+
2935+
/* ...the inode btree root... */
2936+
first_bno += 1;
2937+
2938+
/* ...the initial AGFL... */
2939+
first_bno += xfs_alloc_min_freelist(mp, NULL);
2940+
2941+
/* ...the free inode btree root... */
2942+
if (xfs_sb_version_hasfinobt(&mp->m_sb))
2943+
first_bno++;
2944+
2945+
/* ...the reverse mapping btree root... */
2946+
if (xfs_sb_version_hasrmapbt(&mp->m_sb))
2947+
first_bno++;
2948+
2949+
/* ...the reference count btree... */
2950+
if (xfs_sb_version_hasreflink(&mp->m_sb))
2951+
first_bno++;
2952+
2953+
/*
2954+
* ...and the log, if it is allocated in the first allocation group.
2955+
*
2956+
* This can happen with filesystems that only have a single
2957+
* allocation group, or very odd geometries created by old mkfs
2958+
* versions on very small filesystems.
2959+
*/
2960+
if (mp->m_sb.sb_logstart &&
2961+
XFS_FSB_TO_AGNO(mp, mp->m_sb.sb_logstart) == 0)
2962+
first_bno += mp->m_sb.sb_logblocks;
2963+
2964+
/*
2965+
* Now round first_bno up to whatever allocation alignment is given
2966+
* by the filesystem or was passed in.
2967+
*/
2968+
if (xfs_sb_version_hasdalign(&mp->m_sb) && igeo->ialloc_align > 0)
2969+
first_bno = roundup(first_bno, sunit);
2970+
else if (xfs_sb_version_hasalign(&mp->m_sb) &&
2971+
mp->m_sb.sb_inoalignmt > 1)
2972+
first_bno = roundup(first_bno, mp->m_sb.sb_inoalignmt);
2973+
2974+
return XFS_AGINO_TO_INO(mp, 0, XFS_AGB_TO_AGINO(mp, first_bno));
2975+
}

fs/xfs/libxfs/xfs_ialloc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,6 @@ int xfs_inobt_insert_rec(struct xfs_btree_cur *cur, uint16_t holemask,
152152

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

156157
#endif /* __XFS_IALLOC_H__ */

fs/xfs/xfs_mount.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include "xfs_reflink.h"
3232
#include "xfs_extent_busy.h"
3333
#include "xfs_health.h"
34-
34+
#include "xfs_trace.h"
3535

3636
static DEFINE_MUTEX(xfs_uuid_table_mutex);
3737
static int xfs_uuid_table_size;
@@ -359,6 +359,42 @@ xfs_readsb(
359359
return error;
360360
}
361361

362+
/*
363+
* If the sunit/swidth change would move the precomputed root inode value, we
364+
* must reject the ondisk change because repair will stumble over that.
365+
* However, we allow the mount to proceed because we never rejected this
366+
* combination before. Returns true to update the sb, false otherwise.
367+
*/
368+
static inline int
369+
xfs_check_new_dalign(
370+
struct xfs_mount *mp,
371+
int new_dalign,
372+
bool *update_sb)
373+
{
374+
struct xfs_sb *sbp = &mp->m_sb;
375+
xfs_ino_t calc_ino;
376+
377+
calc_ino = xfs_ialloc_calc_rootino(mp, new_dalign);
378+
trace_xfs_check_new_dalign(mp, new_dalign, calc_ino);
379+
380+
if (sbp->sb_rootino == calc_ino) {
381+
*update_sb = true;
382+
return 0;
383+
}
384+
385+
xfs_warn(mp,
386+
"Cannot change stripe alignment; would require moving root inode.");
387+
388+
/*
389+
* XXX: Next time we add a new incompat feature, this should start
390+
* returning -EINVAL to fail the mount. Until then, spit out a warning
391+
* that we're ignoring the administrator's instructions.
392+
*/
393+
xfs_warn(mp, "Skipping superblock stripe alignment update.");
394+
*update_sb = false;
395+
return 0;
396+
}
397+
362398
/*
363399
* If we were provided with new sunit/swidth values as mount options, make sure
364400
* that they pass basic alignment and superblock feature checks, and convert
@@ -419,10 +455,17 @@ xfs_update_alignment(
419455
struct xfs_sb *sbp = &mp->m_sb;
420456

421457
if (mp->m_dalign) {
458+
bool update_sb;
459+
int error;
460+
422461
if (sbp->sb_unit == mp->m_dalign &&
423462
sbp->sb_width == mp->m_swidth)
424463
return 0;
425464

465+
error = xfs_check_new_dalign(mp, mp->m_dalign, &update_sb);
466+
if (error || !update_sb)
467+
return error;
468+
426469
sbp->sb_unit = mp->m_dalign;
427470
sbp->sb_width = mp->m_swidth;
428471
mp->m_update_sb = true;

fs/xfs/xfs_trace.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3573,6 +3573,27 @@ DEFINE_KMEM_EVENT(kmem_alloc_large);
35733573
DEFINE_KMEM_EVENT(kmem_realloc);
35743574
DEFINE_KMEM_EVENT(kmem_zone_alloc);
35753575

3576+
TRACE_EVENT(xfs_check_new_dalign,
3577+
TP_PROTO(struct xfs_mount *mp, int new_dalign, xfs_ino_t calc_rootino),
3578+
TP_ARGS(mp, new_dalign, calc_rootino),
3579+
TP_STRUCT__entry(
3580+
__field(dev_t, dev)
3581+
__field(int, new_dalign)
3582+
__field(xfs_ino_t, sb_rootino)
3583+
__field(xfs_ino_t, calc_rootino)
3584+
),
3585+
TP_fast_assign(
3586+
__entry->dev = mp->m_super->s_dev;
3587+
__entry->new_dalign = new_dalign;
3588+
__entry->sb_rootino = mp->m_sb.sb_rootino;
3589+
__entry->calc_rootino = calc_rootino;
3590+
),
3591+
TP_printk("dev %d:%d new_dalign %d sb_rootino %llu calc_rootino %llu",
3592+
MAJOR(__entry->dev), MINOR(__entry->dev),
3593+
__entry->new_dalign, __entry->sb_rootino,
3594+
__entry->calc_rootino)
3595+
)
3596+
35763597
#endif /* _TRACE_XFS_H */
35773598

35783599
#undef TRACE_INCLUDE_PATH

0 commit comments

Comments
 (0)