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 : }
|