1

XFS bug fixes for 6.12-rc5

* fix recovery of allocator ops after a growfs
 * Do not fail repairs on metadata files with no attr fork
 
 Signed-off-by: Carlos Maiolino <cem@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iJUEABMJAB0WIQQMHYkcUKcy4GgPe2RGdaER5QtfpgUCZxjo7gAKCRBGdaER5Qtf
 pr26AYCUc9+Vlg5iReesrghYHJgeCaMYZm2i4WdNdI+BO8d+5+AA1oUO55ib3xWd
 fX8A0MEBf32eeMR0E+K0NeKsmHnbGHXyWRg/27IlNRniL4/yldssEFB8X3b7Gkw5
 /geUVdz99A==
 =+NGs
 -----END PGP SIGNATURE-----

Merge tag 'xfs-6.12-fixes-5' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux

Pull xfs fixes from Carlos Maiolino:

 - Fix recovery of allocator ops after a growfs

 - Do not fail repairs on metadata files with no attr fork

* tag 'xfs-6.12-fixes-5' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux:
  xfs: update the pag for the last AG at recovery time
  xfs: don't use __GFP_RETRY_MAYFAIL in xfs_initialize_perag
  xfs: error out when a superblock buffer update reduces the agcount
  xfs: update the file system geometry after recoverying superblock buffers
  xfs: merge the perag freeing helpers
  xfs: pass the exact range to initialize to xfs_initialize_perag
  xfs: don't fail repairs on metadata files with no attr fork
This commit is contained in:
Linus Torvalds 2024-10-27 08:23:49 -10:00
commit a8b3be2617
7 changed files with 122 additions and 78 deletions

View File

