1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2022-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_log_format.h" |
11 | #include "xfs_trans_resv.h" |
12 | #include "xfs_mount.h" |
13 | #include "xfs_inode.h" |
14 | #include "xfs_dir2.h" |
15 | #include "xfs_dir2_priv.h" |
16 | #include "xfs_trace.h" |
17 | #include "xfs_bmap.h" |
18 | #include "xfs_trans.h" |
19 | #include "xfs_error.h" |
20 | #include "scrub/scrub.h" |
21 | #include "scrub/readdir.h" |
22 | |
23 | /* Call a function for every entry in a shortform directory. */ |
24 | STATIC int |
25 | xchk_dir_walk_sf( |
26 | struct xfs_scrub *sc, |
27 | struct xfs_inode *dp, |
28 | xchk_dirent_fn dirent_fn, |
29 | void *priv) |
30 | { |
31 | struct xfs_name name = { |
32 | .name = "." , |
33 | .len = 1, |
34 | .type = XFS_DIR3_FT_DIR, |
35 | }; |
36 | struct xfs_mount *mp = dp->i_mount; |
37 | struct xfs_da_geometry *geo = mp->m_dir_geo; |
38 | struct xfs_dir2_sf_entry *sfep; |
39 | struct xfs_dir2_sf_hdr *sfp = dp->i_df.if_data; |
40 | xfs_ino_t ino; |
41 | xfs_dir2_dataptr_t dapos; |
42 | unsigned int i; |
43 | int error; |
44 | |
45 | ASSERT(dp->i_df.if_bytes == dp->i_disk_size); |
46 | ASSERT(sfp != NULL); |
47 | |
48 | /* dot entry */ |
49 | dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, |
50 | geo->data_entry_offset); |
51 | |
52 | error = dirent_fn(sc, dp, dapos, &name, dp->i_ino, priv); |
53 | if (error) |
54 | return error; |
55 | |
56 | /* dotdot entry */ |
57 | dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, |
58 | geo->data_entry_offset + |
59 | xfs_dir2_data_entsize(mp, sizeof("." ) - 1)); |
60 | ino = xfs_dir2_sf_get_parent_ino(sfp); |
61 | name.name = ".." ; |
62 | name.len = 2; |
63 | |
64 | error = dirent_fn(sc, dp, dapos, &name, ino, priv); |
65 | if (error) |
66 | return error; |
67 | |
68 | /* iterate everything else */ |
69 | sfep = xfs_dir2_sf_firstentry(sfp); |
70 | for (i = 0; i < sfp->count; i++) { |
71 | dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, |
72 | xfs_dir2_sf_get_offset(sfep)); |
73 | ino = xfs_dir2_sf_get_ino(mp, sfp, sfep); |
74 | name.name = sfep->name; |
75 | name.len = sfep->namelen; |
76 | name.type = xfs_dir2_sf_get_ftype(mp, sfep); |
77 | |
78 | error = dirent_fn(sc, dp, dapos, &name, ino, priv); |
79 | if (error) |
80 | return error; |
81 | |
82 | sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep); |
83 | } |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | /* Call a function for every entry in a block directory. */ |
89 | STATIC int |
90 | xchk_dir_walk_block( |
91 | struct xfs_scrub *sc, |
92 | struct xfs_inode *dp, |
93 | xchk_dirent_fn dirent_fn, |
94 | void *priv) |
95 | { |
96 | struct xfs_mount *mp = dp->i_mount; |
97 | struct xfs_da_geometry *geo = mp->m_dir_geo; |
98 | struct xfs_buf *bp; |
99 | unsigned int off, next_off, end; |
100 | int error; |
101 | |
102 | error = xfs_dir3_block_read(sc->tp, dp, &bp); |
103 | if (error) |
104 | return error; |
105 | |
106 | /* Walk each directory entry. */ |
107 | end = xfs_dir3_data_end_offset(geo, bp->b_addr); |
108 | for (off = geo->data_entry_offset; off < end; off = next_off) { |
109 | struct xfs_name name = { }; |
110 | struct xfs_dir2_data_unused *dup = bp->b_addr + off; |
111 | struct xfs_dir2_data_entry *dep = bp->b_addr + off; |
112 | xfs_ino_t ino; |
113 | xfs_dir2_dataptr_t dapos; |
114 | |
115 | /* Skip an empty entry. */ |
116 | if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { |
117 | next_off = off + be16_to_cpu(dup->length); |
118 | continue; |
119 | } |
120 | |
121 | /* Otherwise, find the next entry and report it. */ |
122 | next_off = off + xfs_dir2_data_entsize(mp, dep->namelen); |
123 | if (next_off > end) |
124 | break; |
125 | |
126 | dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, off); |
127 | ino = be64_to_cpu(dep->inumber); |
128 | name.name = dep->name; |
129 | name.len = dep->namelen; |
130 | name.type = xfs_dir2_data_get_ftype(mp, dep); |
131 | |
132 | error = dirent_fn(sc, dp, dapos, &name, ino, priv); |
133 | if (error) |
134 | break; |
135 | } |
136 | |
137 | xfs_trans_brelse(sc->tp, bp); |
138 | return error; |
139 | } |
140 | |
141 | /* Read a leaf-format directory buffer. */ |
142 | STATIC int |
143 | xchk_read_leaf_dir_buf( |
144 | struct xfs_trans *tp, |
145 | struct xfs_inode *dp, |
146 | struct xfs_da_geometry *geo, |
147 | xfs_dir2_off_t *curoff, |
148 | struct xfs_buf **bpp) |
149 | { |
150 | struct xfs_iext_cursor icur; |
151 | struct xfs_bmbt_irec map; |
152 | struct xfs_ifork *ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK); |
153 | xfs_dablk_t last_da; |
154 | xfs_dablk_t map_off; |
155 | xfs_dir2_off_t new_off; |
156 | |
157 | *bpp = NULL; |
158 | |
159 | /* |
160 | * Look for mapped directory blocks at or above the current offset. |
161 | * Truncate down to the nearest directory block to start the scanning |
162 | * operation. |
163 | */ |
164 | last_da = xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET); |
165 | map_off = xfs_dir2_db_to_da(geo, xfs_dir2_byte_to_db(geo, *curoff)); |
166 | |
167 | if (!xfs_iext_lookup_extent(dp, ifp, map_off, &icur, &map)) |
168 | return 0; |
169 | if (map.br_startoff >= last_da) |
170 | return 0; |
171 | xfs_trim_extent(&map, map_off, last_da - map_off); |
172 | |
173 | /* Read the directory block of that first mapping. */ |
174 | new_off = xfs_dir2_da_to_byte(geo, map.br_startoff); |
175 | if (new_off > *curoff) |
176 | *curoff = new_off; |
177 | |
178 | return xfs_dir3_data_read(tp, dp, map.br_startoff, 0, bpp); |
179 | } |
180 | |
181 | /* Call a function for every entry in a leaf directory. */ |
182 | STATIC int |
183 | xchk_dir_walk_leaf( |
184 | struct xfs_scrub *sc, |
185 | struct xfs_inode *dp, |
186 | xchk_dirent_fn dirent_fn, |
187 | void *priv) |
188 | { |
189 | struct xfs_mount *mp = dp->i_mount; |
190 | struct xfs_da_geometry *geo = mp->m_dir_geo; |
191 | struct xfs_buf *bp = NULL; |
192 | xfs_dir2_off_t curoff = 0; |
193 | unsigned int offset = 0; |
194 | int error; |
195 | |
196 | /* Iterate every directory offset in this directory. */ |
197 | while (curoff < XFS_DIR2_LEAF_OFFSET) { |
198 | struct xfs_name name = { }; |
199 | struct xfs_dir2_data_unused *dup; |
200 | struct xfs_dir2_data_entry *dep; |
201 | xfs_ino_t ino; |
202 | unsigned int length; |
203 | xfs_dir2_dataptr_t dapos; |
204 | |
205 | /* |
206 | * If we have no buffer, or we're off the end of the |
207 | * current buffer, need to get another one. |
208 | */ |
209 | if (!bp || offset >= geo->blksize) { |
210 | if (bp) { |
211 | xfs_trans_brelse(sc->tp, bp); |
212 | bp = NULL; |
213 | } |
214 | |
215 | error = xchk_read_leaf_dir_buf(sc->tp, dp, geo, &curoff, |
216 | &bp); |
217 | if (error || !bp) |
218 | break; |
219 | |
220 | /* |
221 | * Find our position in the block. |
222 | */ |
223 | offset = geo->data_entry_offset; |
224 | curoff += geo->data_entry_offset; |
225 | } |
226 | |
227 | /* Skip an empty entry. */ |
228 | dup = bp->b_addr + offset; |
229 | if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { |
230 | length = be16_to_cpu(dup->length); |
231 | offset += length; |
232 | curoff += length; |
233 | continue; |
234 | } |
235 | |
236 | /* Otherwise, find the next entry and report it. */ |
237 | dep = bp->b_addr + offset; |
238 | length = xfs_dir2_data_entsize(mp, dep->namelen); |
239 | |
240 | dapos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff; |
241 | ino = be64_to_cpu(dep->inumber); |
242 | name.name = dep->name; |
243 | name.len = dep->namelen; |
244 | name.type = xfs_dir2_data_get_ftype(mp, dep); |
245 | |
246 | error = dirent_fn(sc, dp, dapos, &name, ino, priv); |
247 | if (error) |
248 | break; |
249 | |
250 | /* Advance to the next entry. */ |
251 | offset += length; |
252 | curoff += length; |
253 | } |
254 | |
255 | if (bp) |
256 | xfs_trans_brelse(sc->tp, bp); |
257 | return error; |
258 | } |
259 | |
260 | /* |
261 | * Call a function for every entry in a directory. |
262 | * |
263 | * Callers must hold the ILOCK. File types are XFS_DIR3_FT_*. |
264 | */ |
265 | int |
266 | xchk_dir_walk( |
267 | struct xfs_scrub *sc, |
268 | struct xfs_inode *dp, |
269 | xchk_dirent_fn dirent_fn, |
270 | void *priv) |
271 | { |
272 | struct xfs_da_args args = { |
273 | .dp = dp, |
274 | .geo = dp->i_mount->m_dir_geo, |
275 | .trans = sc->tp, |
276 | }; |
277 | bool isblock; |
278 | int error; |
279 | |
280 | if (xfs_is_shutdown(dp->i_mount)) |
281 | return -EIO; |
282 | |
283 | ASSERT(S_ISDIR(VFS_I(dp)->i_mode)); |
284 | xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL); |
285 | |
286 | if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) |
287 | return xchk_dir_walk_sf(sc, dp, dirent_fn, priv); |
288 | |
289 | /* dir2 functions require that the data fork is loaded */ |
290 | error = xfs_iread_extents(sc->tp, dp, XFS_DATA_FORK); |
291 | if (error) |
292 | return error; |
293 | |
294 | error = xfs_dir2_isblock(&args, &isblock); |
295 | if (error) |
296 | return error; |
297 | |
298 | if (isblock) |
299 | return xchk_dir_walk_block(sc, dp, dirent_fn, priv); |
300 | |
301 | return xchk_dir_walk_leaf(sc, dp, dirent_fn, priv); |
302 | } |
303 | |
304 | /* |
305 | * Look up the inode number for an exact name in a directory. |
306 | * |
307 | * Callers must hold the ILOCK. File types are XFS_DIR3_FT_*. Names are not |
308 | * checked for correctness. |
309 | */ |
310 | int |
311 | xchk_dir_lookup( |
312 | struct xfs_scrub *sc, |
313 | struct xfs_inode *dp, |
314 | const struct xfs_name *name, |
315 | xfs_ino_t *ino) |
316 | { |
317 | struct xfs_da_args args = { |
318 | .dp = dp, |
319 | .geo = dp->i_mount->m_dir_geo, |
320 | .trans = sc->tp, |
321 | .name = name->name, |
322 | .namelen = name->len, |
323 | .filetype = name->type, |
324 | .hashval = xfs_dir2_hashname(dp->i_mount, name), |
325 | .whichfork = XFS_DATA_FORK, |
326 | .op_flags = XFS_DA_OP_OKNOENT, |
327 | }; |
328 | bool isblock, isleaf; |
329 | int error; |
330 | |
331 | if (xfs_is_shutdown(dp->i_mount)) |
332 | return -EIO; |
333 | |
334 | ASSERT(S_ISDIR(VFS_I(dp)->i_mode)); |
335 | xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL); |
336 | |
337 | if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) { |
338 | error = xfs_dir2_sf_lookup(&args); |
339 | goto out_check_rval; |
340 | } |
341 | |
342 | /* dir2 functions require that the data fork is loaded */ |
343 | error = xfs_iread_extents(sc->tp, dp, XFS_DATA_FORK); |
344 | if (error) |
345 | return error; |
346 | |
347 | error = xfs_dir2_isblock(&args, &isblock); |
348 | if (error) |
349 | return error; |
350 | |
351 | if (isblock) { |
352 | error = xfs_dir2_block_lookup(&args); |
353 | goto out_check_rval; |
354 | } |
355 | |
356 | error = xfs_dir2_isleaf(&args, &isleaf); |
357 | if (error) |
358 | return error; |
359 | |
360 | if (isleaf) { |
361 | error = xfs_dir2_leaf_lookup(&args); |
362 | goto out_check_rval; |
363 | } |
364 | |
365 | error = xfs_dir2_node_lookup(&args); |
366 | |
367 | out_check_rval: |
368 | if (error == -EEXIST) |
369 | error = 0; |
370 | if (!error) |
371 | *ino = args.inumber; |
372 | return error; |
373 | } |
374 | |