xfs: scrub metadata directories
authorDarrick J. Wong <djwong@kernel.org>
Mon, 4 Nov 2024 04:19:00 +0000 (20:19 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 5 Nov 2024 21:38:34 +0000 (13:38 -0800)
Teach online scrub about the metadata directory tree.

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

index 6b719c8885ef75f13975432b4b6bfad272c7e21a..c877bde71e62808036a7262e042a0937d40b190d 100644 (file)
@@ -100,6 +100,14 @@ xchk_dir_check_ftype(
 
        if (xfs_mode_to_ftype(VFS_I(ip)->i_mode) != ftype)
                xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
+
+       /*
+        * Metadata and regular inodes cannot cross trees.  This property
+        * cannot change without a full inode free and realloc cycle, so it's
+        * safe to check this without holding locks.
+        */
+       if (xfs_is_metadir_inode(ip) != xfs_is_metadir_inode(sc->ip))
+               xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
 }
 
 /*
index 0c2cd42b3110f2e9971cbe88750c3dd4889b9d15..2456cf1cb744110e3041dfaf26a3c24473bd369c 100644 (file)
@@ -415,6 +415,12 @@ xrep_dir_salvage_entry(
        if (error)
                return 0;
 
+       /* Don't mix metadata and regular directory trees. */
+       if (xfs_is_metadir_inode(ip) != xfs_is_metadir_inode(rd->sc->ip)) {
+               xchk_irele(sc, ip);
+               return 0;
+       }
+
        xname.type = xfs_mode_to_ftype(VFS_I(ip)->i_mode);
        xchk_irele(sc, ip);
 
index e43840733de9469dfacafc62005a1c54524d5e96..3a9cdf8738b6db3d6ec7cac716b96236fd9bf0da 100644 (file)
@@ -362,7 +362,8 @@ xchk_dirpath_set_outcome(
 STATIC int
 xchk_dirpath_step_up(
        struct xchk_dirtree     *dl,
-       struct xchk_dirpath     *path)
+       struct xchk_dirpath     *path,
+       bool                    is_metadir)
 {
        struct xfs_scrub        *sc = dl->sc;
        struct xfs_inode        *dp;
@@ -435,6 +436,14 @@ xchk_dirpath_step_up(
                goto out_scanlock;
        }
 
+       /* Parent must be in the same directory tree. */
+       if (is_metadir != xfs_is_metadir_inode(dp)) {
+               trace_xchk_dirpath_crosses_tree(dl->sc, dp, path->path_nr,
+                               path->nr_steps, &dl->xname, &dl->pptr_rec);
+               error = -EFSCORRUPTED;
+               goto out_scanlock;
+       }
+
        /*
         * If the extended attributes look as though they has been zapped by
         * the inode record repair code, we cannot scan for parent pointers.
@@ -508,6 +517,7 @@ xchk_dirpath_walk_upwards(
        struct xchk_dirpath     *path)
 {
        struct xfs_scrub        *sc = dl->sc;
+       bool                    is_metadir;
        int                     error;
 
        ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
@@ -538,6 +548,7 @@ xchk_dirpath_walk_upwards(
         * ILOCK state is no longer tracked in the scrub context.  Hence we
         * must drop @sc->ip's ILOCK during the walk.
         */
+       is_metadir = xfs_is_metadir_inode(sc->ip);
        mutex_unlock(&dl->lock);
        xchk_iunlock(sc, XFS_ILOCK_EXCL);
 
@@ -547,7 +558,7 @@ xchk_dirpath_walk_upwards(
         * If we see any kind of error here (including corruptions), the parent
         * pointer of @sc->ip is corrupt.  Stop the whole scan.
         */
-       error = xchk_dirpath_step_up(dl, path);
+       error = xchk_dirpath_step_up(dl, path, is_metadir);
        if (error) {
                xchk_ilock(sc, XFS_ILOCK_EXCL);
                mutex_lock(&dl->lock);
@@ -560,7 +571,7 @@ xchk_dirpath_walk_upwards(
         * *somewhere* in the path, but we don't need to stop scanning.
         */
        while (!error && path->outcome == XCHK_DIRPATH_SCANNING)
-               error = xchk_dirpath_step_up(dl, path);
+               error = xchk_dirpath_step_up(dl, path, is_metadir);
 
        /* Retake the locks we had, mark paths, etc. */
        xchk_ilock(sc, XFS_ILOCK_EXCL);
index 153d185190d8ad08baf5e0aaf65bb4935ff8c421..84487072b6dd6f51cd69fe875d5d096986d90608 100644 (file)
@@ -172,6 +172,10 @@ xrep_findparent_walk_directory(
         */
        lock_mode = xfs_ilock_data_map_shared(dp);
 
+       /* Don't mix metadata and regular directory trees. */
+       if (xfs_is_metadir_inode(dp) != xfs_is_metadir_inode(sc->ip))
+               goto out_unlock;
+
        /*
         * If this directory is known to be sick, we cannot scan it reliably
         * and must abort.
@@ -368,6 +372,12 @@ xrep_findparent_confirm(
                return 0;
        }
 
+       /* The metadata root directory always points to itself. */
+       if (sc->ip == sc->mp->m_metadirip) {
+               *parent_ino = sc->mp->m_sb.sb_metadirino;
+               return 0;
+       }
+
        /* Unlinked dirs can point anywhere; point them up to the root dir. */
        if (VFS_I(sc->ip)->i_nlink == 0) {
                *parent_ino = xchk_inode_rootdir_inum(sc->ip);
@@ -415,6 +425,9 @@ xrep_findparent_self_reference(
        if (sc->ip->i_ino == sc->mp->m_sb.sb_rootino)
                return sc->mp->m_sb.sb_rootino;
 
+       if (sc->ip->i_ino == sc->mp->m_sb.sb_metadirino)
+               return sc->mp->m_sb.sb_metadirino;
+
        if (VFS_I(sc->ip)->i_nlink == 0)
                return xchk_inode_rootdir_inum(sc->ip);
 
index d8ea393f5059700eb3495de5fe63f3dc7245516d..3b692c4acc1e6f0a8cee8d6d05a68054d0caf6e4 100644 (file)
@@ -132,6 +132,14 @@ xchk_parent_validate(
                return 0;
        }
 
+       /* Is this the metadata root dir?  Then '..' must point to itself. */
+       if (sc->ip == mp->m_metadirip) {
+               if (sc->ip->i_ino != mp->m_sb.sb_metadirino ||
+                   sc->ip->i_ino != parent_ino)
+                       xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
+               return 0;
+       }
+
        /* '..' must not point to ourselves. */
        if (sc->ip->i_ino == parent_ino) {
                xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
@@ -185,6 +193,12 @@ xchk_parent_validate(
                goto out_unlock;
        }
 
+       /* Metadata and regular inodes cannot cross trees. */
+       if (xfs_is_metadir_inode(dp) != xfs_is_metadir_inode(sc->ip)) {
+               xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
+               goto out_unlock;
+       }
+
        /* Look for a directory entry in the parent pointing to the child. */
        error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
        if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
index 58cc61f2ed53720d36fa1b693eca91a0218c7688..bc246d86a5c89facc3aa1abb53ed7673ac9a7ef1 100644 (file)
@@ -1753,6 +1753,7 @@ DEFINE_XCHK_DIRPATH_EVENT(xchk_dirpath_badgen);
 DEFINE_XCHK_DIRPATH_EVENT(xchk_dirpath_nondir_parent);
 DEFINE_XCHK_DIRPATH_EVENT(xchk_dirpath_unlinked_parent);
 DEFINE_XCHK_DIRPATH_EVENT(xchk_dirpath_found_next_step);
+DEFINE_XCHK_DIRPATH_EVENT(xchk_dirpath_crosses_tree);
 
 TRACE_DEFINE_ENUM(XCHK_DIRPATH_SCANNING);
 TRACE_DEFINE_ENUM(XCHK_DIRPATH_DELETE);