@ -185,17 +185,20 @@ out:
} }
/* /*
* Free up the per-ag resources associated with the mount structure. * Free up the per-ag resources within the specified AG range.
*/ */
void void
xfs_free_perag( xfs_free_perag_range(
struct xfs_mount *mp) struct xfs_mount *mp,
xfs_agnumber_t first_agno,
xfs_agnumber_t end_agno)
{ {
struct xfs_perag *pag;
xfs_agnumber_t agno; xfs_agnumber_t agno;
for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) { for (agno = first_agno; agno < end_agno; agno++) {
pag = xa_erase(&mp->m_perags, agno); struct xfs_perag *pag = xa_erase(&mp->m_perags, agno);
ASSERT(pag); ASSERT(pag);
XFS_IS_CORRUPT(pag->pag_mount, atomic_read(&pag->pag_ref) != 0); XFS_IS_CORRUPT(pag->pag_mount, atomic_read(&pag->pag_ref) != 0);
xfs_defer_drain_free(&pag->pag_intents_drain); xfs_defer_drain_free(&pag->pag_intents_drain);
@ -270,54 +273,37 @@ xfs_agino_range(
return __xfs_agino_range(mp, xfs_ag_block_count(mp, agno), first, last); return __xfs_agino_range(mp, xfs_ag_block_count(mp, agno), first, last);
} }
/* int
* Free perag within the specified AG range, it is only used to free unused xfs_update_last_ag_size(
* perags under the error handling path.
*/
void
xfs_free_unused_perag_range(
struct xfs_mount *mp, struct xfs_mount *mp,
xfs_agnumber_t agstart, xfs_agnumber_t prev_agcount)
xfs_agnumber_t agend)
{ {
struct xfs_perag *pag; struct xfs_perag *pag = xfs_perag_grab(mp, prev_agcount - 1);
xfs_agnumber_t index;
for (index = agstart; index < agend; index++) { if (!pag)
pag = xa_erase(&mp->m_perags, index); return -EFSCORRUPTED;
if (!pag) pag->block_count = __xfs_ag_block_count(mp, prev_agcount - 1,
break; mp->m_sb.sb_agcount, mp->m_sb.sb_dblocks);
xfs_buf_cache_destroy(&pag->pag_bcache); __xfs_agino_range(mp, pag->block_count, &pag->agino_min,
xfs_defer_drain_free(&pag->pag_intents_drain); &pag->agino_max);
kfree(pag); xfs_perag_rele(pag);
} return 0;
} }
int int
xfs_initialize_perag( xfs_initialize_perag(
struct xfs_mount *mp, struct xfs_mount *mp,
xfs_agnumber_t agcount, xfs_agnumber_t old_agcount,
xfs_agnumber_t new_agcount,
xfs_rfsblock_t dblocks, xfs_rfsblock_t dblocks,
xfs_agnumber_t *maxagi) xfs_agnumber_t *maxagi)
{ {
struct xfs_perag *pag; struct xfs_perag *pag;
xfs_agnumber_t index; xfs_agnumber_t index;
xfs_agnumber_t first_initialised = NULLAGNUMBER;
int error; int error;
/* for (index = old_agcount; index < new_agcount; index++) {
* Walk the current per-ag tree so we don't try to initialise AGs pag = kzalloc(sizeof(*pag), GFP_KERNEL);
* that already exist (growfs case). Allocate and insert all the
* AGs we don't find ready for initialisation.
*/
for (index = 0; index < agcount; index++) {
pag = xfs_perag_get(mp, index);
if (pag) {
xfs_perag_put(pag);
continue;
}
pag = kzalloc(sizeof(*pag), GFP_KERNEL | __GFP_RETRY_MAYFAIL);
if (!pag) { if (!pag) {
error = -ENOMEM; error = -ENOMEM;
goto out_unwind_new_pags; goto out_unwind_new_pags;
@ -353,21 +339,17 @@ xfs_initialize_perag(
/* Active ref owned by mount indicates AG is online. */ /* Active ref owned by mount indicates AG is online. */
atomic_set(&pag->pag_active_ref, 1); atomic_set(&pag->pag_active_ref, 1);
/* first new pag is fully initialized */
if (first_initialised == NULLAGNUMBER)
first_initialised = index;
/* /*
* Pre-calculated geometry * Pre-calculated geometry
*/ */
pag->block_count = __xfs_ag_block_count(mp, index, agcount, pag->block_count = __xfs_ag_block_count(mp, index, new_agcount,
dblocks); dblocks);
pag->min_block = XFS_AGFL_BLOCK(mp); pag->min_block = XFS_AGFL_BLOCK(mp);
__xfs_agino_range(mp, pag->block_count, &pag->agino_min, __xfs_agino_range(mp, pag->block_count, &pag->agino_min,
&pag->agino_max); &pag->agino_max);
} }
index = xfs_set_inode_alloc(mp, agcount); index = xfs_set_inode_alloc(mp, new_agcount);
if (maxagi) if (maxagi)
*maxagi = index; *maxagi = index;
@ -381,8 +363,7 @@ out_remove_pag:
out_free_pag: out_free_pag:
kfree(pag); kfree(pag);
out_unwind_new_pags: out_unwind_new_pags:
/* unwind any prior newly initialized pags */ xfs_free_perag_range(mp, old_agcount, index);
xfs_free_unused_perag_range(mp, first_initialised, agcount);
return error; return error;
} }

View File

@ -144,12 +144,13 @@ __XFS_AG_OPSTATE(prefers_metadata, PREFERS_METADATA)
__XFS_AG_OPSTATE(allows_inodes, ALLOWS_INODES) __XFS_AG_OPSTATE(allows_inodes, ALLOWS_INODES)
__XFS_AG_OPSTATE(agfl_needs_reset, AGFL_NEEDS_RESET) __XFS_AG_OPSTATE(agfl_needs_reset, AGFL_NEEDS_RESET)
void xfs_free_unused_perag_range(struct xfs_mount *mp, xfs_agnumber_t agstart, int xfs_initialize_perag(struct xfs_mount *mp, xfs_agnumber_t old_agcount,
xfs_agnumber_t agend); xfs_agnumber_t agcount, xfs_rfsblock_t dcount,
int xfs_initialize_perag(struct xfs_mount *mp, xfs_agnumber_t agcount, xfs_agnumber_t *maxagi);
xfs_rfsblock_t dcount, xfs_agnumber_t *maxagi); void xfs_free_perag_range(struct xfs_mount *mp, xfs_agnumber_t first_agno,
xfs_agnumber_t end_agno);
int xfs_initialize_perag_data(struct xfs_mount *mp, xfs_agnumber_t agno); int xfs_initialize_perag_data(struct xfs_mount *mp, xfs_agnumber_t agno);
void xfs_free_perag(struct xfs_mount *mp); int xfs_update_last_ag_size(struct xfs_mount *mp, xfs_agnumber_t prev_agcount);
/* Passive AG references */ /* Passive AG references */
struct xfs_perag *xfs_perag_get(struct xfs_mount *mp, xfs_agnumber_t agno); struct xfs_perag *xfs_perag_get(struct xfs_mount *mp, xfs_agnumber_t agno);

View File

@ -1084,9 +1084,11 @@ xrep_metadata_inode_forks(
return error; return error;
/* Make sure the attr fork looks ok before we delete it. */ /* Make sure the attr fork looks ok before we delete it. */
error = xrep_metadata_inode_subtype(sc, XFS_SCRUB_TYPE_BMBTA); if (xfs_inode_hasattr(sc->ip)) {
if (error) error = xrep_metadata_inode_subtype(sc, XFS_SCRUB_TYPE_BMBTA);
return error; if (error)
return error;
}
/* Clear the reflink flag since metadata never shares. */ /* Clear the reflink flag since metadata never shares. */
if (xfs_is_reflink_inode(sc->ip)) { if (xfs_is_reflink_inode(sc->ip)) {

View File

@ -22,6 +22,9 @@
#include "xfs_inode.h" #include "xfs_inode.h"
#include "xfs_dir2.h" #include "xfs_dir2.h"
#include "xfs_quota.h" #include "xfs_quota.h"
#include "xfs_alloc.h"
#include "xfs_ag.h"
#include "xfs_sb.h"
/* /*
* This is the number of entries in the l_buf_cancel_table used during * This is the number of entries in the l_buf_cancel_table used during
@ -684,6 +687,67 @@ xlog_recover_do_inode_buffer(
return 0; return 0;
} }
/*
* Update the in-memory superblock and perag structures from the primary SB
* buffer.
*
* This is required because transactions running after growfs may require the
* updated values to be set in a previous fully commit transaction.
*/
static int
xlog_recover_do_primary_sb_buffer(
struct xfs_mount *mp,
struct xlog_recover_item *item,
struct xfs_buf *bp,
struct xfs_buf_log_format *buf_f,
xfs_lsn_t current_lsn)
{
struct xfs_dsb *dsb = bp->b_addr;
xfs_agnumber_t orig_agcount = mp->m_sb.sb_agcount;
int error;
xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn);
if (orig_agcount == 0) {
xfs_alert(mp, "Trying to grow file system without AGs");
return -EFSCORRUPTED;
}
/*
* Update the in-core super block from the freshly recovered on-disk one.
*/
xfs_sb_from_disk(&mp->m_sb, dsb);
if (mp->m_sb.sb_agcount < orig_agcount) {
xfs_alert(mp, "Shrinking AG count in log recovery not supported");
return -EFSCORRUPTED;
}
/*
* Growfs can also grow the last existing AG. In this case we also need
* to update the length in the in-core perag structure and values
* depending on it.
*/
error = xfs_update_last_ag_size(mp, orig_agcount);
if (error)
return error;
/*
* Initialize the new perags, and also update various block and inode
* allocator setting based off the number of AGs or total blocks.
* Because of the latter this also needs to happen if the agcount did
* not change.
*/
error = xfs_initialize_perag(mp, orig_agcount, mp->m_sb.sb_agcount,
mp->m_sb.sb_dblocks, &mp->m_maxagi);
if (error) {
xfs_warn(mp, "Failed recovery per-ag init: %d", error);
return error;
}
mp->m_alloc_set_aside = xfs_alloc_set_aside(mp);
return 0;
}
/* /*
* V5 filesystems know the age of the buffer on disk being recovered. We can * V5 filesystems know the age of the buffer on disk being recovered. We can
* have newer objects on disk than we are replaying, and so for these cases we * have newer objects on disk than we are replaying, and so for these cases we
@ -967,6 +1031,12 @@ xlog_recover_buf_commit_pass2(
dirty = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f); dirty = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f);
if (!dirty) if (!dirty)
goto out_release; goto out_release;
} else if ((xfs_blft_from_flags(buf_f) & XFS_BLFT_SB_BUF) &&
xfs_buf_daddr(bp) == 0) {
error = xlog_recover_do_primary_sb_buffer(mp, item, bp, buf_f,
current_lsn);
if (error)
goto out_release;
} else { } else {
xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn);
} }

View File

@ -87,6 +87,7 @@ xfs_growfs_data_private(
struct xfs_mount *mp, /* mount point for filesystem */ struct xfs_mount *mp, /* mount point for filesystem */
struct xfs_growfs_data *in) /* growfs data input struct */ struct xfs_growfs_data *in) /* growfs data input struct */
{ {
xfs_agnumber_t oagcount = mp->m_sb.sb_agcount;
struct xfs_buf *bp; struct xfs_buf *bp;
int error; int error;
xfs_agnumber_t nagcount; xfs_agnumber_t nagcount;
@ -94,7 +95,6 @@ xfs_growfs_data_private(
xfs_rfsblock_t nb, nb_div, nb_mod; xfs_rfsblock_t nb, nb_div, nb_mod;
int64_t delta; int64_t delta;
bool lastag_extended = false; bool lastag_extended = false;
xfs_agnumber_t oagcount;
struct xfs_trans *tp; struct xfs_trans *tp;
struct aghdr_init_data id = {}; struct aghdr_init_data id = {};
struct xfs_perag *last_pag; struct xfs_perag *last_pag;
@ -138,16 +138,14 @@ xfs_growfs_data_private(
if (delta == 0) if (delta == 0)
return 0; return 0;
oagcount = mp->m_sb.sb_agcount; /* TODO: shrinking the entire AGs hasn't yet completed */
/* allocate the new per-ag structures */ if (nagcount < oagcount)
if (nagcount > oagcount) {
error = xfs_initialize_perag(mp, nagcount, nb, &nagimax);
if (error)
return error;
} else if (nagcount < oagcount) {
/* TODO: shrinking the entire AGs hasn't yet completed */
return -EINVAL; return -EINVAL;
}
/* allocate the new per-ag structures */
error = xfs_initialize_perag(mp, oagcount, nagcount, nb, &nagimax);
if (error)
return error;
if (delta > 0) if (delta > 0)
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_growdata, error = xfs_trans_alloc(mp, &M_RES(mp)->tr_growdata,
@ -231,7 +229,7 @@ out_trans_cancel:
xfs_trans_cancel(tp); xfs_trans_cancel(tp);
out_free_unused_perag: out_free_unused_perag:
if (nagcount > oagcount) if (nagcount > oagcount)
xfs_free_unused_perag_range(mp, oagcount, nagcount); xfs_free_perag_range(mp, oagcount, nagcount);
return error; return error;
} }

View File

@ -3393,13 +3393,6 @@ xlog_do_recover(
/* re-initialise in-core superblock and geometry structures */ /* re-initialise in-core superblock and geometry structures */
mp->m_features |= xfs_sb_version_to_features(sbp); mp->m_features |= xfs_sb_version_to_features(sbp);
xfs_reinit_percpu_counters(mp); xfs_reinit_percpu_counters(mp);
error = xfs_initialize_perag(mp, sbp->sb_agcount, sbp->sb_dblocks,
&mp->m_maxagi);
if (error) {
xfs_warn(mp, "Failed post-recovery per-ag init: %d", error);
return error;
}
mp->m_alloc_set_aside = xfs_alloc_set_aside(mp);
/* Normal transactions can now occur */ /* Normal transactions can now occur */
clear_bit(XLOG_ACTIVE_RECOVERY, &log->l_opstate); clear_bit(XLOG_ACTIVE_RECOVERY, &log->l_opstate);

View File

@ -810,8 +810,8 @@ xfs_mountfs(
/* /*
* Allocate and initialize the per-ag data. * Allocate and initialize the per-ag data.
*/ */
error = xfs_initialize_perag(mp, sbp->sb_agcount, mp->m_sb.sb_dblocks, error = xfs_initialize_perag(mp, 0, sbp->sb_agcount,
&mp->m_maxagi); mp->m_sb.sb_dblocks, &mp->m_maxagi);
if (error) { if (error) {
xfs_warn(mp, "Failed per-ag init: %d", error); xfs_warn(mp, "Failed per-ag init: %d", error);
goto out_free_dir; goto out_free_dir;
@ -1048,7 +1048,7 @@ xfs_mountfs(
xfs_buftarg_drain(mp->m_logdev_targp); xfs_buftarg_drain(mp->m_logdev_targp);
xfs_buftarg_drain(mp->m_ddev_targp); xfs_buftarg_drain(mp->m_ddev_targp);
out_free_perag: out_free_perag:
xfs_free_perag(mp); xfs_free_perag_range(mp, 0, mp->m_sb.sb_agcount);
out_free_dir: out_free_dir:
xfs_da_unmount(mp); xfs_da_unmount(mp);
out_remove_uuid: out_remove_uuid:
@ -1129,8 +1129,7 @@ xfs_unmountfs(
xfs_errortag_clearall(mp); xfs_errortag_clearall(mp);
#endif #endif
shrinker_free(mp->m_inodegc_shrinker); shrinker_free(mp->m_inodegc_shrinker);
xfs_free_perag(mp); xfs_free_perag_range(mp, 0, mp->m_sb.sb_agcount);
xfs_errortag_del(mp); xfs_errortag_del(mp);
xfs_error_sysfs_del(mp); xfs_error_sysfs_del(mp);
xchk_stats_unregister(mp->m_scrub_stats); xchk_stats_unregister(mp->m_scrub_stats);