FAILED: patch "[PATCH] xfs: don't livelock in scrub on a circular unlinked list" failed to apply to 6.18-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 527eaaefddb6ec5c83a06c9a1559960dd6361753
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026082043-rely-pentagram-07de@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 527eaaefddb6ec5c83a06c9a1559960dd6361753 Mon Sep 17 00:00:00 2001
From: "Darrick J. Wong" <[email protected]>
Date: Sun, 26 Jul 2026 22:24:48 -0700
Subject: [PATCH] xfs: don't livelock in scrub on a circular unlinked list
LOLLM points out that online fsck can livelock if an unlinked inode list contains a loop. Use a bitmap to detect cycles.
fs/xfs/scrub/agheader.c +36 -8
@@ -18,6 +18,8 @@#include "xfs_inode.h" #include "scrub/scrub.h" #include "scrub/common.h" +#include "scrub/bitmap.h" +#include "scrub/agino_bitmap.h" int xchk_setup_agheader(
@@ -935,7 +937,8 @@ xchk_agi_xref(/* * Walk the incore unlinked list for a particular AGI bucket to construct * the unlinked inode bitmap for later reconstruction of the unlinked list. -* Returns 1 if we should keep checking, or 0 to stop checking. +* Returns 1 if we should keep checking, 0 to stop checking, or a negative +* errno. */ static int xchk_iunlink_bucket(
@@ -943,36 +946,57 @@ xchk_iunlink_bucket( unsigned int bucket, xfs_agino_t agino) { +struct xagino_bitmap seen; +int ret; + +xagino_bitmap_init (&seen); +while (agino !=NULLAGINO ) {struct xfs_inode *ip; +unsigned int len =1 ;if (agino %XFS_AGI_UNLINKED_BUCKETS != bucket) {xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return 0 ; + goto bad; + } + +if (xagino_bitmap_test (&seen, agino, &len)) { +xchk_block_set_corrupt (sc, sc->sa.agi_bp); + goto bad; } ip =xfs_iunlink_lookup (sc->sa.pag, agino);if (!ip) {xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return 0 ; + goto bad; }if (!xfs_inode_on_unlinked_list (ip)) {xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return 0 ; + goto bad; } + ret =xagino_bitmap_set (&seen, agino,1 ); +if (ret) + goto out_bitmap; + agino = ip->i_next_unlinked; } + ret =1 ; -return 1 ; +out_bitmap: +xagino_bitmap_destroy (&seen); +return ret; +bad: + ret =0 ; + goto out_bitmap; }/* * Check the unlinked buckets for links to bad inodes. We hold the AGI, so * there cannot be any threads updating unlinked list pointers in this AG. */ -STATIC void +STATIC intxchk_iunlink (struct xfs_scrub *sc,struct xfs_agi *agi)
@@ -985,8 +1009,10 @@ xchk_iunlink( ret =xchk_iunlink_bucket (sc, i,be32_to_cpu (agi->agi_unlinked[i]));if (ret <1 ) -return ; +return ret; } + +return 0 ; }/* Scrub the AGI. */
@@ -1073,7 +1099,9 @@ xchk_agi(if (pag->pagi_freecount !=be32_to_cpu (agi->agi_freecount))xchk_block_set_corrupt (sc, sc->sa.agi_bp); -xchk_iunlink (sc, agi); + error =xchk_iunlink (sc, agi); +if (error) + goto out;xchk_agi_xref (sc); out:
fs/xfs/scrub/agheader_repair.c +15 -2
@@ -1080,18 +1080,22 @@ xrep_iunlink_walk_ondisk_bucket( struct xrep_agi *ragi, unsigned int bucket) { +struct xagino_bitmap seen;struct xfs_scrub *sc = ragi->sc;struct xfs_agi *agi = sc->sa.agi_bp->b_addr;xfs_agino_t prev_agino =NULLAGINO ;xfs_agino_t next_agino;int error =0 ; +xagino_bitmap_init (&seen); + next_agino =be32_to_cpu (agi->agi_unlinked[bucket]);while (next_agino !=NULLAGINO ) {xfs_agino_t agino = next_agino; +unsigned int len =1 ;if (xchk_should_terminate (ragi->sc, &error)) -return error; + goto out_bitmap;trace_xrep_iunlink_walk_ondisk_bucket (sc->sa.pag, bucket, prev_agino, agino);
@@ -1099,15 +1103,24 @@ xrep_iunlink_walk_ondisk_bucket(if (bucket != agino %XFS_AGI_UNLINKED_BUCKETS )break ; +if (xagino_bitmap_test (&seen, agino, &len)) +break ; + next_agino =xrep_iunlink_next (sc, agino);if (!next_agino) next_agino =xrep_iunlink_reload_next (ragi, prev_agino, agino); + error =xagino_bitmap_set (&seen, agino,1 ); +if (error) + goto out_bitmap; + prev_agino = agino; } -return 0 ; +out_bitmap: +xagino_bitmap_destroy (&seen); +return error; }/* Decide if this is an unlinked inode in this AG. */
Sasha Levin
maintainerPATCH 1/2 xfs: hoist per-bucket unlinked list check to helper
From: "Darrick J. Wong" <[email protected]>
[ Upstream commit 7cdafd8f10ebdf745ba6046b9fa67490c343a17f ]
In the next patch we're going to make this loop more exciting, so hoist the code to a helper function to reduce clutter in the resulting code.
Stable-dep-of: 527eaaefddb6 ("xfs: don't livelock in scrub on a circular unlinked list")
--- fs/xfs/scrub/agheader.c | 64 +++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 22 deletions(-)
fs/xfs/scrub/agheader.c +42 -22
@@ -932,6 +932,42 @@ xchk_agi_xref(/* scrub teardown will take care of sc->sa for us */ } +/* +* Walk the incore unlinked list for a particular AGI bucket to construct +* the unlinked inode bitmap for later reconstruction of the unlinked list. +* Returns 1 if we should keep checking, or 0 to stop checking. +*/ +static int +xchk_iunlink_bucket ( +struct xfs_scrub *sc, +unsigned int bucket, +xfs_agino_t agino) +{ +while (agino !=NULLAGINO ) { +struct xfs_inode *ip; + +if (agino %XFS_AGI_UNLINKED_BUCKETS != bucket) { +xchk_block_set_corrupt (sc, sc->sa.agi_bp); +return 0 ; + } + + ip =xfs_iunlink_lookup (sc->sa.pag, agino); +if (!ip) { +xchk_block_set_corrupt (sc, sc->sa.agi_bp); +return 0 ; + } + +if (!xfs_inode_on_unlinked_list (ip)) { +xchk_block_set_corrupt (sc, sc->sa.agi_bp); +return 0 ; + } + + agino = ip->i_next_unlinked; + } + +return 1 ; +} +/* * Check the unlinked buckets for links to bad inodes. We hold the AGI, so * there cannot be any threads updating unlinked list pointers in this AG.
@@ -942,30 +978,14 @@ xchk_iunlink( struct xfs_agi *agi) {unsigned int i; -struct xfs_inode *ip;for (i =0 ; i <XFS_AGI_UNLINKED_BUCKETS ; i++) { -xfs_agino_t agino =be32_to_cpu (agi->agi_unlinked[i]); - -while (agino !=NULLAGINO ) { -if (agino %XFS_AGI_UNLINKED_BUCKETS != i) { -xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return ; - } - - ip =xfs_iunlink_lookup (sc->sa.pag, agino); -if (!ip) { -xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return ; - } - -if (!xfs_inode_on_unlinked_list (ip)) { -xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return ; - } - - agino = ip->i_next_unlinked; - } +int ret; + + ret =xchk_iunlink_bucket (sc, i, +be32_to_cpu (agi->agi_unlinked[i])); +if (ret <1 ) +return ; } }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 xfs: don't livelock in scrub on a circular unlinked list
From: "Darrick J. Wong" <[email protected]>
[ Upstream commit 527eaaefddb6ec5c83a06c9a1559960dd6361753 ]
LOLLM points out that online fsck can livelock if an unlinked inode list contains a loop. Use a bitmap to detect cycles.
--- fs/xfs/scrub/agheader.c | 46 +++++++++++++++++++++++++++------- fs/xfs/scrub/agheader_repair.c | 17 +++++++++++-- 2 files changed, 52 insertions(+), 11 deletions(-)
fs/xfs/scrub/agheader.c +37 -9
@@ -18,6 +18,8 @@#include "xfs_inode.h" #include "scrub/scrub.h" #include "scrub/common.h" +#include "scrub/bitmap.h" +#include "scrub/agino_bitmap.h" int xchk_setup_agheader(
@@ -935,7 +937,8 @@ xchk_agi_xref(/* * Walk the incore unlinked list for a particular AGI bucket to construct * the unlinked inode bitmap for later reconstruction of the unlinked list. -* Returns 1 if we should keep checking, or 0 to stop checking. +* Returns 1 if we should keep checking, 0 to stop checking, or a negative +* errno. */ static int xchk_iunlink_bucket(
@@ -943,36 +946,57 @@ xchk_iunlink_bucket( unsigned int bucket, xfs_agino_t agino) { +struct xagino_bitmap seen; +int ret; + +xagino_bitmap_init (&seen); +while (agino !=NULLAGINO ) {struct xfs_inode *ip; +unsigned int len =1 ;if (agino %XFS_AGI_UNLINKED_BUCKETS != bucket) {xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return 0 ; + goto bad; + } + +if (xagino_bitmap_test (&seen, agino, &len)) { +xchk_block_set_corrupt (sc, sc->sa.agi_bp); + goto bad; } ip =xfs_iunlink_lookup (sc->sa.pag, agino);if (!ip) {xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return 0 ; + goto bad; }if (!xfs_inode_on_unlinked_list (ip)) {xchk_block_set_corrupt (sc, sc->sa.agi_bp); -return 0 ; + goto bad; } + ret =xagino_bitmap_set (&seen, agino,1 ); +if (ret) + goto out_bitmap; + agino = ip->i_next_unlinked; } - -return 1 ; + ret =1 ; + +out_bitmap: +xagino_bitmap_destroy (&seen); +return ret; +bad: + ret =0 ; + goto out_bitmap; }/* * Check the unlinked buckets for links to bad inodes. We hold the AGI, so * there cannot be any threads updating unlinked list pointers in this AG. */ -STATIC void +STATIC intxchk_iunlink (struct xfs_scrub *sc,struct xfs_agi *agi)
@@ -985,8 +1009,10 @@ xchk_iunlink( ret =xchk_iunlink_bucket (sc, i,be32_to_cpu (agi->agi_unlinked[i]));if (ret <1 ) -return ; +return ret; } + +return 0 ; }/* Scrub the AGI. */
@@ -1073,7 +1099,9 @@ xchk_agi(if (pag->pagi_freecount !=be32_to_cpu (agi->agi_freecount))xchk_block_set_corrupt (sc, sc->sa.agi_bp); -xchk_iunlink (sc, agi); + error =xchk_iunlink (sc, agi); +if (error) + goto out;xchk_agi_xref (sc); out:
fs/xfs/scrub/agheader_repair.c +15 -2
@@ -1096,18 +1096,22 @@ xrep_iunlink_walk_ondisk_bucket( struct xrep_agi *ragi, unsigned int bucket) { +struct xagino_bitmap seen;struct xfs_scrub *sc = ragi->sc;struct xfs_agi *agi = ragi->agi_bp->b_addr;xfs_agino_t prev_agino =NULLAGINO ;xfs_agino_t next_agino;int error =0 ; +xagino_bitmap_init (&seen); + next_agino =be32_to_cpu (agi->agi_unlinked[bucket]);while (next_agino !=NULLAGINO ) {xfs_agino_t agino = next_agino; +unsigned int len =1 ;if (xchk_should_terminate (ragi->sc, &error)) -return error; + goto out_bitmap;trace_xrep_iunlink_walk_ondisk_bucket (sc->sa.pag, bucket, prev_agino, agino);
@@ -1115,6 +1119,9 @@ xrep_iunlink_walk_ondisk_bucket(if (bucket != agino %XFS_AGI_UNLINKED_BUCKETS )break ; +if (xagino_bitmap_test (&seen, agino, &len)) +break ; + next_agino =xrep_iunlink_next (sc, agino);if (!next_agino) { error = xrep_iunlink_reload_next(ragi, prev_agino,
@@ -1123,10 +1130,16 @@ xrep_iunlink_walk_ondisk_bucket(break ; } + error =xagino_bitmap_set (&seen, agino,1 ); +if (error) + goto out_bitmap; + prev_agino = agino; } -return 0 ; +out_bitmap: +xagino_bitmap_destroy (&seen); +return error; }/* Decide if this is an unlinked inode in this AG. */
Signature · 1 line
2.53.0