xfs: deferred scrub of parent pointers
authorDarrick J. Wong <djwong@kernel.org>
Mon, 22 Apr 2024 16:48:04 +0000 (09:48 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 23 Apr 2024 14:47:02 +0000 (07:47 -0700)
If the trylock-based dirent check fails, retain those parent pointers
and check them at the end.  This may involve dropping the locks on the
file being scanned, so yay.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/Makefile
fs/xfs/scrub/parent.c
fs/xfs/scrub/trace.h

index c969b11ce0f478b13b557ed4c9b3f7a3f57a2e80..af99a455ce4dbccf0b39de399f1ad8cbd2d3c75d 100644 (file)
@@ -177,6 +177,7 @@ xfs-y                               += $(addprefix scrub/, \
                                   scrub.o \
                                   symlink.o \
                                   xfarray.o \
+                                  xfblob.o \
                                   xfile.o \
                                   )
 
@@ -218,7 +219,6 @@ xfs-y                               += $(addprefix scrub/, \
                                   rmap_repair.o \
                                   symlink_repair.o \
                                   tempfile.o \
-                                  xfblob.o \
                                   )
 
 xfs-$(CONFIG_XFS_RT)           += $(addprefix scrub/, \
index 6ebbb7104126965590b5542bc5498a1a892c61e4..f14e6f643fb60261a3e458ec439d560d6945c02e 100644 (file)
@@ -23,6 +23,9 @@
 #include "scrub/tempfile.h"
 #include "scrub/repair.h"
 #include "scrub/listxattr.h"
+#include "scrub/xfile.h"
+#include "scrub/xfarray.h"
+#include "scrub/xfblob.h"
 #include "scrub/trace.h"
 
 /* Set us up to scrub parents. */
@@ -211,6 +214,18 @@ out_rele:
  * forward to the child file.
  */
 
+/* Deferred parent pointer entry that we saved for later. */
+struct xchk_pptr {
+       /* Cookie for retrieval of the pptr name. */
+       xfblob_cookie           name_cookie;
+
+       /* Parent pointer record. */
+       struct xfs_parent_rec   pptr_rec;
+
+       /* Length of the pptr name. */
+       uint8_t                 namelen;
+};
+
 struct xchk_pptrs {
        struct xfs_scrub        *sc;
 
@@ -219,6 +234,22 @@ struct xchk_pptrs {
 
        /* Parent of this directory. */
        xfs_ino_t               parent_ino;
+
+       /* Fixed-size array of xchk_pptr structures. */
+       struct xfarray          *pptr_entries;
+
+       /* Blobs containing parent pointer names. */
+       struct xfblob           *pptr_names;
+
+       /* Scratch buffer for scanning pptr xattrs */
+       struct xfs_da_args      pptr_args;
+
+       /* If we've cycled the ILOCK, we must revalidate all deferred pptrs. */
+       bool                    need_revalidate;
+
+       /* Name buffer */
+       struct xfs_name         xname;
+       char                    namebuf[MAXNAMELEN];
 };
 
 /* Does this parent pointer match the dotdot entry? */
@@ -461,8 +492,25 @@ xchk_parent_scan_attr(
        /* Try to lock the inode. */
        lockmode = xchk_parent_lock_dir(sc, dp);
        if (!lockmode) {
-               xchk_set_incomplete(sc);
-               error = -ECANCELED;
+               struct xchk_pptr        save_pp = {
+                       .pptr_rec       = *pptr_rec, /* struct copy */
+                       .namelen        = namelen,
+               };
+
+               /* Couldn't lock the inode, so save the pptr for later. */
+               trace_xchk_parent_defer(sc->ip, &xname, dp->i_ino);
+
+               error = xfblob_storename(pp->pptr_names, &save_pp.name_cookie,
+                               &xname);
+               if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
+                                       &error))
+                       goto out_rele;
+
+               error = xfarray_append(pp->pptr_entries, &save_pp);
+               if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
+                                       &error))
+                       goto out_rele;
+
                goto out_rele;
        }
 
@@ -477,6 +525,162 @@ out_rele:
        return error;
 }
 
