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 xchk_setup_parent( 24 struct xfs_scrub *sc) 25 { 26 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 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 struct xchk_parent_ctx *spc = priv; 49 int error = 0; 50 51 /* Does this name make sense? */ 52 if (!xfs_dir2_namecheck(name->name, name->len)) 53 error = -EFSCORRUPTED; 54 if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) 55 return error; 56 57 if (sc->ip->i_ino == ino) 58 spc->nlink++; 59 60 if (xchk_should_terminate(spc->sc, &error)) 61 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 xchk_parent_ilock_dir( 72 struct xfs_inode *dp) 73 { 74 if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED)) 75 return 0; 76 77 if (!xfs_need_iread_extents(&dp->i_df)) 78 return XFS_ILOCK_SHARED; 79 80 xfs_iunlock(dp, XFS_ILOCK_SHARED); 81 82 if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL)) 83 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 xchk_parent_validate( 96 struct xfs_scrub *sc, 97 xfs_ino_t parent_ino) 98 { 99 struct xchk_parent_ctx spc = { 100 .sc = sc, 101 .nlink = 0, 102 }; 103 struct xfs_mount *mp = sc->mp; 104 struct xfs_inode *dp = NULL; 105 xfs_nlink_t expected_nlink; 106 unsigned int lock_mode; 107 int error = 0; 108 109 /* Is this the root dir? Then '..' must point to itself. */ 110 if (sc->ip == mp->m_rootip) { 111 if (sc->ip->i_ino != mp->m_sb.sb_rootino || 112 sc->ip->i_ino != parent_ino) 113 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); 114 return 0; 115 } 116 117 /* '..' must not point to ourselves. */ 118 if (sc->ip->i_ino == parent_ino) { 119 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); 120 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 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 error = xchk_iget(sc, parent_ino, &dp); 139 if (error == -EINVAL || error == -ENOENT) { 140 error = -EFSCORRUPTED; 141 xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error); 142 return error; 143 } 144 if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) 145 return error; 146 if (dp == sc->ip || !S_ISDIR(VFS_I(dp)->i_mode)) { 147 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); 148 goto out_rele; 149 } 150 151 lock_mode = xchk_parent_ilock_dir(dp); 152 if (!lock_mode) { 153 xfs_iunlock(sc->ip, XFS_ILOCK_EXCL); 154 xfs_ilock(sc->ip, XFS_ILOCK_EXCL); 155 error = -EAGAIN; 156 goto out_rele; 157 } 158 159 /* Look for a directory entry in the parent pointing to the child. */ 160 error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc); 161 if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) 162 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 if (spc.nlink != expected_nlink) 169 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); 170 171 out_unlock: 172 xfs_iunlock(dp, lock_mode); 173 out_rele: 174 xchk_irele(sc, dp); 175 return error; 176 } 177 178 /* Scrub a parent pointer. */ 179 int 180 xchk_parent( 181 struct xfs_scrub *sc) 182 { 183 struct xfs_mount *mp = sc->mp; 184 xfs_ino_t parent_ino; 185 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 if (!S_ISDIR(VFS_I(sc->ip)->i_mode)) 192 return -ENOENT; 193 194 /* We're not a special inode, are we? */ 195 if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) { 196 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); 197 return 0; 198 } 199 200 do { 201 if (xchk_should_terminate(sc, &error)) 202 break; 203 204 /* Look up '..' */ 205 error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, 206 &parent_ino); 207 if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error)) 208 return error; 209 if (!xfs_verify_dir_ino(mp, parent_ino)) { 210 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); 211 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 error = xchk_parent_validate(sc, parent_ino); 219 } while (error == -EAGAIN); 220 221 return error; 222 } 223