* FAILED: patch "[PATCH] xfs: don't livelock in scrub on a circular unlinked list" failed to apply to 7.1-stable tree
@ 2026-08-20 11:37 gregkh
2026-08-23 18:03 ` [PATCH 7.1.y 1/2] xfs: hoist per-bucket unlinked list check to helper Sasha Levin
0 siblings, 1 reply; 3+ messages in thread
From: gregkh @ 2026-08-20 11:37 UTC (permalink / raw)
To: djwong, cem, hch; +Cc: stable
The patch below does not apply to the 7.1-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 <stable@vger.kernel.org>.
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-7.1.y
git checkout FETCH_HEAD
git cherry-pick -x 527eaaefddb6ec5c83a06c9a1559960dd6361753
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026082043-tameness-radiated-971a@gregkh' --subject-prefix 'PATCH 7.1.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" <djwong@kernel.org>
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.
Cc: stable@vger.kernel.org # v4.15
Fixes: a12890aebb8959 ("xfs: scrub the AGI")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c
index cecf034ef989..1fa66aa68e16 100644
--- a/fs/xfs/scrub/agheader.c
+++ b/fs/xfs/scrub/agheader.c
@@ -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 int
xchk_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:
diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c
index 2554494847ff..13074d5e319c 100644
--- a/fs/xfs/scrub/agheader_repair.c
+++ b/fs/xfs/scrub/agheader_repair.c
@@ -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. */
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 7.1.y 1/2] xfs: hoist per-bucket unlinked list check to helper
2026-08-20 11:37 FAILED: patch "[PATCH] xfs: don't livelock in scrub on a circular unlinked list" failed to apply to 7.1-stable tree gregkh
@ 2026-08-23 18:03 ` Sasha Levin
2026-08-23 18:03 ` [PATCH 7.1.y 2/2] xfs: don't livelock in scrub on a circular unlinked list Sasha Levin
0 siblings, 1 reply; 3+ messages in thread
From: Sasha Levin @ 2026-08-23 18:03 UTC (permalink / raw)
To: stable; +Cc: Darrick J. Wong, Christoph Hellwig, Carlos Maiolino, Sasha Levin
From: "Darrick J. Wong" <djwong@kernel.org>
[ 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.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
Stable-dep-of: 527eaaefddb6 ("xfs: don't livelock in scrub on a circular unlinked list")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
| 64 +++++++++++++++++++++++++++--------------
1 file changed, 42 insertions(+), 22 deletions(-)
--git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c
index 62ed5eaf08fbc..cecf034ef989c 100644
--- a/fs/xfs/scrub/agheader.c
+++ b/fs/xfs/scrub/agheader.c
@@ -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;
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 7.1.y 2/2] xfs: don't livelock in scrub on a circular unlinked list
2026-08-23 18:03 ` [PATCH 7.1.y 1/2] xfs: hoist per-bucket unlinked list check to helper Sasha Levin
@ 2026-08-23 18:03 ` Sasha Levin
0 siblings, 0 replies; 3+ messages in thread
From: Sasha Levin @ 2026-08-23 18:03 UTC (permalink / raw)
To: stable; +Cc: Darrick J. Wong, Christoph Hellwig, Carlos Maiolino, Sasha Levin
From: "Darrick J. Wong" <djwong@kernel.org>
[ 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.
Cc: stable@vger.kernel.org # v4.15
Fixes: a12890aebb8959 ("xfs: scrub the AGI")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
| 46 +++++++++++++++++++++++++++-------
| 17 +++++++++++--
2 files changed, 52 insertions(+), 11 deletions(-)
--git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c
index cecf034ef989c..1fa66aa68e169 100644
--- a/fs/xfs/scrub/agheader.c
+++ b/fs/xfs/scrub/agheader.c
@@ -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 int
xchk_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:
--git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c
index c2acd77482699..59375b759ba8f 100644
--- a/fs/xfs/scrub/agheader_repair.c
+++ b/fs/xfs/scrub/agheader_repair.c
@@ -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. */
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-23 18:03 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 11:37 FAILED: patch "[PATCH] xfs: don't livelock in scrub on a circular unlinked list" failed to apply to 7.1-stable tree gregkh
2026-08-23 18:03 ` [PATCH 7.1.y 1/2] xfs: hoist per-bucket unlinked list check to helper Sasha Levin
2026-08-23 18:03 ` [PATCH 7.1.y 2/2] xfs: don't livelock in scrub on a circular unlinked list Sasha Levin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.