+/*
+ * Revalidate a parent pointer that we collected in the past but couldn't check
+ * because of lock contention.  Returns 0 if the parent pointer is still valid,
+ * -ENOENT if it has gone away on us, or a negative errno.
+ */
+STATIC int
+xchk_parent_revalidate_pptr(
+       struct xchk_pptrs               *pp,
+       const struct xfs_name           *xname,
+       struct xfs_parent_rec           *pptr)
+{
+       struct xfs_scrub                *sc = pp->sc;
+       int                             error;
+
+       error = xfs_parent_lookup(sc->tp, sc->ip, xname, pptr, &pp->pptr_args);
+       if (error == -ENOATTR) {
+               /* Parent pointer went away, nothing to revalidate. */
+               return -ENOENT;
+       }
+
+       return error;
+}
+
+/*
+ * Check a parent pointer the slow way, which means we cycle locks a bunch
+ * and put up with revalidation until we get it done.
+ */
+STATIC int
+xchk_parent_slow_pptr(
+       struct xchk_pptrs       *pp,
+       const struct xfs_name   *xname,
+       struct xfs_parent_rec   *pptr)
+{
+       struct xfs_scrub        *sc = pp->sc;
+       struct xfs_inode        *dp = NULL;
+       unsigned int            lockmode;
+       int                     error;
+
+       /* Check that the deferred parent pointer still exists. */
+       if (pp->need_revalidate) {
+               error = xchk_parent_revalidate_pptr(pp, xname, pptr);
+               if (error == -ENOENT)
+                       return 0;
+               if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
+                                       &error))
+                       return error;
+       }
+
+       error = xchk_parent_iget(pp, pptr, &dp);
+       if (error)
+               return error;
+       if (!dp)
+               return 0;
+
+       /*
+        * If we can grab both IOLOCK and ILOCK of the alleged parent, we
+        * can proceed with the validation.
+        */
+       lockmode = xchk_parent_lock_dir(sc, dp);
+       if (lockmode) {
+               trace_xchk_parent_slowpath(sc->ip, xname, dp->i_ino);
+               goto check_dirent;
+       }
+
+       /*
+        * We couldn't lock the parent dir.  Drop all the locks and try to
+        * get them again, one at a time.
+        */
+       xchk_iunlock(sc, sc->ilock_flags);
+       pp->need_revalidate = true;
+
+       trace_xchk_parent_ultraslowpath(sc->ip, xname, dp->i_ino);
+
+       error = xchk_dir_trylock_for_pptrs(sc, dp, &lockmode);
+       if (error)
+               goto out_rele;
+
+       /* Revalidate the parent pointer now that we cycled locks. */
+       error = xchk_parent_revalidate_pptr(pp, xname, pptr);
+       if (error == -ENOENT) {
+               error = 0;
+               goto out_unlock;
+       }
+       if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
+               goto out_unlock;
+
+check_dirent:
+       error = xchk_parent_dirent(pp, xname, dp);
+out_unlock:
+       xfs_iunlock(dp, lockmode);
+out_rele:
+       xchk_irele(sc, dp);
+       return error;
+}
+
+/* Check all the parent pointers that we deferred the first time around. */
+STATIC int
+xchk_parent_finish_slow_pptrs(
+       struct xchk_pptrs       *pp)
+{
+       xfarray_idx_t           array_cur;
+       int                     error;
+
+       foreach_xfarray_idx(pp->pptr_entries, array_cur) {
+               struct xchk_pptr        pptr;
+
+               if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+                       return 0;
+
+               error = xfarray_load(pp->pptr_entries, array_cur, &pptr);
+               if (error)
+                       return error;
+
+               error = xfblob_loadname(pp->pptr_names, pptr.name_cookie,
+                               &pp->xname, pptr.namelen);
+               if (error)
+                       return error;
+
+               error = xchk_parent_slow_pptr(pp, &pp->xname, &pptr.pptr_rec);
+               if (error)
+                       return error;
+       }
+
+       /* Empty out both xfiles now that we've checked everything. */
+       xfarray_truncate(pp->pptr_entries);
+       xfblob_truncate(pp->pptr_names);
+       return 0;
+}
+
+/* Count the number of parent pointers. */
+STATIC int
+xchk_parent_count_pptr(
+       struct xfs_scrub                *sc,
+       struct xfs_inode                *ip,
+       unsigned int                    attr_flags,
+       const unsigned char             *name,
+       unsigned int                    namelen,
+       const void                      *value,
+       unsigned int                    valuelen,
+       void                            *priv)
+{
+       struct xchk_pptrs               *pp = priv;
+       int                             error;
+
+       if (!(attr_flags & XFS_ATTR_PARENT))
+               return 0;
+
+       error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
+                       valuelen, NULL, NULL);
+       if (error)
+               return error;
+
+       pp->pptrs_found++;
+       return 0;
+}
+
 /*
  * Compare the number of parent pointers to the link count.  For
  * non-directories these should be the same.  For unlinked directories the
@@ -487,6 +691,23 @@ xchk_parent_count_pptrs(
        struct xchk_pptrs       *pp)
 {
        struct xfs_scrub        *sc = pp->sc;
+       int                     error;
+
+       /*
+        * If we cycled the ILOCK while cross-checking parent pointers with
+        * dirents, then we need to recalculate the number of parent pointers.
+        */
+       if (pp->need_revalidate) {
+               pp->pptrs_found = 0;
+               error = xchk_xattr_walk(sc, sc->ip, xchk_parent_count_pptr, pp);
+               if (error == -EFSCORRUPTED) {
+                       /* Found a bad parent pointer */
+                       xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
+                       return 0;
+               }
+               if (error)
+                       return error;
+       }
 
        if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
                if (sc->ip == sc->mp->m_rootip)
