Teach online scrub about the metadata directory tree.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
 
        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);
 }
 
 /*
 
        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);
 
 
 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;
                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.
        struct xchk_dirpath     *path)
 {
        struct xfs_scrub        *sc = dl->sc;
+       bool                    is_metadir;
        int                     error;
 
        ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
         * 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);
 
         * 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);
         * *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);
 
         */
        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.
                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);
        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);
 
 
                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);
                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))
 
 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);