xfs: check dirents have parent pointers
authorDarrick J. Wong <djwong@kernel.org>
Mon, 22 Apr 2024 16:48:01 +0000 (09:48 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 23 Apr 2024 14:47:02 +0000 (07:47 -0700)
If the fs has parent pointers, we need to check that each child dirent
points to a file that has a parent pointer pointing back at us.

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

index 504de1ef33876b3c64f159be145ef44eb7f03cb4..1e53df5f83323c3fd44451d58096c788446c3ce0 100644 (file)
@@ -291,3 +291,25 @@ xfs_parent_from_attr(
                *parent_gen = be32_to_cpu(rec->p_gen);
        return 0;
 }
+
+/*
+ * Look up a parent pointer record (@parent_name -> @pptr) of @ip.
+ *
+ * Caller must hold at least ILOCK_SHARED.  The scratchpad need not be
+ * initialized.
+ *
+ * Returns 0 if the pointer is found, -ENOATTR if there is no match, or a
+ * negative errno.
+ */
+int
+xfs_parent_lookup(
+       struct xfs_trans                *tp,
+       struct xfs_inode                *ip,
+       const struct xfs_name           *parent_name,
+       struct xfs_parent_rec           *pptr,
+       struct xfs_da_args              *scratch)
+{
+       memset(scratch, 0, sizeof(struct xfs_da_args));
+       xfs_parent_da_args_init(scratch, tp, pptr, ip, ip->i_ino, parent_name);
+       return xfs_attr_get_ilocked(scratch);
+}
index d7ab09e738ad42df52517132908978bdd38ca719..97788582321a6fe0fe609b29e01d108381edc0d6 100644 (file)
@@ -96,4 +96,9 @@ int xfs_parent_from_attr(struct xfs_mount *mp, unsigned int attr_flags,
                const void *value, unsigned int valuelen,
                xfs_ino_t *parent_ino, uint32_t *parent_gen);
 
+/* Repair functions */
+int xfs_parent_lookup(struct xfs_trans *tp, struct xfs_inode *ip,
+               const struct xfs_name *name, struct xfs_parent_rec *pptr,
+               struct xfs_da_args *scratch);
+
 #endif /* __XFS_PARENT_H__ */
index 3fe6ffcf9c0629409ac483b7ca759a39350bd12e..e11d73eb89352a680ab5ddc4a4071e9ff3176b01 100644 (file)
@@ -16,6 +16,8 @@
 #include "xfs_dir2.h"
 #include "xfs_dir2_priv.h"
 #include "xfs_health.h"
+#include "xfs_attr.h"
+#include "xfs_parent.h"
 #include "scrub/scrub.h"
 #include "scrub/common.h"
 #include "scrub/dabtree.h"
@@ -41,6 +43,14 @@ xchk_setup_directory(
 
 /* Directories */
 
+struct xchk_dir {
+       struct xfs_scrub        *sc;
+
+       /* information for parent pointer validation. */
+       struct xfs_parent_rec   pptr_rec;
+       struct xfs_da_args      pptr_args;
+};
+
 /* Scrub a directory entry. */
 
 /* Check that an inode's mode matches a given XFS_DIR3_FT_* type. */
@@ -63,6 +73,90 @@ xchk_dir_check_ftype(
                xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
 }
 
+/*
+ * Try to lock a child file for checking parent pointers.  Returns the inode
+ * flags for the locks we now hold, or zero if we failed.
+ */
+STATIC unsigned int
+xchk_dir_lock_child(
+       struct xfs_scrub        *sc,
+       struct xfs_inode        *ip)
+{
+       if (!xfs_ilock_nowait(ip, XFS_IOLOCK_SHARED))
+               return 0;
+
+       if (!xfs_ilock_nowait(ip, XFS_ILOCK_SHARED)) {
+               xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+               return 0;
+       }
+
+       if (!xfs_inode_has_attr_fork(ip) || !xfs_need_iread_extents(&ip->i_af))
+               return XFS_IOLOCK_SHARED | XFS_ILOCK_SHARED;
+
+       xfs_iunlock(ip, XFS_ILOCK_SHARED);
+
+       if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL)) {
+               xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+               return 0;
+       }
+
+       return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
+}
+
+/* Check the backwards link (parent pointer) associated with this dirent. */
+STATIC int
+xchk_dir_parent_pointer(
+       struct xchk_dir         *sd,
+       const struct xfs_name   *name,
+       struct xfs_inode        *ip)
+{
+       struct xfs_scrub        *sc = sd->sc;
+       int                     error;
+
+       xfs_inode_to_parent_rec(&sd->pptr_rec, sc->ip);
+       error = xfs_parent_lookup(sc->tp, ip, name, &sd->pptr_rec,
+                       &sd->pptr_args);
+       if (error == -ENOATTR)
+               xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0);
+
+       return 0;
+}
+
+/* Look for a parent pointer matching this dirent, if the child isn't busy. */
+STATIC int
+xchk_dir_check_pptr_fast(
+       struct xchk_dir         *sd,
+       xfs_dir2_dataptr_t      dapos,
+       const struct xfs_name   *name,
+       struct xfs_inode        *ip)
+{
+       struct xfs_scrub        *sc = sd->sc;
+       unsigned int            lockmode;
+       int                     error;
+
+       /* dot and dotdot entries do not have parent pointers */
+       if (xfs_dir2_samename(name, &xfs_name_dot) ||
+           xfs_dir2_samename(name, &xfs_name_dotdot))
+               return 0;
+
+       /* No self-referential non-dot or dotdot dirents. */
+       if (ip == sc->ip) {
+               xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
+               return -ECANCELED;
+       }
+
+       /* Try to lock the inode. */
+       lockmode = xchk_dir_lock_child(sc, ip);
+       if (!lockmode) {
+               xchk_set_incomplete(sc);
+               return -ECANCELED;
+       }
+
+       error = xchk_dir_parent_pointer(sd, name, ip);
+       xfs_iunlock(ip, lockmode);
+       return error;
+}
+
 /*
  * Scrub a single directory entry.
  *
@@ -80,6 +174,7 @@ xchk_dir_actor(
 {
        struct xfs_mount        *mp = dp->i_mount;
        struct xfs_inode        *ip;
+       struct xchk_dir         *sd = priv;
        xfs_ino_t               lookup_ino;
        xfs_dablk_t             offset;
        int                     error = 0;
@@ -146,6 +241,14 @@ xchk_dir_actor(
                goto out;
 
        xchk_dir_check_ftype(sc, offset, ip, name->type);
+
+       if (xfs_has_parent(mp)) {
+               error = xchk_dir_check_pptr_fast(sd, dapos, name, ip);
+               if (error)
+                       goto out_rele;
+       }
+
+out_rele:
        xchk_irele(sc, ip);
 out:
        if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
@@ -767,6 +870,7 @@ int
 xchk_directory(
        struct xfs_scrub        *sc)
 {
+       struct xchk_dir         *sd;
        int                     error;
 
        if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
@@ -799,8 +903,14 @@ xchk_directory(
        if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
                return 0;
 
+       sd = kvzalloc(sizeof(struct xchk_dir), XCHK_GFP_FLAGS);
+       if (!sd)
+               return -ENOMEM;
+       sd->sc = sc;
+
        /* Look up every name in this directory by hash. */
-       error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, NULL);
+       error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, sd);
+       kvfree(sd);
        if (error && error != -ECANCELED)
                return error;