@@ -511,23 +732,51 @@ xchk_parent_pptr(
        struct xfs_scrub        *sc)
 {
        struct xchk_pptrs       *pp;
+       char                    *descr;
        int                     error;
 
        pp = kvzalloc(sizeof(struct xchk_pptrs), XCHK_GFP_FLAGS);
        if (!pp)
                return -ENOMEM;
        pp->sc = sc;
+       pp->xname.name = pp->namebuf;
+
+       /*
+        * Set up some staging memory for parent pointers that we can't check
+        * due to locking contention.
+        */
+       descr = xchk_xfile_ino_descr(sc, "slow parent pointer entries");
+       error = xfarray_create(descr, 0, sizeof(struct xchk_pptr),
+                       &pp->pptr_entries);
+       kfree(descr);
+       if (error)
+               goto out_pp;
+
+       descr = xchk_xfile_ino_descr(sc, "slow parent pointer names");
+       error = xfblob_create(descr, &pp->pptr_names);
+       kfree(descr);
+       if (error)
+               goto out_entries;
 
        error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_attr, pp);
        if (error == -ECANCELED) {
                error = 0;
-               goto out_pp;
+               goto out_names;
        }
        if (error)
-               goto out_pp;
+               goto out_names;
+
+       error = xchk_parent_finish_slow_pptrs(pp);
+       if (error == -ETIMEDOUT) {
+               /* Couldn't grab a lock, scrub was marked incomplete */
+               error = 0;
+               goto out_names;
+       }
+       if (error)
+               goto out_names;
 
        if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
-               goto out_pp;
+               goto out_names;
 
        /*
         * For subdirectories, make sure the dotdot entry references the same
@@ -545,7 +794,7 @@ xchk_parent_pptr(
        if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
                error = xchk_parent_pptr_and_dotdot(pp);
                if (error)
-                       goto out_pp;
+                       goto out_names;
        }
 
        if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
@@ -558,8 +807,12 @@ xchk_parent_pptr(
         */
        error = xchk_parent_count_pptrs(pp);
        if (error)
-               goto out_pp;
+               goto out_names;
 
+out_names:
+       xfblob_destroy(pp->pptr_names);
+out_entries:
+       xfarray_destroy(pp->pptr_entries);
 out_pp:
        kvfree(pp);
        return error;
index 4db762480b8d426da1d93dad4b042a57ed4f8c04..97a106519b5314de83fb77db15a8c9b71a7bbc9e 100644 (file)
@@ -1544,6 +1544,9 @@ DEFINE_EVENT(xchk_pptr_class, name, \
 DEFINE_XCHK_PPTR_EVENT(xchk_dir_defer);
 DEFINE_XCHK_PPTR_EVENT(xchk_dir_slowpath);
 DEFINE_XCHK_PPTR_EVENT(xchk_dir_ultraslowpath);
+DEFINE_XCHK_PPTR_EVENT(xchk_parent_defer);
+DEFINE_XCHK_PPTR_EVENT(xchk_parent_slowpath);
+DEFINE_XCHK_PPTR_EVENT(xchk_parent_ultraslowpath);
 
 /* repair tracepoints */
 #if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)