xfs: use i_prev_unlinked to distinguish inodes that are not on the unlinked list
authorDarrick J. Wong <djwong@kernel.org>
Mon, 11 Sep 2023 15:39:07 +0000 (08:39 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 12 Sep 2023 17:31:07 +0000 (10:31 -0700)
Alter the definition of i_prev_unlinked slightly to make it more obvious
when an inode with 0 link count is not part of the iunlink bucket lists
rooted in the AGI.  This distinction is necessary because it is not
sufficient to check inode.i_nlink to decide if an inode is on the
unlinked list.  Updates to i_nlink can happen while holding only
ILOCK_EXCL, but updates to an inode's position in the AGI unlinked list
(which happen after the nlink update) requires both ILOCK_EXCL and the
AGI buffer lock.

The next few patches will make it possible to reload an entire unlinked
bucket list when we're walking the inode table or performing handle
operations and need more than the ability to iget the last inode in the
chain.

The upcoming directory repair code also needs to be able to make this
distinction to decide if a zero link count directory should be moved to
the orphanage or allowed to inactivate.  An upcoming enhancement to the
online AGI fsck code will need this distinction to check and rebuild the
AGI unlinked buckets.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
fs/xfs/xfs_icache.c
fs/xfs/xfs_inode.c
fs/xfs/xfs_inode.h

index 30d7454a9b9377563118a9d5e70921a7a7f90b7c..3c210ac8371368bc00a77fb9df52315b710d17ce 100644 (file)
@@ -113,7 +113,7 @@ xfs_inode_alloc(
        INIT_LIST_HEAD(&ip->i_ioend_list);
        spin_lock_init(&ip->i_ioend_lock);
        ip->i_next_unlinked = NULLAGINO;
-       ip->i_prev_unlinked = NULLAGINO;
+       ip->i_prev_unlinked = 0;
 
        return ip;
 }
index 7b11059067f75170e7057d70b89f1085e066388c..475de8f919bee208553a89f07f5ef3d8e2a32c52 100644 (file)
@@ -2014,6 +2014,7 @@ xfs_iunlink_insert_inode(
        }
 
        /* Point the head of the list to point to this inode. */
+       ip->i_prev_unlinked = NULLAGINO;
        return xfs_iunlink_update_bucket(tp, pag, agibp, bucket_index, agino);
 }
 
@@ -2116,7 +2117,7 @@ xfs_iunlink_remove_inode(
        }
 
        ip->i_next_unlinked = NULLAGINO;
-       ip->i_prev_unlinked = NULLAGINO;
+       ip->i_prev_unlinked = 0;
        return error;
 }
 
index 7547caf2f2abd525010aa046d8b1e93e29d599dd..65aae89255098c6f03a70401ea4fe210f65ee704 100644 (file)
@@ -68,8 +68,21 @@ typedef struct xfs_inode {
        uint64_t                i_diflags2;     /* XFS_DIFLAG2_... */
        struct timespec64       i_crtime;       /* time created */
 
-       /* unlinked list pointers */
+       /*
+        * Unlinked list pointers.  These point to the next and previous inodes
+        * in the AGI unlinked bucket list, respectively.  These fields can
+        * only be updated with the AGI locked.
+        *
+        * i_next_unlinked caches di_next_unlinked.
+        */
        xfs_agino_t             i_next_unlinked;
+
+       /*
+        * If the inode is not on an unlinked list, this field is zero.  If the
+        * inode is the first element in an unlinked list, this field is
+        * NULLAGINO.  Otherwise, i_prev_unlinked points to the previous inode
+        * in the unlinked list.
+        */
        xfs_agino_t             i_prev_unlinked;
 
        /* VFS inode */
@@ -81,6 +94,11 @@ typedef struct xfs_inode {
        struct list_head        i_ioend_list;
 } xfs_inode_t;
 
+static inline bool xfs_inode_on_unlinked_list(const struct xfs_inode *ip)
+{
+       return ip->i_prev_unlinked != 0;
+}
+
 static inline bool xfs_inode_has_attr_fork(struct xfs_inode *ip)
 {
        return ip->i_forkoff > 0;