xref: /linux/fs/xfs/scrub/readdir.c (revision ba3193fa8fc8910f724b67a523ec67ee24997d3e)
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