]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfs: ask the dentry cache if it knows the parent of a directory
authorDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:54:54 +0000 (14:54 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:58:56 +0000 (14:58 -0700)
It's possible that the dentry cache can tell us the parent of a
directory.  Therefore, when repairing directory dot dot entries, query
the dcache as a last resort before scanning the entire filesystem.

A reviewer asks:

"How high is the chance that we actually have a valid dcache entry for a
file in a corrupted directory?"

There's a decent chance of this actually working.  Say you have a
1000-block directory foo, and block 980 gets corrupted.  Let's further
suppose that block 0 has a correct entry for ".." and "bar".  If someone
accesses /mnt/foo/bar, that will cause the dcache to create a dentry
from /mnt to /mnt/foo whose d_parent points back to /mnt.  If you then
want to rebuild the directory, XFS can obtain the parent from the dcache
without needing to wander into parent pointers or scan the filesystem to
find /mnt's connection to foo.

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

index b17de79207dbae058fe6fc526e582c2fb43f7c08..34fe720fde0eb89b1e28d0aa097173de31d8b4b8 100644 (file)
@@ -208,6 +208,29 @@ xrep_dir_lookup_parent(
        return ino;
 }
 
+/*
+ * Look up '..' in the dentry cache and confirm that it's really the parent.
+ * Returns NULLFSINO if the dcache misses or if the hit is implausible.
+ */
+static inline xfs_ino_t
+xrep_dir_dcache_parent(
+       struct xrep_dir         *rd)
+{
+       struct xfs_scrub        *sc = rd->sc;
+       xfs_ino_t               parent_ino;
+       int                     error;
+
+       parent_ino = xrep_findparent_from_dcache(sc);
+       if (parent_ino == NULLFSINO)
+               return parent_ino;
+
+       error = xrep_findparent_confirm(sc, &parent_ino);
+       if (error)
+               return NULLFSINO;
+
+       return parent_ino;
+}
+
 /* Try to find the parent of the directory being repaired. */
 STATIC int
 xrep_dir_find_parent(
@@ -221,6 +244,12 @@ xrep_dir_find_parent(
                return 0;
        }
 
+       ino = xrep_dir_dcache_parent(rd);
+       if (ino != NULLFSINO) {
+               xrep_findparent_scan_finish_early(&rd->pscan, ino);
+               return 0;
+       }
+
        ino = xrep_dir_lookup_parent(rd);
        if (ino != NULLFSINO) {
                xrep_findparent_scan_finish_early(&rd->pscan, ino);
index 7b3ec8d7d6cc97c4a21d69515e333b1d449d477a..712dd73e4789f636f64f1bb3ef1ae33bc8e6ddbe 100644 (file)
@@ -53,7 +53,8 @@
  * must not read the scan results without re-taking @sc->ip's ILOCK.
  *
  * There are a few shortcuts that we can take to avoid scanning the entire
- * filesystem, such as noticing directory tree roots.
+ * filesystem, such as noticing directory tree roots and querying the dentry
+ * cache for parent information.
  */
 
 struct xrep_findparent_info {
@@ -410,3 +411,38 @@ xrep_findparent_self_reference(
 
        return NULLFSINO;
 }
+
+/* Check the dentry cache to see if knows of a parent for the scrub target. */
+xfs_ino_t
+xrep_findparent_from_dcache(
+       struct xfs_scrub        *sc)
+{
+       struct inode            *pip = NULL;
+       struct dentry           *dentry, *parent;
+       xfs_ino_t               ret = NULLFSINO;
+
+       dentry = d_find_alias(VFS_I(sc->ip));
+       if (!dentry)
+               goto out;
+
+       parent = dget_parent(dentry);
+       if (!parent)
+               goto out_dput;
+
+       ASSERT(parent->d_sb == sc->ip->i_mount->m_super);
+
+       pip = igrab(d_inode(parent));
+       dput(parent);
+
+       if (S_ISDIR(pip->i_mode)) {
+               trace_xrep_findparent_from_dcache(sc->ip, XFS_I(pip)->i_ino);
+               ret = XFS_I(pip)->i_ino;
+       }
+
+       xchk_irele(sc, XFS_I(pip));
+
+out_dput:
+       dput(dentry);
+out:
+       return ret;
+}
index d946bc81f34e686d8962e004f05e0388613da504..501f99d3164ed1ed558d6f52e95f3dea60e9b13c 100644 (file)
@@ -45,5 +45,6 @@ void xrep_findparent_scan_finish_early(struct xrep_parent_scan_info *pscan,
 int xrep_findparent_confirm(struct xfs_scrub *sc, xfs_ino_t *parent_ino);
 
 xfs_ino_t xrep_findparent_self_reference(struct xfs_scrub *sc);
+xfs_ino_t xrep_findparent_from_dcache(struct xfs_scrub *sc);
 
 #endif /* __XFS_SCRUB_FINDPARENT_H__ */
index 0a9651bb0b0575ceb3be48fc3a0b5a8134a53c2a..826926c2bb0ddcc6814635543b13d22c77065c1e 100644 (file)
@@ -118,7 +118,20 @@ xrep_parent_find_dotdot(
         * then retake the ILOCK so that we can salvage directory entries.
         */
        xchk_iunlock(sc, XFS_ILOCK_EXCL);
+
+       /* Does the VFS dcache have an answer for us? */
+       ino = xrep_findparent_from_dcache(sc);
+       if (ino != NULLFSINO) {
+               error = xrep_findparent_confirm(sc, &ino);
+               if (!error && ino != NULLFSINO) {
+                       xrep_findparent_scan_finish_early(&rp->pscan, ino);
+                       goto out_relock;
+               }
+       }
+
+       /* Scan the entire filesystem for a parent. */
        error = xrep_findparent_scan(&rp->pscan);
+out_relock:
        xchk_ilock(sc, XFS_ILOCK_EXCL);
 
        return error;
index e1755fe63e67fd86edd67cc64d697de0c5b310ca..d68ec8e2781e51c6c162f9abfa00b5aaab3bbc05 100644 (file)
@@ -2613,6 +2613,7 @@ DEFINE_EVENT(xrep_parent_salvage_class, name, \
        TP_ARGS(dp, ino))
 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);
 
 #endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */