162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2000-2005 Silicon Graphics, Inc.
462306a36Sopenharmony_ci * Copyright (c) 2013 Red Hat, Inc.
562306a36Sopenharmony_ci * All Rights Reserved.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include "xfs.h"
862306a36Sopenharmony_ci#include "xfs_fs.h"
962306a36Sopenharmony_ci#include "xfs_shared.h"
1062306a36Sopenharmony_ci#include "xfs_format.h"
1162306a36Sopenharmony_ci#include "xfs_log_format.h"
1262306a36Sopenharmony_ci#include "xfs_trans_resv.h"
1362306a36Sopenharmony_ci#include "xfs_mount.h"
1462306a36Sopenharmony_ci#include "xfs_inode.h"
1562306a36Sopenharmony_ci#include "xfs_dir2.h"
1662306a36Sopenharmony_ci#include "xfs_dir2_priv.h"
1762306a36Sopenharmony_ci#include "xfs_trace.h"
1862306a36Sopenharmony_ci#include "xfs_bmap.h"
1962306a36Sopenharmony_ci#include "xfs_trans.h"
2062306a36Sopenharmony_ci#include "xfs_error.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/*
2362306a36Sopenharmony_ci * Directory file type support functions
2462306a36Sopenharmony_ci */
2562306a36Sopenharmony_cistatic unsigned char xfs_dir3_filetype_table[] = {
2662306a36Sopenharmony_ci	DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK,
2762306a36Sopenharmony_ci	DT_FIFO, DT_SOCK, DT_LNK, DT_WHT,
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ciunsigned char
3162306a36Sopenharmony_cixfs_dir3_get_dtype(
3262306a36Sopenharmony_ci	struct xfs_mount	*mp,
3362306a36Sopenharmony_ci	uint8_t			filetype)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	if (!xfs_has_ftype(mp))
3662306a36Sopenharmony_ci		return DT_UNKNOWN;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (filetype >= XFS_DIR3_FT_MAX)
3962306a36Sopenharmony_ci		return DT_UNKNOWN;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	return xfs_dir3_filetype_table[filetype];
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ciSTATIC int
4562306a36Sopenharmony_cixfs_dir2_sf_getdents(
4662306a36Sopenharmony_ci	struct xfs_da_args	*args,
4762306a36Sopenharmony_ci	struct dir_context	*ctx)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	int			i;		/* shortform entry number */
5062306a36Sopenharmony_ci	struct xfs_inode	*dp = args->dp;	/* incore directory inode */
5162306a36Sopenharmony_ci	struct xfs_mount	*mp = dp->i_mount;
5262306a36Sopenharmony_ci	xfs_dir2_dataptr_t	off;		/* current entry's offset */
5362306a36Sopenharmony_ci	xfs_dir2_sf_entry_t	*sfep;		/* shortform directory entry */
5462306a36Sopenharmony_ci	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
5562306a36Sopenharmony_ci	xfs_dir2_dataptr_t	dot_offset;
5662306a36Sopenharmony_ci	xfs_dir2_dataptr_t	dotdot_offset;
5762306a36Sopenharmony_ci	xfs_ino_t		ino;
5862306a36Sopenharmony_ci	struct xfs_da_geometry	*geo = args->geo;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	ASSERT(dp->i_df.if_format == XFS_DINODE_FMT_LOCAL);
6162306a36Sopenharmony_ci	ASSERT(dp->i_df.if_bytes == dp->i_disk_size);
6262306a36Sopenharmony_ci	ASSERT(dp->i_df.if_u1.if_data != NULL);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	/*
6762306a36Sopenharmony_ci	 * If the block number in the offset is out of range, we're done.
6862306a36Sopenharmony_ci	 */
6962306a36Sopenharmony_ci	if (xfs_dir2_dataptr_to_db(geo, ctx->pos) > geo->datablk)
7062306a36Sopenharmony_ci		return 0;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/*
7362306a36Sopenharmony_ci	 * Precalculate offsets for "." and ".." as we will always need them.
7462306a36Sopenharmony_ci	 * This relies on the fact that directories always start with the
7562306a36Sopenharmony_ci	 * entries for "." and "..".
7662306a36Sopenharmony_ci	 */
7762306a36Sopenharmony_ci	dot_offset = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
7862306a36Sopenharmony_ci			geo->data_entry_offset);
7962306a36Sopenharmony_ci	dotdot_offset = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
8062306a36Sopenharmony_ci			geo->data_entry_offset +
8162306a36Sopenharmony_ci			xfs_dir2_data_entsize(mp, sizeof(".") - 1));
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/*
8462306a36Sopenharmony_ci	 * Put . entry unless we're starting past it.
8562306a36Sopenharmony_ci	 */
8662306a36Sopenharmony_ci	if (ctx->pos <= dot_offset) {
8762306a36Sopenharmony_ci		ctx->pos = dot_offset & 0x7fffffff;
8862306a36Sopenharmony_ci		if (!dir_emit(ctx, ".", 1, dp->i_ino, DT_DIR))
8962306a36Sopenharmony_ci			return 0;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/*
9362306a36Sopenharmony_ci	 * Put .. entry unless we're starting past it.
9462306a36Sopenharmony_ci	 */
9562306a36Sopenharmony_ci	if (ctx->pos <= dotdot_offset) {
9662306a36Sopenharmony_ci		ino = xfs_dir2_sf_get_parent_ino(sfp);
9762306a36Sopenharmony_ci		ctx->pos = dotdot_offset & 0x7fffffff;
9862306a36Sopenharmony_ci		if (!dir_emit(ctx, "..", 2, ino, DT_DIR))
9962306a36Sopenharmony_ci			return 0;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/*
10362306a36Sopenharmony_ci	 * Loop while there are more entries and put'ing works.
10462306a36Sopenharmony_ci	 */
10562306a36Sopenharmony_ci	sfep = xfs_dir2_sf_firstentry(sfp);
10662306a36Sopenharmony_ci	for (i = 0; i < sfp->count; i++) {
10762306a36Sopenharmony_ci		uint8_t filetype;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
11062306a36Sopenharmony_ci				xfs_dir2_sf_get_offset(sfep));
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci		if (ctx->pos > off) {
11362306a36Sopenharmony_ci			sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
11462306a36Sopenharmony_ci			continue;
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci		ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
11862306a36Sopenharmony_ci		filetype = xfs_dir2_sf_get_ftype(mp, sfep);
11962306a36Sopenharmony_ci		ctx->pos = off & 0x7fffffff;
12062306a36Sopenharmony_ci		if (XFS_IS_CORRUPT(dp->i_mount,
12162306a36Sopenharmony_ci				   !xfs_dir2_namecheck(sfep->name,
12262306a36Sopenharmony_ci						       sfep->namelen)))
12362306a36Sopenharmony_ci			return -EFSCORRUPTED;
12462306a36Sopenharmony_ci		if (!dir_emit(ctx, (char *)sfep->name, sfep->namelen, ino,
12562306a36Sopenharmony_ci			    xfs_dir3_get_dtype(mp, filetype)))
12662306a36Sopenharmony_ci			return 0;
12762306a36Sopenharmony_ci		sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	ctx->pos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk + 1, 0) &
13162306a36Sopenharmony_ci								0x7fffffff;
13262306a36Sopenharmony_ci	return 0;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/*
13662306a36Sopenharmony_ci * Readdir for block directories.
13762306a36Sopenharmony_ci */
13862306a36Sopenharmony_ciSTATIC int
13962306a36Sopenharmony_cixfs_dir2_block_getdents(
14062306a36Sopenharmony_ci	struct xfs_da_args	*args,
14162306a36Sopenharmony_ci	struct dir_context	*ctx,
14262306a36Sopenharmony_ci	unsigned int		*lock_mode)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	struct xfs_inode	*dp = args->dp;	/* incore directory inode */
14562306a36Sopenharmony_ci	struct xfs_buf		*bp;		/* buffer for block */
14662306a36Sopenharmony_ci	int			error;		/* error return value */
14762306a36Sopenharmony_ci	int			wantoff;	/* starting block offset */
14862306a36Sopenharmony_ci	xfs_off_t		cook;
14962306a36Sopenharmony_ci	struct xfs_da_geometry	*geo = args->geo;
15062306a36Sopenharmony_ci	unsigned int		offset, next_offset;
15162306a36Sopenharmony_ci	unsigned int		end;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/*
15462306a36Sopenharmony_ci	 * If the block number in the offset is out of range, we're done.
15562306a36Sopenharmony_ci	 */
15662306a36Sopenharmony_ci	if (xfs_dir2_dataptr_to_db(geo, ctx->pos) > geo->datablk)
15762306a36Sopenharmony_ci		return 0;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	error = xfs_dir3_block_read(args->trans, dp, &bp);
16062306a36Sopenharmony_ci	if (error)
16162306a36Sopenharmony_ci		return error;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	xfs_iunlock(dp, *lock_mode);
16462306a36Sopenharmony_ci	*lock_mode = 0;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/*
16762306a36Sopenharmony_ci	 * Extract the byte offset we start at from the seek pointer.
16862306a36Sopenharmony_ci	 * We'll skip entries before this.
16962306a36Sopenharmony_ci	 */
17062306a36Sopenharmony_ci	wantoff = xfs_dir2_dataptr_to_off(geo, ctx->pos);
17162306a36Sopenharmony_ci	xfs_dir3_data_check(dp, bp);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/*
17462306a36Sopenharmony_ci	 * Loop over the data portion of the block.
17562306a36Sopenharmony_ci	 * Each object is a real entry (dep) or an unused one (dup).
17662306a36Sopenharmony_ci	 */
17762306a36Sopenharmony_ci	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
17862306a36Sopenharmony_ci	for (offset = geo->data_entry_offset;
17962306a36Sopenharmony_ci	     offset < end;
18062306a36Sopenharmony_ci	     offset = next_offset) {
18162306a36Sopenharmony_ci		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
18262306a36Sopenharmony_ci		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
18362306a36Sopenharmony_ci		uint8_t filetype;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci		/*
18662306a36Sopenharmony_ci		 * Unused, skip it.
18762306a36Sopenharmony_ci		 */
18862306a36Sopenharmony_ci		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
18962306a36Sopenharmony_ci			next_offset = offset + be16_to_cpu(dup->length);
19062306a36Sopenharmony_ci			continue;
19162306a36Sopenharmony_ci		}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci		/*
19462306a36Sopenharmony_ci		 * Bump pointer for the next iteration.
19562306a36Sopenharmony_ci		 */
19662306a36Sopenharmony_ci		next_offset = offset +
19762306a36Sopenharmony_ci			xfs_dir2_data_entsize(dp->i_mount, dep->namelen);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci		/*
20062306a36Sopenharmony_ci		 * The entry is before the desired starting point, skip it.
20162306a36Sopenharmony_ci		 */
20262306a36Sopenharmony_ci		if (offset < wantoff)
20362306a36Sopenharmony_ci			continue;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		cook = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		ctx->pos = cook & 0x7fffffff;
20862306a36Sopenharmony_ci		filetype = xfs_dir2_data_get_ftype(dp->i_mount, dep);
20962306a36Sopenharmony_ci		/*
21062306a36Sopenharmony_ci		 * If it didn't fit, set the final offset to here & return.
21162306a36Sopenharmony_ci		 */
21262306a36Sopenharmony_ci		if (XFS_IS_CORRUPT(dp->i_mount,
21362306a36Sopenharmony_ci				   !xfs_dir2_namecheck(dep->name,
21462306a36Sopenharmony_ci						       dep->namelen))) {
21562306a36Sopenharmony_ci			error = -EFSCORRUPTED;
21662306a36Sopenharmony_ci			goto out_rele;
21762306a36Sopenharmony_ci		}
21862306a36Sopenharmony_ci		if (!dir_emit(ctx, (char *)dep->name, dep->namelen,
21962306a36Sopenharmony_ci			    be64_to_cpu(dep->inumber),
22062306a36Sopenharmony_ci			    xfs_dir3_get_dtype(dp->i_mount, filetype)))
22162306a36Sopenharmony_ci			goto out_rele;
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	/*
22562306a36Sopenharmony_ci	 * Reached the end of the block.
22662306a36Sopenharmony_ci	 * Set the offset to a non-existent block 1 and return.
22762306a36Sopenharmony_ci	 */
22862306a36Sopenharmony_ci	ctx->pos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk + 1, 0) &
22962306a36Sopenharmony_ci								0x7fffffff;
23062306a36Sopenharmony_ciout_rele:
23162306a36Sopenharmony_ci	xfs_trans_brelse(args->trans, bp);
23262306a36Sopenharmony_ci	return error;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci/*
23662306a36Sopenharmony_ci * Read a directory block and initiate readahead for blocks beyond that.
23762306a36Sopenharmony_ci * We maintain a sliding readahead window of the remaining space in the
23862306a36Sopenharmony_ci * buffer rounded up to the nearest block.
23962306a36Sopenharmony_ci */
24062306a36Sopenharmony_ciSTATIC int
24162306a36Sopenharmony_cixfs_dir2_leaf_readbuf(
24262306a36Sopenharmony_ci	struct xfs_da_args	*args,
24362306a36Sopenharmony_ci	size_t			bufsize,
24462306a36Sopenharmony_ci	xfs_dir2_off_t		*cur_off,
24562306a36Sopenharmony_ci	xfs_dablk_t		*ra_blk,
24662306a36Sopenharmony_ci	struct xfs_buf		**bpp)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct xfs_inode	*dp = args->dp;
24962306a36Sopenharmony_ci	struct xfs_buf		*bp = NULL;
25062306a36Sopenharmony_ci	struct xfs_da_geometry	*geo = args->geo;
25162306a36Sopenharmony_ci	struct xfs_ifork	*ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK);
25262306a36Sopenharmony_ci	struct xfs_bmbt_irec	map;
25362306a36Sopenharmony_ci	struct blk_plug		plug;
25462306a36Sopenharmony_ci	xfs_dir2_off_t		new_off;
25562306a36Sopenharmony_ci	xfs_dablk_t		next_ra;
25662306a36Sopenharmony_ci	xfs_dablk_t		map_off;
25762306a36Sopenharmony_ci	xfs_dablk_t		last_da;
25862306a36Sopenharmony_ci	struct xfs_iext_cursor	icur;
25962306a36Sopenharmony_ci	int			ra_want;
26062306a36Sopenharmony_ci	int			error = 0;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	error = xfs_iread_extents(args->trans, dp, XFS_DATA_FORK);
26362306a36Sopenharmony_ci	if (error)
26462306a36Sopenharmony_ci		goto out;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/*
26762306a36Sopenharmony_ci	 * Look for mapped directory blocks at or above the current offset.
26862306a36Sopenharmony_ci	 * Truncate down to the nearest directory block to start the scanning
26962306a36Sopenharmony_ci	 * operation.
27062306a36Sopenharmony_ci	 */
27162306a36Sopenharmony_ci	last_da = xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET);
27262306a36Sopenharmony_ci	map_off = xfs_dir2_db_to_da(geo, xfs_dir2_byte_to_db(geo, *cur_off));
27362306a36Sopenharmony_ci	if (!xfs_iext_lookup_extent(dp, ifp, map_off, &icur, &map))
27462306a36Sopenharmony_ci		goto out;
27562306a36Sopenharmony_ci	if (map.br_startoff >= last_da)
27662306a36Sopenharmony_ci		goto out;
27762306a36Sopenharmony_ci	xfs_trim_extent(&map, map_off, last_da - map_off);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* Read the directory block of that first mapping. */
28062306a36Sopenharmony_ci	new_off = xfs_dir2_da_to_byte(geo, map.br_startoff);
28162306a36Sopenharmony_ci	if (new_off > *cur_off)
28262306a36Sopenharmony_ci		*cur_off = new_off;
28362306a36Sopenharmony_ci	error = xfs_dir3_data_read(args->trans, dp, map.br_startoff, 0, &bp);
28462306a36Sopenharmony_ci	if (error)
28562306a36Sopenharmony_ci		goto out;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	/*
28862306a36Sopenharmony_ci	 * Start readahead for the next bufsize's worth of dir data blocks.
28962306a36Sopenharmony_ci	 * We may have already issued readahead for some of that range;
29062306a36Sopenharmony_ci	 * ra_blk tracks the last block we tried to read(ahead).
29162306a36Sopenharmony_ci	 */
29262306a36Sopenharmony_ci	ra_want = howmany(bufsize + geo->blksize, (1 << geo->fsblog));
29362306a36Sopenharmony_ci	if (*ra_blk >= last_da)
29462306a36Sopenharmony_ci		goto out;
29562306a36Sopenharmony_ci	else if (*ra_blk == 0)
29662306a36Sopenharmony_ci		*ra_blk = map.br_startoff;
29762306a36Sopenharmony_ci	next_ra = map.br_startoff + geo->fsbcount;
29862306a36Sopenharmony_ci	if (next_ra >= last_da)
29962306a36Sopenharmony_ci		goto out_no_ra;
30062306a36Sopenharmony_ci	if (map.br_blockcount < geo->fsbcount &&
30162306a36Sopenharmony_ci	    !xfs_iext_next_extent(ifp, &icur, &map))
30262306a36Sopenharmony_ci		goto out_no_ra;
30362306a36Sopenharmony_ci	if (map.br_startoff >= last_da)
30462306a36Sopenharmony_ci		goto out_no_ra;
30562306a36Sopenharmony_ci	xfs_trim_extent(&map, next_ra, last_da - next_ra);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	/* Start ra for each dir (not fs) block that has a mapping. */
30862306a36Sopenharmony_ci	blk_start_plug(&plug);
30962306a36Sopenharmony_ci	while (ra_want > 0) {
31062306a36Sopenharmony_ci		next_ra = roundup((xfs_dablk_t)map.br_startoff, geo->fsbcount);
31162306a36Sopenharmony_ci		while (ra_want > 0 &&
31262306a36Sopenharmony_ci		       next_ra < map.br_startoff + map.br_blockcount) {
31362306a36Sopenharmony_ci			if (next_ra >= last_da) {
31462306a36Sopenharmony_ci				*ra_blk = last_da;
31562306a36Sopenharmony_ci				break;
31662306a36Sopenharmony_ci			}
31762306a36Sopenharmony_ci			if (next_ra > *ra_blk) {
31862306a36Sopenharmony_ci				xfs_dir3_data_readahead(dp, next_ra,
31962306a36Sopenharmony_ci							XFS_DABUF_MAP_HOLE_OK);
32062306a36Sopenharmony_ci				*ra_blk = next_ra;
32162306a36Sopenharmony_ci			}
32262306a36Sopenharmony_ci			ra_want -= geo->fsbcount;
32362306a36Sopenharmony_ci			next_ra += geo->fsbcount;
32462306a36Sopenharmony_ci		}
32562306a36Sopenharmony_ci		if (!xfs_iext_next_extent(ifp, &icur, &map)) {
32662306a36Sopenharmony_ci			*ra_blk = last_da;
32762306a36Sopenharmony_ci			break;
32862306a36Sopenharmony_ci		}
32962306a36Sopenharmony_ci	}
33062306a36Sopenharmony_ci	blk_finish_plug(&plug);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ciout:
33362306a36Sopenharmony_ci	*bpp = bp;
33462306a36Sopenharmony_ci	return error;
33562306a36Sopenharmony_ciout_no_ra:
33662306a36Sopenharmony_ci	*ra_blk = last_da;
33762306a36Sopenharmony_ci	goto out;
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci/*
34162306a36Sopenharmony_ci * Getdents (readdir) for leaf and node directories.
34262306a36Sopenharmony_ci * This reads the data blocks only, so is the same for both forms.
34362306a36Sopenharmony_ci */
34462306a36Sopenharmony_ciSTATIC int
34562306a36Sopenharmony_cixfs_dir2_leaf_getdents(
34662306a36Sopenharmony_ci	struct xfs_da_args	*args,
34762306a36Sopenharmony_ci	struct dir_context	*ctx,
34862306a36Sopenharmony_ci	size_t			bufsize,
34962306a36Sopenharmony_ci	unsigned int		*lock_mode)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	struct xfs_inode	*dp = args->dp;
35262306a36Sopenharmony_ci	struct xfs_mount	*mp = dp->i_mount;
35362306a36Sopenharmony_ci	struct xfs_buf		*bp = NULL;	/* data block buffer */
35462306a36Sopenharmony_ci	xfs_dir2_data_entry_t	*dep;		/* data entry */
35562306a36Sopenharmony_ci	xfs_dir2_data_unused_t	*dup;		/* unused entry */
35662306a36Sopenharmony_ci	struct xfs_da_geometry	*geo = args->geo;
35762306a36Sopenharmony_ci	xfs_dablk_t		rablk = 0;	/* current readahead block */
35862306a36Sopenharmony_ci	xfs_dir2_off_t		curoff;		/* current overall offset */
35962306a36Sopenharmony_ci	int			length;		/* temporary length value */
36062306a36Sopenharmony_ci	int			byteoff;	/* offset in current block */
36162306a36Sopenharmony_ci	unsigned int		offset = 0;
36262306a36Sopenharmony_ci	int			error = 0;	/* error return value */
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	/*
36562306a36Sopenharmony_ci	 * If the offset is at or past the largest allowed value,
36662306a36Sopenharmony_ci	 * give up right away.
36762306a36Sopenharmony_ci	 */
36862306a36Sopenharmony_ci	if (ctx->pos >= XFS_DIR2_MAX_DATAPTR)
36962306a36Sopenharmony_ci		return 0;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	/*
37262306a36Sopenharmony_ci	 * Inside the loop we keep the main offset value as a byte offset
37362306a36Sopenharmony_ci	 * in the directory file.
37462306a36Sopenharmony_ci	 */
37562306a36Sopenharmony_ci	curoff = xfs_dir2_dataptr_to_byte(ctx->pos);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	/*
37862306a36Sopenharmony_ci	 * Loop over directory entries until we reach the end offset.
37962306a36Sopenharmony_ci	 * Get more blocks and readahead as necessary.
38062306a36Sopenharmony_ci	 */
38162306a36Sopenharmony_ci	while (curoff < XFS_DIR2_LEAF_OFFSET) {
38262306a36Sopenharmony_ci		uint8_t filetype;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci		/*
38562306a36Sopenharmony_ci		 * If we have no buffer, or we're off the end of the
38662306a36Sopenharmony_ci		 * current buffer, need to get another one.
38762306a36Sopenharmony_ci		 */
38862306a36Sopenharmony_ci		if (!bp || offset >= geo->blksize) {
38962306a36Sopenharmony_ci			if (bp) {
39062306a36Sopenharmony_ci				xfs_trans_brelse(args->trans, bp);
39162306a36Sopenharmony_ci				bp = NULL;
39262306a36Sopenharmony_ci			}
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci			if (*lock_mode == 0)
39562306a36Sopenharmony_ci				*lock_mode = xfs_ilock_data_map_shared(dp);
39662306a36Sopenharmony_ci			error = xfs_dir2_leaf_readbuf(args, bufsize, &curoff,
39762306a36Sopenharmony_ci					&rablk, &bp);
39862306a36Sopenharmony_ci			if (error || !bp)
39962306a36Sopenharmony_ci				break;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci			xfs_iunlock(dp, *lock_mode);
40262306a36Sopenharmony_ci			*lock_mode = 0;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci			xfs_dir3_data_check(dp, bp);
40562306a36Sopenharmony_ci			/*
40662306a36Sopenharmony_ci			 * Find our position in the block.
40762306a36Sopenharmony_ci			 */
40862306a36Sopenharmony_ci			offset = geo->data_entry_offset;
40962306a36Sopenharmony_ci			byteoff = xfs_dir2_byte_to_off(geo, curoff);
41062306a36Sopenharmony_ci			/*
41162306a36Sopenharmony_ci			 * Skip past the header.
41262306a36Sopenharmony_ci			 */
41362306a36Sopenharmony_ci			if (byteoff == 0)
41462306a36Sopenharmony_ci				curoff += geo->data_entry_offset;
41562306a36Sopenharmony_ci			/*
41662306a36Sopenharmony_ci			 * Skip past entries until we reach our offset.
41762306a36Sopenharmony_ci			 */
41862306a36Sopenharmony_ci			else {
41962306a36Sopenharmony_ci				while (offset < byteoff) {
42062306a36Sopenharmony_ci					dup = bp->b_addr + offset;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci					if (be16_to_cpu(dup->freetag)
42362306a36Sopenharmony_ci						  == XFS_DIR2_DATA_FREE_TAG) {
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci						length = be16_to_cpu(dup->length);
42662306a36Sopenharmony_ci						offset += length;
42762306a36Sopenharmony_ci						continue;
42862306a36Sopenharmony_ci					}
42962306a36Sopenharmony_ci					dep = bp->b_addr + offset;
43062306a36Sopenharmony_ci					length = xfs_dir2_data_entsize(mp,
43162306a36Sopenharmony_ci							dep->namelen);
43262306a36Sopenharmony_ci					offset += length;
43362306a36Sopenharmony_ci				}
43462306a36Sopenharmony_ci				/*
43562306a36Sopenharmony_ci				 * Now set our real offset.
43662306a36Sopenharmony_ci				 */
43762306a36Sopenharmony_ci				curoff =
43862306a36Sopenharmony_ci					xfs_dir2_db_off_to_byte(geo,
43962306a36Sopenharmony_ci					    xfs_dir2_byte_to_db(geo, curoff),
44062306a36Sopenharmony_ci					    offset);
44162306a36Sopenharmony_ci				if (offset >= geo->blksize)
44262306a36Sopenharmony_ci					continue;
44362306a36Sopenharmony_ci			}
44462306a36Sopenharmony_ci		}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci		/*
44762306a36Sopenharmony_ci		 * We have a pointer to an entry.  Is it a live one?
44862306a36Sopenharmony_ci		 */
44962306a36Sopenharmony_ci		dup = bp->b_addr + offset;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci		/*
45262306a36Sopenharmony_ci		 * No, it's unused, skip over it.
45362306a36Sopenharmony_ci		 */
45462306a36Sopenharmony_ci		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
45562306a36Sopenharmony_ci			length = be16_to_cpu(dup->length);
45662306a36Sopenharmony_ci			offset += length;
45762306a36Sopenharmony_ci			curoff += length;
45862306a36Sopenharmony_ci			continue;
45962306a36Sopenharmony_ci		}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci		dep = bp->b_addr + offset;
46262306a36Sopenharmony_ci		length = xfs_dir2_data_entsize(mp, dep->namelen);
46362306a36Sopenharmony_ci		filetype = xfs_dir2_data_get_ftype(mp, dep);
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci		ctx->pos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff;
46662306a36Sopenharmony_ci		if (XFS_IS_CORRUPT(dp->i_mount,
46762306a36Sopenharmony_ci				   !xfs_dir2_namecheck(dep->name,
46862306a36Sopenharmony_ci						       dep->namelen))) {
46962306a36Sopenharmony_ci			error = -EFSCORRUPTED;
47062306a36Sopenharmony_ci			break;
47162306a36Sopenharmony_ci		}
47262306a36Sopenharmony_ci		if (!dir_emit(ctx, (char *)dep->name, dep->namelen,
47362306a36Sopenharmony_ci			    be64_to_cpu(dep->inumber),
47462306a36Sopenharmony_ci			    xfs_dir3_get_dtype(dp->i_mount, filetype)))
47562306a36Sopenharmony_ci			break;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci		/*
47862306a36Sopenharmony_ci		 * Advance to next entry in the block.
47962306a36Sopenharmony_ci		 */
48062306a36Sopenharmony_ci		offset += length;
48162306a36Sopenharmony_ci		curoff += length;
48262306a36Sopenharmony_ci		/* bufsize may have just been a guess; don't go negative */
48362306a36Sopenharmony_ci		bufsize = bufsize > length ? bufsize - length : 0;
48462306a36Sopenharmony_ci	}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	/*
48762306a36Sopenharmony_ci	 * All done.  Set output offset value to current offset.
48862306a36Sopenharmony_ci	 */
48962306a36Sopenharmony_ci	if (curoff > xfs_dir2_dataptr_to_byte(XFS_DIR2_MAX_DATAPTR))
49062306a36Sopenharmony_ci		ctx->pos = XFS_DIR2_MAX_DATAPTR & 0x7fffffff;
49162306a36Sopenharmony_ci	else
49262306a36Sopenharmony_ci		ctx->pos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff;
49362306a36Sopenharmony_ci	if (bp)
49462306a36Sopenharmony_ci		xfs_trans_brelse(args->trans, bp);
49562306a36Sopenharmony_ci	return error;
49662306a36Sopenharmony_ci}
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci/*
49962306a36Sopenharmony_ci * Read a directory.
50062306a36Sopenharmony_ci *
50162306a36Sopenharmony_ci * If supplied, the transaction collects locked dir buffers to avoid
50262306a36Sopenharmony_ci * nested buffer deadlocks.  This function does not dirty the
50362306a36Sopenharmony_ci * transaction.  The caller must hold the IOLOCK (shared or exclusive)
50462306a36Sopenharmony_ci * before calling this function.
50562306a36Sopenharmony_ci */
50662306a36Sopenharmony_ciint
50762306a36Sopenharmony_cixfs_readdir(
50862306a36Sopenharmony_ci	struct xfs_trans	*tp,
50962306a36Sopenharmony_ci	struct xfs_inode	*dp,
51062306a36Sopenharmony_ci	struct dir_context	*ctx,
51162306a36Sopenharmony_ci	size_t			bufsize)
51262306a36Sopenharmony_ci{
51362306a36Sopenharmony_ci	struct xfs_da_args	args = { NULL };
51462306a36Sopenharmony_ci	unsigned int		lock_mode;
51562306a36Sopenharmony_ci	bool			isblock;
51662306a36Sopenharmony_ci	int			error;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	trace_xfs_readdir(dp);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	if (xfs_is_shutdown(dp->i_mount))
52162306a36Sopenharmony_ci		return -EIO;
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
52462306a36Sopenharmony_ci	ASSERT(xfs_isilocked(dp, XFS_IOLOCK_SHARED | XFS_IOLOCK_EXCL));
52562306a36Sopenharmony_ci	XFS_STATS_INC(dp->i_mount, xs_dir_getdents);
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	args.dp = dp;
52862306a36Sopenharmony_ci	args.geo = dp->i_mount->m_dir_geo;
52962306a36Sopenharmony_ci	args.trans = tp;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
53262306a36Sopenharmony_ci		return xfs_dir2_sf_getdents(&args, ctx);
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	lock_mode = xfs_ilock_data_map_shared(dp);
53562306a36Sopenharmony_ci	error = xfs_dir2_isblock(&args, &isblock);
53662306a36Sopenharmony_ci	if (error)
53762306a36Sopenharmony_ci		goto out_unlock;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	if (isblock) {
54062306a36Sopenharmony_ci		error = xfs_dir2_block_getdents(&args, ctx, &lock_mode);
54162306a36Sopenharmony_ci		goto out_unlock;
54262306a36Sopenharmony_ci	}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	error = xfs_dir2_leaf_getdents(&args, ctx, bufsize, &lock_mode);
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ciout_unlock:
54762306a36Sopenharmony_ci	if (lock_mode)
54862306a36Sopenharmony_ci		xfs_iunlock(dp, lock_mode);
54962306a36Sopenharmony_ci	return error;
55062306a36Sopenharmony_ci}
551