xfs: move files to orphanage instead of letting nlinks drop to zero
authorDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:54:56 +0000 (14:54 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:58:57 +0000 (14:58 -0700)
If we encounter an inode with a nonzero link count but zero observed
links, move it to the orphanage.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Documentation/filesystems/xfs/xfs-online-fsck-design.rst
fs/xfs/scrub/nlinks.c
fs/xfs/scrub/nlinks.h
fs/xfs/scrub/nlinks_repair.c
fs/xfs/scrub/repair.h
fs/xfs/scrub/trace.c
fs/xfs/scrub/trace.h

index 37dddaaeda50c436e92420e2cf221959bba0a4f9..74a8e42c74bd04e22d91eb8b4ed0b376a6836a80 100644 (file)
@@ -4789,7 +4789,8 @@ Orphaned files are adopted by the orphanage as follows:
    cache.
 
 6. Call ``xrep_adoption_finish`` to commit any filesystem updates, release the
-   orphanage ILOCK, and clean the scrub transaction.
+   orphanage ILOCK, and clean the scrub transaction.  Call
+   ``xrep_adoption_commit`` to commit the updates and the scrub transaction.
 
 7. If a runtime error happens, call ``xrep_adoption_cancel`` to release all
    resources.
index 8b9aa73093d667b15088aae43a989f1e18c215ec..c456523fac9c6f8679e9f5077b9495636cc2dab8 100644 (file)
@@ -24,6 +24,7 @@
 #include "scrub/xfile.h"
 #include "scrub/xfarray.h"
 #include "scrub/iscan.h"
+#include "scrub/orphanage.h"
 #include "scrub/nlinks.h"
 #include "scrub/trace.h"
 #include "scrub/readdir.h"
@@ -44,11 +45,23 @@ int
 xchk_setup_nlinks(
        struct xfs_scrub        *sc)
 {
+       struct xchk_nlink_ctrs  *xnc;
+       int                     error;
+
        xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);
 
-       sc->buf = kzalloc(sizeof(struct xchk_nlink_ctrs), XCHK_GFP_FLAGS);
-       if (!sc->buf)
+       if (xchk_could_repair(sc)) {
+               error = xrep_setup_nlinks(sc);
+               if (error)
+                       return error;
+       }
+
+       xnc = kvzalloc(sizeof(struct xchk_nlink_ctrs), XCHK_GFP_FLAGS);
+       if (!xnc)
                return -ENOMEM;
+       xnc->xname.name = xnc->namebuf;
+       xnc->sc = sc;
+       sc->buf = xnc;
 
        return xchk_setup_fs(sc);
 }
