LCOV - code coverage report
Current view: top level - fs/xfs/scrub - parent.c (source / functions) Hit Total Coverage
Test: fstests of 6.5.0-rc3-djwx @ Mon Jul 31 20:08:22 PDT 2023 Lines: 61 79 77.2 %
Date: 2023-07-31 20:08:22 Functions: 5 5 100.0 %

          Line data    Source code
       1             : // SPDX-License-Identifier: GPL-2.0-or-later
       2             : /*
       3             :  * Copyright (C) 2017-2023 Oracle.  All Rights Reserved.
       4             :  * Author: Darrick J. Wong <djwong@kernel.org>
       5             :  */
       6             : #include "xfs.h"
       7             : #include "xfs_fs.h"
       8             : #include "xfs_shared.h"
       9             : #include "xfs_format.h"
      10             : #include "xfs_trans_resv.h"
      11             : #include "xfs_mount.h"
      12             : #include "xfs_log_format.h"
      13             : #include "xfs_inode.h"
      14             : #include "xfs_icache.h"
      15             : #include "xfs_dir2.h"
      16             : #include "xfs_dir2_priv.h"
      17             : #include "scrub/scrub.h"
      18             : #include "scrub/common.h"
      19             : #include "scrub/readdir.h"
      20             : 
      21             : /* Set us up to scrub parents. */
      22             : int
      23   316463168 : xchk_setup_parent(
      24             :         struct xfs_scrub        *sc)
      25             : {
      26   316463168 :         return xchk_setup_inode_contents(sc, 0);
      27             : }
      28             : 
      29             : /* Parent pointers */
      30             : 
      31             : /* Look for an entry in a parent pointing to this inode. */
      32             : 
      33             : struct xchk_parent_ctx {
      34             :         struct xfs_scrub        *sc;
      35             :         xfs_nlink_t             nlink;
      36             : };
      37             : 
      38             : /* Look for a single entry in a directory pointing to an inode. */
      39             : STATIC int
      40  2146696713 : xchk_parent_actor(
      41             :         struct xfs_scrub        *sc,
      42             :         struct xfs_inode        *dp,
      43             :         xfs_dir2_dataptr_t      dapos,
      44             :         const struct xfs_name   *name,
      45             :         xfs_ino_t               ino,
      46             :         void                    *priv)
      47             : {
      48  2146696713 :         struct xchk_parent_ctx  *spc = priv;
      49  2146696713 :         int                     error = 0;
      50             : 
      51             :         /* Does this name make sense? */
      52  2146696713 :         if (!xfs_dir2_namecheck(name->name, name->len))
      53           0 :                 error = -EFSCORRUPTED;
      54  2178599950 :         if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
      55           0 :                 return error;
      56             : 
      57  2363906266 :         if (sc->ip->i_ino == ino)
      58    89237079 :                 spc->nlink++;
      59             : 
      60  2363906266 :         if (xchk_should_terminate(spc->sc, &error))
      61           0 :                 return error;
      62             : 
      63             :         return 0;
      64             : }
      65             : 
      66             : /*
      67             :  * Try to lock a parent directory for checking dirents.  Returns the inode
      68             :  * flags for the locks we now hold, or zero if we failed.
      69             :  */
      70             : STATIC unsigned int
      71   163740612 : xchk_parent_ilock_dir(
      72             :         struct xfs_inode        *dp)
      73             : {
      74   163740612 :         if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED))
      75             :                 return 0;
      76             : 
      77    89206070 :         if (!xfs_need_iread_extents(&dp->i_df))
      78             :                 return XFS_ILOCK_SHARED;
      79             : 
      80          30 :         xfs_iunlock(dp, XFS_ILOCK_SHARED);
      81             : 
      82          30 :         if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL))
      83           0 :                 return 0;
      84             : 
      85             :         return XFS_ILOCK_EXCL;
      86             : }
      87             : 
      88             : /*
      89             :  * Given the inode number of the alleged parent of the inode being scrubbed,
      90             :  * try to validate that the parent has exactly one directory entry pointing
      91             :  * back to the inode being scrubbed.  Returns -EAGAIN if we need to revalidate
      92             :  * the dotdot entry.
      93             :  */
      94             : STATIC int
      95   162989019 : xchk_parent_validate(
      96             :         struct xfs_scrub        *sc,
      97             :         xfs_ino_t               parent_ino)
      98             : {
      99   162989019 :         struct xchk_parent_ctx  spc = {
     100             :                 .sc             = sc,
     101             :                 .nlink          = 0,
     102             :         };
     103   162989019 :         struct xfs_mount        *mp = sc->mp;
     104   162989019 :         struct xfs_inode        *dp = NULL;
     105   162989019 :         xfs_nlink_t             expected_nlink;
     106   162989019 :         unsigned int            lock_mode;
     107   162989019 :         int                     error = 0;
     108             : 
     109             :         /* Is this the root dir?  Then '..' must point to itself. */
     110   162989019 :         if (sc->ip == mp->m_rootip) {
     111      342065 :                 if (sc->ip->i_ino != mp->m_sb.sb_rootino ||
     112             :                     sc->ip->i_ino != parent_ino)
     113           0 :                         xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
     114      342065 :                 return 0;
     115             :         }
     116             : 
     117             :         /* '..' must not point to ourselves. */
     118   162646954 :         if (sc->ip->i_ino == parent_ino) {
     119           0 :                 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
     120           0 :                 return 0;
     121             :         }
     122             : 
     123             :         /*
     124             :          * If we're an unlinked directory, the parent /won't/ have a link
     125             :          * to us.  Otherwise, it should have one link.
     126             :          */
     127   162646954 :         expected_nlink = VFS_I(sc->ip)->i_nlink == 0 ? 0 : 1;
     128             : 
     129             :         /*
     130             :          * Grab the parent directory inode.  This must be released before we
     131             :          * cancel the scrub transaction.
     132             :          *
     133             :          * If _iget returns -EINVAL or -ENOENT then the parent inode number is
     134             :          * garbage and the directory is corrupt.  If the _iget returns
     135             :          * -EFSCORRUPTED or -EFSBADCRC then the parent is corrupt which is a
     136             :          *  cross referencing error.  Any other error is an operational error.
     137             :          */
     138   162646954 :         error = xchk_iget(sc, parent_ino, &dp);
     139   163903542 :         if (error == -EINVAL || error == -ENOENT) {
     140           0 :                 error = -EFSCORRUPTED;
     141           0 :                 xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
     142           0 :                 return error;
     143             :         }
     144   163903542 :         if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
     145           0 :                 return error;
     146   163814325 :         if (dp == sc->ip || !S_ISDIR(VFS_I(dp)->i_mode)) {
     147        3266 :                 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
     148          11 :                 goto out_rele;
     149             :         }
     150             : 
     151   163817025 :         lock_mode = xchk_parent_ilock_dir(dp);
     152   163495766 :         if (!lock_mode) {
     153    74288704 :                 xfs_iunlock(sc->ip, XFS_ILOCK_EXCL);
     154    73983930 :                 xfs_ilock(sc->ip, XFS_ILOCK_EXCL);
     155    74067413 :                 error = -EAGAIN;
     156    74067413 :                 goto out_rele;
     157             :         }
     158             : 
     159             :         /* Look for a directory entry in the parent pointing to the child. */
     160    89207062 :         error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
     161    89237032 :         if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
     162           0 :                 goto out_unlock;
     163             : 
     164             :         /*
     165             :          * Ensure that the parent has as many links to the child as the child
     166             :          * thinks it has to the parent.
     167             :          */
     168    89231371 :         if (spc.nlink != expected_nlink)
     169           0 :                 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
     170             : 
     171    89231371 : out_unlock:
     172    89231371 :         xfs_iunlock(dp, lock_mode);
     173   163274766 : out_rele:
     174   163274766 :         xchk_irele(sc, dp);
     175   163840343 :         return error;
     176             : }
     177             : 
     178             : /* Scrub a parent pointer. */
     179             : int
     180   314829183 : xchk_parent(
     181             :         struct xfs_scrub        *sc)
     182             : {
     183   314829183 :         struct xfs_mount        *mp = sc->mp;
     184   314829183 :         xfs_ino_t               parent_ino;
     185   314829183 :         int                     error = 0;
     186             : 
     187             :         /*
     188             :          * If we're a directory, check that the '..' link points up to
     189             :          * a directory that has one entry pointing to us.
     190             :          */
     191   314829183 :         if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
     192             :                 return -ENOENT;
     193             : 
     194             :         /* We're not a special inode, are we? */
     195    89525741 :         if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
     196           0 :                 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
     197           0 :                 return 0;
     198             :         }
     199             : 
     200   164145618 :         do {
     201   164145618 :                 if (xchk_should_terminate(sc, &error))
     202             :                         break;
     203             : 
     204             :                 /* Look up '..' */
     205   163757533 :                 error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
     206             :                                 &parent_ino);
     207   163028650 :                 if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
     208           0 :                         return error;
     209   163244035 :                 if (!xfs_verify_dir_ino(mp, parent_ino)) {
     210           0 :                         xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
     211           0 :                         return 0;
     212             :                 }
     213             : 
     214             :                 /*
     215             :                  * Check that the dotdot entry points to a parent directory
     216             :                  * containing a dirent pointing to this subdirectory.
     217             :                  */
     218   162973974 :                 error = xchk_parent_validate(sc, parent_ino);
     219   164184751 :         } while (error == -EAGAIN);
     220             : 
     221    89565533 :         return error;
     222             : }

Generated by: LCOV version 1.14