]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfs: fix allocated inodes that show up in the unlinked list
authorDarrick J. Wong <djwong@kernel.org>
Mon, 27 Jul 2026 05:26:06 +0000 (22:26 -0700)
committerCarlos Maiolino <cem@kernel.org>
Mon, 3 Aug 2026 08:20:43 +0000 (10:20 +0200)
If an allocated inode shows up in the unlinked list, we need to get it
completely off the list.  Set the corrected next/prev pointers such that
the inode will not look like it should be on an unlinked list at all.

Cc: stable@vger.kernel.org # v6.10
Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
fs/xfs/scrub/agheader_repair.c
fs/xfs/scrub/trace.h

index 8514d2d7e3cea3c0e1e8e68d95b83b5590e4dfbc..41de5cf87352b4383c262597cccf75b62739f90a 100644 (file)
@@ -979,6 +979,13 @@ err:
        return error;
 }
 
+/*
+ * Magic value that means "not unlinked" because xfarrays don't support storing
+ * totally zeroed elements.  There can't be a cluster that starts in daddr 0 so
+ * there can't be an inode #1 either.
+ */
+#define LINKED_AGINO   (0x1)
+
 /*
  * Record a forwards unlinked chain pointer from agino -> next_agino in our
  * staging information.
@@ -1362,6 +1369,35 @@ xrep_iunlink_resolve_bucket(
                        break;
                }
 
+               if (VFS_I(ip)->i_nlink != 0) {
+                       /*
+                        * Inode is linked somewhere!  Blow out both unlinked
+                        * list pointers, advance the list, and pretend we
+                        * didn't see this inode.  Clear it from iunlink_bmp
+                        * because it's linked.
+                        */
+                       trace_xrep_iunlink_resolve_allocated(sc->sa.pag,
+                                       bucket, prev_agino, next_agino);
+
+                       error = xrep_iunlink_store_next(ragi, next_agino,
+                                       NULLAGINO);
+                       if (error)
+                               return error;
+
+                       error = xrep_iunlink_store_prev(ragi, next_agino,
+                                       LINKED_AGINO);
+                       if (error)
+                               return error;
+
+                       error = xagino_bitmap_clear(&ragi->iunlink_bmp,
+                                       next_agino, 1);
+                       if (error)
+                               return error;
+
+                       next_agino = ip->i_next_unlinked;
+                       continue;
+               }
+
                if (next_agino % XFS_AGI_UNLINKED_BUCKETS != bucket) {
                        /*
                         * Inode is in the wrong bucket.  Advance the list,
@@ -1540,6 +1576,24 @@ xrep_iunlink_rebuild_buckets(
                        xrep_iunlink_add_lost_inodes, ragi);
 }
 
+static inline void
+set_inode_prev_unlinked(
+       struct xfs_inode        *ip,
+       xfs_agino_t             prev_agino)
+{
+       /*
+        * Magic value that means "not unlinked" because xfarrays don't support
+        * storing totally zeroed elements.
+        */
+       if (prev_agino == LINKED_AGINO)
+               prev_agino = 0;
+
+       if (ip->i_prev_unlinked != prev_agino) {
+               trace_xrep_iunlink_relink_prev(ip, prev_agino);
+               ip->i_prev_unlinked = prev_agino;
+       }
+}
+
 /* Update i_next_iunlinked for the inode @agino. */
 STATIC int
 xrep_iunlink_relink_next(
@@ -1573,8 +1627,7 @@ xrep_iunlink_relink_next(
                if (error)
                        goto out_rele;
 
-               trace_xrep_iunlink_relink_prev(ip, prev_agino);
-               ip->i_prev_unlinked = prev_agino;
+               set_inode_prev_unlinked(ip, prev_agino);
        }
 
        /* Update the forward pointer. */
@@ -1641,11 +1694,7 @@ xrep_iunlink_relink_prev(
                ip->i_next_unlinked = next_agino;
        }
 
-       /* Update the backward pointer. */
-       if (ip->i_prev_unlinked != prev_agino) {
-               trace_xrep_iunlink_relink_prev(ip, prev_agino);
-               ip->i_prev_unlinked = prev_agino;
-       }
+       set_inode_prev_unlinked(ip, prev_agino);
 
 out_rele:
        /*
index d5d39d82749e5ca5daa479f851323eab8c087f32..00fbe1b9c2354fe1302c218d56daf0e01ea82187 100644 (file)
@@ -3542,6 +3542,7 @@ DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_uncached);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_wronglist);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_nolist);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_ok);
+DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_allocated);
 
 TRACE_EVENT(xrep_iunlink_relink_next,
        TP_PROTO(struct xfs_inode *ip, xfs_agino_t next_agino),