@@ -873,9 +886,6 @@ xchk_nlinks_setup_scan(
        xfs_agino_t             first_agino, last_agino;
        int                     error;
 
-       ASSERT(xnc->sc == NULL);
-       xnc->sc = sc;
-
        mutex_init(&xnc->lock);
 
        /* Retry iget every tenth of a second for up to 30 seconds. */
index a950f3daf204cd7cbb1d1fae4631be94df006d2e..b820712bfd87ce4b68ac39c2d1270b656a0a288a 100644 (file)
@@ -28,6 +28,13 @@ struct xchk_nlink_ctrs {
         * from other writer threads.
         */
        struct xfs_dir_hook     dhook;
+
+       /* Orphanage reparenting request. */
+       struct xrep_adoption    adoption;
+
+       /* Directory entry name, plus the trailing null. */
+       struct xfs_name         xname;
+       char                    namebuf[MAXNAMELEN];
 };
 
 /*
index 23eb08c4b5ad599cc2d342931ecbb06342c857db..0cb67339eac8964823447a08c9aa2744d8711b70 100644 (file)
@@ -24,6 +24,7 @@
 #include "scrub/xfile.h"
 #include "scrub/xfarray.h"
 #include "scrub/iscan.h"
+#include "scrub/orphanage.h"
 #include "scrub/nlinks.h"
 #include "scrub/trace.h"
 #include "scrub/tempfile.h"
  * inode is locked.
  */
 
+/* Set up to repair inode link counts. */
+int
+xrep_setup_nlinks(
+       struct xfs_scrub        *sc)
+{
+       return xrep_orphanage_try_create(sc);
+}
+
+/*
+ * Inodes that aren't the root directory or the orphanage, have a nonzero link
+ * count, and no observed parents should be moved to the orphanage.
+ */
+static inline bool
+xrep_nlinks_is_orphaned(
+       struct xfs_scrub        *sc,
+       struct xfs_inode        *ip,
+       unsigned int            actual_nlink,
+       const struct xchk_nlink *obs)
+{
+       struct xfs_mount        *mp = ip->i_mount;
+
+       if (obs->parents != 0)
+               return false;
+       if (ip == mp->m_rootip || ip == sc->orphanage)
+               return false;
+       return actual_nlink != 0;
+}
+
 /* Remove an inode from the unlinked list. */
 STATIC int
 xrep_nlinks_iunlink_remove(
@@ -66,6 +95,7 @@ xrep_nlinks_repair_inode(
        struct xfs_inode        *ip = sc->ip;
        uint64_t                total_links;
        uint64_t                actual_nlink;
+       bool                    orphanage_available = false;
        bool                    dirty = false;
        int                     error;
 
@@ -77,14 +107,41 @@ xrep_nlinks_repair_inode(
        if (xrep_is_tempfile(ip))
                return 0;
 
-       xchk_ilock(sc, XFS_IOLOCK_EXCL);
+       /*
+        * If the filesystem has an orphanage attached to the scrub context,
+        * prepare for a link count repair that could involve @ip being adopted
+        * by the lost+found.
+        */
+       if (xrep_orphanage_can_adopt(sc)) {
+               error = xrep_orphanage_iolock_two(sc);
+               if (error)
+                       return error;
+
+               error = xrep_adoption_trans_alloc(sc, &xnc->adoption);
+               if (error) {
+                       xchk_iunlock(sc, XFS_IOLOCK_EXCL);
+                       xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
+               } else {
+                       orphanage_available = true;
+               }
+       }
 
-       error = xfs_trans_alloc(mp, &M_RES(mp)->tr_link, 0, 0, 0, &sc->tp);
-       if (error)
-               return error;
+       /*
+        * Either there is no orphanage or we couldn't allocate resources for
+        * that kind of update.  Let's try again with only the resources we
+        * need for a simple link count update, since that's much more common.
+        */
+       if (!orphanage_available) {
+               xchk_ilock(sc, XFS_IOLOCK_EXCL);
+
+               error = xfs_trans_alloc(mp, &M_RES(mp)->tr_link, 0, 0, 0,
+                               &sc->tp);
+               if (error)
+                       return error;
 
-       xchk_ilock(sc, XFS_ILOCK_EXCL);
-       xfs_trans_ijoin(sc->tp, ip, 0);
+               xchk_ilock(sc, XFS_ILOCK_EXCL);
+               xfs_trans_ijoin(sc->tp, ip, 0);
+       }
 
        mutex_lock(&xnc->lock);
 
@@ -122,6 +179,41 @@ xrep_nlinks_repair_inode(
                goto out_trans;
        }
 
+       /*
+        * Decide if we're going to move this file to the orphanage, and fix
+        * up the incore link counts if we are.
+        */
+       if (orphanage_available &&
+           xrep_nlinks_is_orphaned(sc, ip, actual_nlink, &obs)) {
+               /* Figure out what name we're going to use here. */
+               error = xrep_adoption_compute_name(&xnc->adoption, &xnc->xname);
+               if (error)
+                       goto out_trans;
+
+               /*
+                * Reattach this file to the directory tree by moving it to
+                * the orphanage per the adoption parameters that we already
+                * computed.
+                */
+               error = xrep_adoption_move(&xnc->adoption);
+               if (error)
+                       goto out_trans;
+
+               /*
+                * Re-read the link counts since the reparenting will have
+                * updated our scan info.
+                */
+               mutex_lock(&xnc->lock);
+               error = xfarray_load_sparse(xnc->nlinks, ip->i_ino, &obs);
+               mutex_unlock(&xnc->lock);
+               if (error)
+                       goto out_trans;
+
+               total_links = xchk_nlink_total(ip, &obs);
+               actual_nlink = VFS_I(ip)->i_nlink;
+               dirty = true;
+       }
+
        /*
         * If this inode is linked from the directory tree and on the unlinked
         * list, remove it from the unlinked list.
@@ -165,14 +257,19 @@ xrep_nlinks_repair_inode(
        xfs_trans_log_inode(sc->tp, ip, XFS_ILOG_CORE);
 
        error = xrep_trans_commit(sc);
-       xchk_iunlock(sc, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL);
-       return error;
+       goto out_unlock;
 
 out_scanlock:
        mutex_unlock(&xnc->lock);
 out_trans:
        xchk_trans_cancel(sc);
-       xchk_iunlock(sc, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL);
+out_unlock:
+       xchk_iunlock(sc, XFS_ILOCK_EXCL);
+       if (orphanage_available) {
+               xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
+               xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
+       }
+       xchk_iunlock(sc, XFS_IOLOCK_EXCL);
        return error;
 }
 
@@ -205,10 +302,10 @@ xrep_nlinks(
        /*
         * We need ftype for an accurate count of the number of child
         * subdirectory links.  Child subdirectories with a back link (dotdot
-        * entry) but no forward link are unfixable, so we cannot repair the
-        * link count of the parent directory based on the back link count
-        * alone.  Filesystems without ftype support are rare (old V4) so we
-        * just skip out here.
+        * entry) but no forward link are moved to the orphanage, so we cannot
+        * repair the link count of the parent directory based on the back link
+        * count alone.  Filesystems without ftype support are rare (old V4) so
+        * we just skip out here.
         */
        if (!xfs_has_ftype(sc->mp))
                return -EOPNOTSUPP;
index e53374fa5430824c80c4812753deac3419efe89b..7e6aba7fe5586ba613668f4087569e15401a3a31 100644 (file)
@@ -93,6 +93,7 @@ int xrep_setup_ag_refcountbt(struct xfs_scrub *sc);
 int xrep_setup_xattr(struct xfs_scrub *sc);
 int xrep_setup_directory(struct xfs_scrub *sc);
 int xrep_setup_parent(struct xfs_scrub *sc);
+int xrep_setup_nlinks(struct xfs_scrub *sc);
 
 /* Repair setup functions */
 int xrep_setup_ag_allocbt(struct xfs_scrub *sc);
@@ -201,6 +202,7 @@ xrep_setup_nothing(
 #define xrep_setup_xattr               xrep_setup_nothing
 #define xrep_setup_directory           xrep_setup_nothing
 #define xrep_setup_parent              xrep_setup_nothing
+#define xrep_setup_nlinks              xrep_setup_nothing
 
 #define xrep_setup_inode(sc, imap)     ((void)0)
 
index 3dd281d6d1854011cdb8e58cb6d4c7b9edeaae3f..b2ce7b22cad348067a480c068bd1f92a5aa73adc 100644 (file)
@@ -24,6 +24,7 @@
 #include "scrub/xfarray.h"
 #include "scrub/quota.h"
 #include "scrub/iscan.h"
+#include "scrub/orphanage.h"
 #include "scrub/nlinks.h"
 #include "scrub/fscounters.h"
 
index 7c49aa6f6b8da6ed5b0d473f03c8067a732cdbbe..2a4c54f7992a284238051c3eb4726b8e8c00298c 100644 (file)
@@ -2643,6 +2643,32 @@ DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_dir_salvaged_parent);
 DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_findparent_dirent);
 DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_findparent_from_dcache);
 
+TRACE_EVENT(xrep_nlinks_set_record,
+       TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino,
+                const struct xchk_nlink *obs),
+       TP_ARGS(mp, ino, obs),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(xfs_ino_t, ino)
+               __field(xfs_nlink_t, parents)
+               __field(xfs_nlink_t, backrefs)
+               __field(xfs_nlink_t, children)
+       ),
+       TP_fast_assign(
+               __entry->dev = mp->m_super->s_dev;
+               __entry->ino = ino;
+               __entry->parents = obs->parents;
+               __entry->backrefs = obs->backrefs;
+               __entry->children = obs->children;
+       ),
+       TP_printk("dev %d:%d ino 0x%llx parents %u backrefs %u children %u",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 __entry->ino,
+                 __entry->parents,
+                 __entry->backrefs,
+                 __entry->children)
+);
+
 #endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */
 
 #endif /* _TRACE_XFS_SCRUB_TRACE_H */