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. */
24STATIC int
25xchk_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. */
89STATIC int
90xchk_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. */
142STATIC int
143xchk_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. */
182STATIC int
183xchk_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 */
265int
266xchk_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 */
310int
311xchk_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
367out_check_rval:
368 if (error == -EEXIST)
369 error = 0;
370 if (!error)
371 *ino = args.inumber;
372 return error;
373}
374

source code of linux/fs/xfs/scrub/readdir.c