xfs: check metadata directory file path connectivity
authorDarrick J. Wong <djwong@kernel.org>
Mon, 4 Nov 2024 04:19:02 +0000 (20:19 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 5 Nov 2024 21:38:34 +0000 (13:38 -0800)
Create a new scrubber type that checks that well known metadata
directory paths are connected to the metadata inode that the incore
structures think is in use.  For example, check that "/quota/user" in
the metadata directory tree actually points to
mp->m_quotainfo->qi_uquotaip->i_ino.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
12 files changed:
fs/xfs/Makefile
fs/xfs/libxfs/xfs_fs.h
fs/xfs/libxfs/xfs_health.h
fs/xfs/scrub/common.h
fs/xfs/scrub/health.c
fs/xfs/scrub/metapath.c [new file with mode: 0644]
fs/xfs/scrub/scrub.c
fs/xfs/scrub/scrub.h
fs/xfs/scrub/stats.c
fs/xfs/scrub/trace.c
fs/xfs/scrub/trace.h
fs/xfs/xfs_health.c

index ba418a4..d80c281 100644 (file)
@@ -174,6 +174,7 @@ xfs-y                               += $(addprefix scrub/, \
                                   inode.o \
                                   iscan.o \
                                   listxattr.o \
+                                  metapath.o \
                                   nlinks.o \
                                   parent.o \
                                   readdir.o \
index b05e6fb..faa38a7 100644 (file)
@@ -199,6 +199,7 @@ struct xfs_fsop_geom {
 #define XFS_FSOP_GEOM_SICK_QUOTACHECK  (1 << 6)  /* quota counts */
 #define XFS_FSOP_GEOM_SICK_NLINKS      (1 << 7)  /* inode link counts */
 #define XFS_FSOP_GEOM_SICK_METADIR     (1 << 8)  /* metadata directory */
+#define XFS_FSOP_GEOM_SICK_METAPATH    (1 << 9)  /* metadir tree path */
 
 /* Output for XFS_FS_COUNTS */
 typedef struct xfs_fsop_counts {
@@ -732,9 +733,10 @@ struct xfs_scrub_metadata {
 #define XFS_SCRUB_TYPE_NLINKS  26      /* inode link counts */
 #define XFS_SCRUB_TYPE_HEALTHY 27      /* everything checked out ok */
 #define XFS_SCRUB_TYPE_DIRTREE 28      /* directory tree structure */
+#define XFS_SCRUB_TYPE_METAPATH        29      /* metadata directory tree paths */
 
 /* Number of scrub subcommands. */
-#define XFS_SCRUB_TYPE_NR      29
+#define XFS_SCRUB_TYPE_NR      30
 
 /*
  * This special type code only applies to the vectored scrub implementation.
@@ -812,6 +814,15 @@ struct xfs_scrub_vec_head {
 
 #define XFS_SCRUB_VEC_FLAGS_ALL                (0)
 
+/*
+ * i: sm_ino values for XFS_SCRUB_TYPE_METAPATH to select a metadata file for
+ * path checking.
+ */
+#define XFS_SCRUB_METAPATH_PROBE       (0)  /* do we have a metapath scrubber? */
+
+/* Number of metapath sm_ino values */
+#define XFS_SCRUB_METAPATH_NR          (1)
+
 /*
  * ioctl limits
  */
index f90e8df..a23df94 100644 (file)
@@ -63,6 +63,7 @@ struct xfs_da_args;
 #define XFS_SICK_FS_QUOTACHECK (1 << 4)  /* quota counts */
 #define XFS_SICK_FS_NLINKS     (1 << 5)  /* inode link counts */
 #define XFS_SICK_FS_METADIR    (1 << 6)  /* metadata directory tree */
+#define XFS_SICK_FS_METAPATH   (1 << 7)  /* metadata directory tree path */
 
 /* Observable health issues for realtime volume metadata. */
 #define XFS_SICK_RT_BITMAP     (1 << 0)  /* realtime bitmap */
@@ -107,7 +108,8 @@ struct xfs_da_args;
                                 XFS_SICK_FS_PQUOTA | \
                                 XFS_SICK_FS_QUOTACHECK | \
                                 XFS_SICK_FS_NLINKS | \
-                                XFS_SICK_FS_METADIR)
+                                XFS_SICK_FS_METADIR | \
+                                XFS_SICK_FS_METAPATH)
 
 #define XFS_SICK_RT_PRIMARY    (XFS_SICK_RT_BITMAP | \
                                 XFS_SICK_RT_SUMMARY)
index b419adc..b2a81e8 100644 (file)
@@ -73,6 +73,7 @@ int xchk_setup_xattr(struct xfs_scrub *sc);
 int xchk_setup_symlink(struct xfs_scrub *sc);
 int xchk_setup_parent(struct xfs_scrub *sc);
 int xchk_setup_dirtree(struct xfs_scrub *sc);
+int xchk_setup_metapath(struct xfs_scrub *sc);
 #ifdef CONFIG_XFS_RT
 int xchk_setup_rtbitmap(struct xfs_scrub *sc);
 int xchk_setup_rtsummary(struct xfs_scrub *sc);
index 6ceef37..b8b92ff 100644 (file)
@@ -109,6 +109,7 @@ static const struct xchk_health_map type_to_health_flag[XFS_SCRUB_TYPE_NR] = {
        [XFS_SCRUB_TYPE_QUOTACHECK]     = { XHG_FS,  XFS_SICK_FS_QUOTACHECK },
        [XFS_SCRUB_TYPE_NLINKS]         = { XHG_FS,  XFS_SICK_FS_NLINKS },
        [XFS_SCRUB_TYPE_DIRTREE]        = { XHG_INO, XFS_SICK_INO_DIRTREE },
+       [XFS_SCRUB_TYPE_METAPATH]       = { XHG_FS,  XFS_SICK_FS_METAPATH },
 };
 
 /* Return the health status mask for this scrub type. */
diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c
new file mode 100644 (file)
index 0000000..b7bd86d
--- /dev/null
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023-2024 Oracle.  All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ */
+#include "xfs.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_inode.h"
+#include "xfs_metafile.h"
+#include "xfs_quota.h"
+#include "xfs_qm.h"
+#include "xfs_dir2.h"
+#include "scrub/scrub.h"
+#include "scrub/common.h"
+#include "scrub/trace.h"
+#include "scrub/readdir.h"
+
+/*
+ * Metadata Directory Tree Paths
+ * =============================
+ *
+ * A filesystem with metadir enabled expects to find metadata structures
+ * attached to files that are accessible by walking a path down the metadata
+ * directory tree.  Given the metadir path and the incore inode storing the
+ * metadata, this scrubber ensures that the ondisk metadir path points to the
+ * ondisk inode represented by the incore inode.
+ */
+
+struct xchk_metapath {
+       struct xfs_scrub                *sc;
+
+       /* Name for lookup */
+       struct xfs_name                 xname;
+
+       /* Path for this metadata file and the parent directory */
+       const char                      *path;
+       const char                      *parent_path;
+
+       /* Directory parent of the metadata file. */
+       struct xfs_inode                *dp;
+
+       /* Locks held on dp */
+       unsigned int                    dp_ilock_flags;
+};
+
+/* Release resources tracked in the buffer. */
+static inline void
+xchk_metapath_cleanup(
+       void                    *buf)
+{
+       struct xchk_metapath    *mpath = buf;
+
+       if (mpath->dp_ilock_flags)
+               xfs_iunlock(mpath->dp, mpath->dp_ilock_flags);
+       kfree(mpath->path);
+}
+
+int
+xchk_setup_metapath(
+       struct xfs_scrub        *sc)
+{
+       if (!xfs_has_metadir(sc->mp))
+               return -ENOENT;
+       if (sc->sm->sm_gen)
+               return -EINVAL;
+
+       switch (sc->sm->sm_ino) {
+       case XFS_SCRUB_METAPATH_PROBE:
+               /* Just probing, nothing else to do. */
+               if (sc->sm->sm_agno)
+                       return -EINVAL;
+               return 0;
+       default:
+               return -ENOENT;
+       }
+}
+
+/*
+ * Take the ILOCK on the metadata directory parent and child.  We do not know
+ * that the metadata directory is not corrupt, so we lock the parent and try
+ * to lock the child.  Returns 0 if successful, or -EINTR to abort the scrub.
+ */
+STATIC int
+xchk_metapath_ilock_both(
+       struct xchk_metapath    *mpath)
+{
+       struct xfs_scrub        *sc = mpath->sc;
+       int                     error = 0;
+
+       while (true) {
+               xfs_ilock(mpath->dp, XFS_ILOCK_EXCL);
+               if (xchk_ilock_nowait(sc, XFS_ILOCK_EXCL)) {
+                       mpath->dp_ilock_flags |= XFS_ILOCK_EXCL;
+                       return 0;
+               }
+               xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
+
+               if (xchk_should_terminate(sc, &error))
+                       return error;
+
+               delay(1);
+       }
+
+       ASSERT(0);
+       return -EINTR;
+}
+
+/* Unlock parent and child inodes. */
+static inline void
+xchk_metapath_iunlock(
+       struct xchk_metapath    *mpath)
+{
+       struct xfs_scrub        *sc = mpath->sc;
+
+       xchk_iunlock(sc, XFS_ILOCK_EXCL);
+
+       mpath->dp_ilock_flags &= ~XFS_ILOCK_EXCL;
+       xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
+}
+
+int
+xchk_metapath(
+       struct xfs_scrub        *sc)
+{
+       struct xchk_metapath    *mpath = sc->buf;
+       xfs_ino_t               ino = NULLFSINO;
+       int                     error;
+
+       /* Just probing, nothing else to do. */
+       if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
+               return 0;
+
+       /* Parent required to do anything else. */
+       if (mpath->dp == NULL) {
+               xchk_ino_set_corrupt(sc, sc->ip->i_ino);
+               return 0;
+       }
+
+       error = xchk_trans_alloc_empty(sc);
+       if (error)
+               return error;
+
+       error = xchk_metapath_ilock_both(mpath);
+       if (error)
+               goto out_cancel;
+
+       /* Make sure the parent dir has a dirent pointing to this file. */
+       error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
+       trace_xchk_metapath_lookup(sc, mpath->path, mpath->dp, ino);
+       if (error == -ENOENT) {
+               /* No directory entry at all */
+               xchk_ino_set_corrupt(sc, sc->ip->i_ino);
+               error = 0;
+               goto out_ilock;
+       }
+       if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
+               goto out_ilock;
+       if (ino != sc->ip->i_ino) {
+               /* Pointing to wrong inode */
+               xchk_ino_set_corrupt(sc, sc->ip->i_ino);
+       }
+
+out_ilock:
+       xchk_metapath_iunlock(mpath);
+out_cancel:
+       xchk_trans_cancel(sc);
+       return error;
+}
index 8a5c3af..a30ed3d 100644 (file)
@@ -442,6 +442,13 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
                .has    = xfs_has_parent,
                .repair = xrep_dirtree,
        },
+       [XFS_SCRUB_TYPE_METAPATH] = {   /* metadata directory tree path */
+               .type   = ST_GENERIC,
+               .setup  = xchk_setup_metapath,
+               .scrub  = xchk_metapath,
+               .has    = xfs_has_metadir,
+               .repair = xrep_notsupported,
+       },
 };
 
 static int
