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 | xchk_iunlock(sc, XFS_ILOCK_EXCL); |
154 | xchk_ilock(sc, XFS_ILOCK_EXCL); |
155 | error = -EAGAIN; |
156 | goto out_rele; |
157 | } |
158 | |
159 | /* |
160 | * We cannot yet validate this parent pointer if the directory looks as |
161 | * though it has been zapped by the inode record repair code. |
162 | */ |
163 | if (xchk_dir_looks_zapped(dp)) { |
164 | error = -EBUSY; |
165 | xchk_set_incomplete(sc); |
166 | goto out_unlock; |
167 | } |
168 | |
169 | /* Look for a directory entry in the parent pointing to the child. */ |
170 | error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc); |
171 | if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) |
172 | goto out_unlock; |
173 | |
174 | /* |
175 | * Ensure that the parent has as many links to the child as the child |
176 | * thinks it has to the parent. |
177 | */ |
178 | if (spc.nlink != expected_nlink) |
179 | xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); |
180 | |
181 | out_unlock: |
182 | xfs_iunlock(dp, lock_mode); |
183 | out_rele: |
184 | xchk_irele(sc, dp); |
185 | return error; |
186 | } |
187 | |
188 | /* Scrub a parent pointer. */ |
189 | int |
190 | xchk_parent( |
191 | struct xfs_scrub *sc) |
192 | { |
193 | struct xfs_mount *mp = sc->mp; |
194 | xfs_ino_t parent_ino; |
195 | int error = 0; |
196 | |
197 | /* |
198 | * If we're a directory, check that the '..' link points up to |
199 | * a directory that has one entry pointing to us. |
200 | */ |
201 | if (!S_ISDIR(VFS_I(sc->ip)->i_mode)) |
202 | return -ENOENT; |
203 | |
204 | /* We're not a special inode, are we? */ |
205 | if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) { |
206 | xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); |
207 | return 0; |
208 | } |
209 | |
210 | do { |
211 | if (xchk_should_terminate(sc, &error)) |
212 | break; |
213 | |
214 | /* Look up '..' */ |
215 | error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, |
216 | &parent_ino); |
217 | if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error)) |
218 | return error; |
219 | if (!xfs_verify_dir_ino(mp, parent_ino)) { |
220 | xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); |
221 | return 0; |
222 | } |
223 | |
224 | /* |
225 | * Check that the dotdot entry points to a parent directory |
226 | * containing a dirent pointing to this subdirectory. |
227 | */ |
228 | error = xchk_parent_validate(sc, parent_ino); |
229 | } while (error == -EAGAIN); |
230 | if (error == -EBUSY) { |
231 | /* |
232 | * We could not scan a directory, so we marked the check |
233 | * incomplete. No further error return is necessary. |
234 | */ |
235 | return 0; |
236 | } |
237 | |
238 | return error; |
239 | } |
240 | |