@@ -489,6 +496,8 @@ xchk_validate_inputs(
                if (sm->sm_agno || (sm->sm_gen && !sm->sm_ino))
                        goto out;
                break;
+       case ST_GENERIC:
+               break;
        default:
                goto out;
        }
index 5993fca..c688ff4 100644 (file)
@@ -73,6 +73,7 @@ enum xchk_type {
        ST_PERAG,       /* per-AG metadata */
        ST_FS,          /* per-FS metadata */
        ST_INODE,       /* per-inode metadata */
+       ST_GENERIC,     /* determined by the scrubber */
 };
 
 struct xchk_meta_ops {
@@ -255,6 +256,7 @@ int xchk_xattr(struct xfs_scrub *sc);
 int xchk_symlink(struct xfs_scrub *sc);
 int xchk_parent(struct xfs_scrub *sc);
 int xchk_dirtree(struct xfs_scrub *sc);
+int xchk_metapath(struct xfs_scrub *sc);
 #ifdef CONFIG_XFS_RT
 int xchk_rtbitmap(struct xfs_scrub *sc);
 int xchk_rtsummary(struct xfs_scrub *sc);
index 7996c23..edcd02d 100644 (file)
@@ -80,6 +80,7 @@ static const char *name_map[XFS_SCRUB_TYPE_NR] = {
        [XFS_SCRUB_TYPE_QUOTACHECK]     = "quotacheck",
        [XFS_SCRUB_TYPE_NLINKS]         = "nlinks",
        [XFS_SCRUB_TYPE_DIRTREE]        = "dirtree",
+       [XFS_SCRUB_TYPE_METAPATH]       = "metapath",
 };
 
 /* Format the scrub stats into a text buffer, similar to pcp style. */
index 4470ad0..98f923a 100644 (file)
@@ -20,6 +20,7 @@
 #include "xfs_dir2.h"
 #include "xfs_rmap.h"
 #include "xfs_parent.h"
+#include "xfs_metafile.h"
 #include "scrub/scrub.h"
 #include "scrub/xfile.h"
 #include "scrub/xfarray.h"
index bc246d8..bb52baa 100644 (file)
@@ -70,6 +70,7 @@ TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_NLINKS);
 TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_HEALTHY);
 TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_DIRTREE);
 TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_BARRIER);
+TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_METAPATH);
 
 #define XFS_SCRUB_TYPE_STRINGS \
        { XFS_SCRUB_TYPE_PROBE,         "probe" }, \
@@ -101,7 +102,8 @@ TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_BARRIER);
        { XFS_SCRUB_TYPE_NLINKS,        "nlinks" }, \
        { XFS_SCRUB_TYPE_HEALTHY,       "healthy" }, \
        { XFS_SCRUB_TYPE_DIRTREE,       "dirtree" }, \
-       { XFS_SCRUB_TYPE_BARRIER,       "barrier" }
+       { XFS_SCRUB_TYPE_BARRIER,       "barrier" }, \
+       { XFS_SCRUB_TYPE_METAPATH,      "metapath" }
 
 #define XFS_SCRUB_FLAG_STRINGS \
        { XFS_SCRUB_IFLAG_REPAIR,               "repair" }, \
@@ -1916,6 +1918,38 @@ TRACE_EVENT(xchk_dirtree_live_update,
                  __get_str(name))
 );
 
+DECLARE_EVENT_CLASS(xchk_metapath_class,
+       TP_PROTO(struct xfs_scrub *sc, const char *path,
+                struct xfs_inode *dp, xfs_ino_t ino),
+       TP_ARGS(sc, path, dp, ino),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(xfs_ino_t, scrub_ino)
+               __field(xfs_ino_t, parent_ino)
+               __field(xfs_ino_t, ino)
+               __string(name, path)
+       ),
+       TP_fast_assign(
+               __entry->dev = sc->mp->m_super->s_dev;
+               __entry->scrub_ino = sc->ip ? sc->ip->i_ino : NULLFSINO;
+               __entry->parent_ino = dp ? dp->i_ino : NULLFSINO;
+               __entry->ino = ino;
+               __assign_str(name);
+       ),
+       TP_printk("dev %d:%d ino 0x%llx parent_ino 0x%llx name '%s' ino 0x%llx",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 __entry->scrub_ino,
+                 __entry->parent_ino,
+                 __get_str(name),
+                 __entry->ino)
+);
+#define DEFINE_XCHK_METAPATH_EVENT(name) \
+DEFINE_EVENT(xchk_metapath_class, name, \
+       TP_PROTO(struct xfs_scrub *sc, const char *path, \
+                struct xfs_inode *dp, xfs_ino_t ino), \
+       TP_ARGS(sc, path, dp, ino))
+DEFINE_XCHK_METAPATH_EVENT(xchk_metapath_lookup);
+
 /* repair tracepoints */
 #if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)
 
index 2382587..e5663e2 100644 (file)
@@ -381,6 +381,7 @@ static const struct ioctl_sick_map fs_map[] = {
        { XFS_SICK_FS_QUOTACHECK, XFS_FSOP_GEOM_SICK_QUOTACHECK },
        { XFS_SICK_FS_NLINKS,   XFS_FSOP_GEOM_SICK_NLINKS },
        { XFS_SICK_FS_METADIR,  XFS_FSOP_GEOM_SICK_METADIR },
+       { XFS_SICK_FS_METAPATH, XFS_FSOP_GEOM_SICK_METAPATH },
        { 0, 0 },
 };