162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2017-2023 Oracle.  All Rights Reserved.
462306a36Sopenharmony_ci * Author: Darrick J. Wong <djwong@kernel.org>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include "xfs.h"
762306a36Sopenharmony_ci#include "xfs_fs.h"
862306a36Sopenharmony_ci#include "xfs_shared.h"
962306a36Sopenharmony_ci#include "xfs_format.h"
1062306a36Sopenharmony_ci#include "xfs_trans_resv.h"
1162306a36Sopenharmony_ci#include "xfs_mount.h"
1262306a36Sopenharmony_ci#include "xfs_log_format.h"
1362306a36Sopenharmony_ci#include "xfs_trans.h"
1462306a36Sopenharmony_ci#include "xfs_inode.h"
1562306a36Sopenharmony_ci#include "xfs_icache.h"
1662306a36Sopenharmony_ci#include "xfs_dir2.h"
1762306a36Sopenharmony_ci#include "xfs_dir2_priv.h"
1862306a36Sopenharmony_ci#include "scrub/scrub.h"
1962306a36Sopenharmony_ci#include "scrub/common.h"
2062306a36Sopenharmony_ci#include "scrub/dabtree.h"
2162306a36Sopenharmony_ci#include "scrub/readdir.h"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* Set us up to scrub directories. */
2462306a36Sopenharmony_ciint
2562306a36Sopenharmony_cixchk_setup_directory(
2662306a36Sopenharmony_ci	struct xfs_scrub	*sc)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	return xchk_setup_inode_contents(sc, 0);
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* Directories */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/* Scrub a directory entry. */
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/* Check that an inode's mode matches a given XFS_DIR3_FT_* type. */
3662306a36Sopenharmony_ciSTATIC void
3762306a36Sopenharmony_cixchk_dir_check_ftype(
3862306a36Sopenharmony_ci	struct xfs_scrub	*sc,
3962306a36Sopenharmony_ci	xfs_fileoff_t		offset,
4062306a36Sopenharmony_ci	struct xfs_inode	*ip,
4162306a36Sopenharmony_ci	int			ftype)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct xfs_mount	*mp = sc->mp;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	if (!xfs_has_ftype(mp)) {
4662306a36Sopenharmony_ci		if (ftype != XFS_DIR3_FT_UNKNOWN && ftype != XFS_DIR3_FT_DIR)
4762306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
4862306a36Sopenharmony_ci		return;
4962306a36Sopenharmony_ci	}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if (xfs_mode_to_ftype(VFS_I(ip)->i_mode) != ftype)
5262306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/*
5662306a36Sopenharmony_ci * Scrub a single directory entry.
5762306a36Sopenharmony_ci *
5862306a36Sopenharmony_ci * Check the inode number to make sure it's sane, then we check that we can
5962306a36Sopenharmony_ci * look up this filename.  Finally, we check the ftype.
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_ciSTATIC int
6262306a36Sopenharmony_cixchk_dir_actor(
6362306a36Sopenharmony_ci	struct xfs_scrub	*sc,
6462306a36Sopenharmony_ci	struct xfs_inode	*dp,
6562306a36Sopenharmony_ci	xfs_dir2_dataptr_t	dapos,
6662306a36Sopenharmony_ci	const struct xfs_name	*name,
6762306a36Sopenharmony_ci	xfs_ino_t		ino,
6862306a36Sopenharmony_ci	void			*priv)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct xfs_mount	*mp = dp->i_mount;
7162306a36Sopenharmony_ci	struct xfs_inode	*ip;
7262306a36Sopenharmony_ci	xfs_ino_t		lookup_ino;
7362306a36Sopenharmony_ci	xfs_dablk_t		offset;
7462306a36Sopenharmony_ci	int			error = 0;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	offset = xfs_dir2_db_to_da(mp->m_dir_geo,
7762306a36Sopenharmony_ci			xfs_dir2_dataptr_to_db(mp->m_dir_geo, dapos));
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (xchk_should_terminate(sc, &error))
8062306a36Sopenharmony_ci		return error;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	/* Does this inode number make sense? */
8362306a36Sopenharmony_ci	if (!xfs_verify_dir_ino(mp, ino)) {
8462306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
8562306a36Sopenharmony_ci		return -ECANCELED;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	/* Does this name make sense? */
8962306a36Sopenharmony_ci	if (!xfs_dir2_namecheck(name->name, name->len)) {
9062306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
9162306a36Sopenharmony_ci		return -ECANCELED;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (!strncmp(".", name->name, name->len)) {
9562306a36Sopenharmony_ci		/* If this is "." then check that the inum matches the dir. */
9662306a36Sopenharmony_ci		if (ino != dp->i_ino)
9762306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
9862306a36Sopenharmony_ci	} else if (!strncmp("..", name->name, name->len)) {
9962306a36Sopenharmony_ci		/*
10062306a36Sopenharmony_ci		 * If this is ".." in the root inode, check that the inum
10162306a36Sopenharmony_ci		 * matches this dir.
10262306a36Sopenharmony_ci		 */
10362306a36Sopenharmony_ci		if (dp->i_ino == mp->m_sb.sb_rootino && ino != dp->i_ino)
10462306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/* Verify that we can look up this name by hash. */
10862306a36Sopenharmony_ci	error = xchk_dir_lookup(sc, dp, name, &lookup_ino);
10962306a36Sopenharmony_ci	/* ENOENT means the hash lookup failed and the dir is corrupt */
11062306a36Sopenharmony_ci	if (error == -ENOENT)
11162306a36Sopenharmony_ci		error = -EFSCORRUPTED;
11262306a36Sopenharmony_ci	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, offset, &error))
11362306a36Sopenharmony_ci		goto out;
11462306a36Sopenharmony_ci	if (lookup_ino != ino) {
11562306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
11662306a36Sopenharmony_ci		return -ECANCELED;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/*
12062306a36Sopenharmony_ci	 * Grab the inode pointed to by the dirent.  We release the inode
12162306a36Sopenharmony_ci	 * before we cancel the scrub transaction.
12262306a36Sopenharmony_ci	 *
12362306a36Sopenharmony_ci	 * If _iget returns -EINVAL or -ENOENT then the child inode number is
12462306a36Sopenharmony_ci	 * garbage and the directory is corrupt.  If the _iget returns
12562306a36Sopenharmony_ci	 * -EFSCORRUPTED or -EFSBADCRC then the child is corrupt which is a
12662306a36Sopenharmony_ci	 *  cross referencing error.  Any other error is an operational error.
12762306a36Sopenharmony_ci	 */
12862306a36Sopenharmony_ci	error = xchk_iget(sc, ino, &ip);
12962306a36Sopenharmony_ci	if (error == -EINVAL || error == -ENOENT) {
13062306a36Sopenharmony_ci		error = -EFSCORRUPTED;
13162306a36Sopenharmony_ci		xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
13262306a36Sopenharmony_ci		goto out;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, offset, &error))
13562306a36Sopenharmony_ci		goto out;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	xchk_dir_check_ftype(sc, offset, ip, name->type);
13862306a36Sopenharmony_ci	xchk_irele(sc, ip);
13962306a36Sopenharmony_ciout:
14062306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
14162306a36Sopenharmony_ci		return -ECANCELED;
14262306a36Sopenharmony_ci	return error;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci/* Scrub a directory btree record. */
14662306a36Sopenharmony_ciSTATIC int
14762306a36Sopenharmony_cixchk_dir_rec(
14862306a36Sopenharmony_ci	struct xchk_da_btree		*ds,
14962306a36Sopenharmony_ci	int				level)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	struct xfs_name			dname = { };
15262306a36Sopenharmony_ci	struct xfs_da_state_blk		*blk = &ds->state->path.blk[level];
15362306a36Sopenharmony_ci	struct xfs_mount		*mp = ds->state->mp;
15462306a36Sopenharmony_ci	struct xfs_inode		*dp = ds->dargs.dp;
15562306a36Sopenharmony_ci	struct xfs_da_geometry		*geo = mp->m_dir_geo;
15662306a36Sopenharmony_ci	struct xfs_dir2_data_entry	*dent;
15762306a36Sopenharmony_ci	struct xfs_buf			*bp;
15862306a36Sopenharmony_ci	struct xfs_dir2_leaf_entry	*ent;
15962306a36Sopenharmony_ci	unsigned int			end;
16062306a36Sopenharmony_ci	unsigned int			iter_off;
16162306a36Sopenharmony_ci	xfs_ino_t			ino;
16262306a36Sopenharmony_ci	xfs_dablk_t			rec_bno;
16362306a36Sopenharmony_ci	xfs_dir2_db_t			db;
16462306a36Sopenharmony_ci	xfs_dir2_data_aoff_t		off;
16562306a36Sopenharmony_ci	xfs_dir2_dataptr_t		ptr;
16662306a36Sopenharmony_ci	xfs_dahash_t			calc_hash;
16762306a36Sopenharmony_ci	xfs_dahash_t			hash;
16862306a36Sopenharmony_ci	struct xfs_dir3_icleaf_hdr	hdr;
16962306a36Sopenharmony_ci	unsigned int			tag;
17062306a36Sopenharmony_ci	int				error;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	ASSERT(blk->magic == XFS_DIR2_LEAF1_MAGIC ||
17362306a36Sopenharmony_ci	       blk->magic == XFS_DIR2_LEAFN_MAGIC);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	xfs_dir2_leaf_hdr_from_disk(mp, &hdr, blk->bp->b_addr);
17662306a36Sopenharmony_ci	ent = hdr.ents + blk->index;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	/* Check the hash of the entry. */
17962306a36Sopenharmony_ci	error = xchk_da_btree_hash(ds, level, &ent->hashval);
18062306a36Sopenharmony_ci	if (error)
18162306a36Sopenharmony_ci		goto out;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	/* Valid hash pointer? */
18462306a36Sopenharmony_ci	ptr = be32_to_cpu(ent->address);
18562306a36Sopenharmony_ci	if (ptr == 0)
18662306a36Sopenharmony_ci		return 0;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	/* Find the directory entry's location. */
18962306a36Sopenharmony_ci	db = xfs_dir2_dataptr_to_db(geo, ptr);
19062306a36Sopenharmony_ci	off = xfs_dir2_dataptr_to_off(geo, ptr);
19162306a36Sopenharmony_ci	rec_bno = xfs_dir2_db_to_da(geo, db);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (rec_bno >= geo->leafblk) {
19462306a36Sopenharmony_ci		xchk_da_set_corrupt(ds, level);
19562306a36Sopenharmony_ci		goto out;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci	error = xfs_dir3_data_read(ds->dargs.trans, dp, rec_bno,
19862306a36Sopenharmony_ci			XFS_DABUF_MAP_HOLE_OK, &bp);
19962306a36Sopenharmony_ci	if (!xchk_fblock_process_error(ds->sc, XFS_DATA_FORK, rec_bno,
20062306a36Sopenharmony_ci			&error))
20162306a36Sopenharmony_ci		goto out;
20262306a36Sopenharmony_ci	if (!bp) {
20362306a36Sopenharmony_ci		xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
20462306a36Sopenharmony_ci		goto out;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci	xchk_buffer_recheck(ds->sc, bp);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (ds->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
20962306a36Sopenharmony_ci		goto out_relse;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	dent = bp->b_addr + off;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	/* Make sure we got a real directory entry. */
21462306a36Sopenharmony_ci	iter_off = geo->data_entry_offset;
21562306a36Sopenharmony_ci	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
21662306a36Sopenharmony_ci	if (!end) {
21762306a36Sopenharmony_ci		xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
21862306a36Sopenharmony_ci		goto out_relse;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci	for (;;) {
22162306a36Sopenharmony_ci		struct xfs_dir2_data_entry	*dep = bp->b_addr + iter_off;
22262306a36Sopenharmony_ci		struct xfs_dir2_data_unused	*dup = bp->b_addr + iter_off;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci		if (iter_off >= end) {
22562306a36Sopenharmony_ci			xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
22662306a36Sopenharmony_ci			goto out_relse;
22762306a36Sopenharmony_ci		}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
23062306a36Sopenharmony_ci			iter_off += be16_to_cpu(dup->length);
23162306a36Sopenharmony_ci			continue;
23262306a36Sopenharmony_ci		}
23362306a36Sopenharmony_ci		if (dep == dent)
23462306a36Sopenharmony_ci			break;
23562306a36Sopenharmony_ci		iter_off += xfs_dir2_data_entsize(mp, dep->namelen);
23662306a36Sopenharmony_ci	}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	/* Retrieve the entry, sanity check it, and compare hashes. */
23962306a36Sopenharmony_ci	ino = be64_to_cpu(dent->inumber);
24062306a36Sopenharmony_ci	hash = be32_to_cpu(ent->hashval);
24162306a36Sopenharmony_ci	tag = be16_to_cpup(xfs_dir2_data_entry_tag_p(mp, dent));
24262306a36Sopenharmony_ci	if (!xfs_verify_dir_ino(mp, ino) || tag != off)
24362306a36Sopenharmony_ci		xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
24462306a36Sopenharmony_ci	if (dent->namelen == 0) {
24562306a36Sopenharmony_ci		xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
24662306a36Sopenharmony_ci		goto out_relse;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* Does the directory hash match? */
25062306a36Sopenharmony_ci	dname.name = dent->name;
25162306a36Sopenharmony_ci	dname.len = dent->namelen;
25262306a36Sopenharmony_ci	calc_hash = xfs_dir2_hashname(mp, &dname);
25362306a36Sopenharmony_ci	if (calc_hash != hash)
25462306a36Sopenharmony_ci		xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ciout_relse:
25762306a36Sopenharmony_ci	xfs_trans_brelse(ds->dargs.trans, bp);
25862306a36Sopenharmony_ciout:
25962306a36Sopenharmony_ci	return error;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci/*
26362306a36Sopenharmony_ci * Is this unused entry either in the bestfree or smaller than all of
26462306a36Sopenharmony_ci * them?  We've already checked that the bestfrees are sorted longest to
26562306a36Sopenharmony_ci * shortest, and that there aren't any bogus entries.
26662306a36Sopenharmony_ci */
26762306a36Sopenharmony_ciSTATIC void
26862306a36Sopenharmony_cixchk_directory_check_free_entry(
26962306a36Sopenharmony_ci	struct xfs_scrub		*sc,
27062306a36Sopenharmony_ci	xfs_dablk_t			lblk,
27162306a36Sopenharmony_ci	struct xfs_dir2_data_free	*bf,
27262306a36Sopenharmony_ci	struct xfs_dir2_data_unused	*dup)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	struct xfs_dir2_data_free	*dfp;
27562306a36Sopenharmony_ci	unsigned int			dup_length;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	dup_length = be16_to_cpu(dup->length);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* Unused entry is shorter than any of the bestfrees */
28062306a36Sopenharmony_ci	if (dup_length < be16_to_cpu(bf[XFS_DIR2_DATA_FD_COUNT - 1].length))
28162306a36Sopenharmony_ci		return;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	for (dfp = &bf[XFS_DIR2_DATA_FD_COUNT - 1]; dfp >= bf; dfp--)
28462306a36Sopenharmony_ci		if (dup_length == be16_to_cpu(dfp->length))
28562306a36Sopenharmony_ci			return;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	/* Unused entry should be in the bestfrees but wasn't found. */
28862306a36Sopenharmony_ci	xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
28962306a36Sopenharmony_ci}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci/* Check free space info in a directory data block. */
29262306a36Sopenharmony_ciSTATIC int
29362306a36Sopenharmony_cixchk_directory_data_bestfree(
29462306a36Sopenharmony_ci	struct xfs_scrub		*sc,
29562306a36Sopenharmony_ci	xfs_dablk_t			lblk,
29662306a36Sopenharmony_ci	bool				is_block)
29762306a36Sopenharmony_ci{
29862306a36Sopenharmony_ci	struct xfs_dir2_data_unused	*dup;
29962306a36Sopenharmony_ci	struct xfs_dir2_data_free	*dfp;
30062306a36Sopenharmony_ci	struct xfs_buf			*bp;
30162306a36Sopenharmony_ci	struct xfs_dir2_data_free	*bf;
30262306a36Sopenharmony_ci	struct xfs_mount		*mp = sc->mp;
30362306a36Sopenharmony_ci	u16				tag;
30462306a36Sopenharmony_ci	unsigned int			nr_bestfrees = 0;
30562306a36Sopenharmony_ci	unsigned int			nr_frees = 0;
30662306a36Sopenharmony_ci	unsigned int			smallest_bestfree;
30762306a36Sopenharmony_ci	int				newlen;
30862306a36Sopenharmony_ci	unsigned int			offset;
30962306a36Sopenharmony_ci	unsigned int			end;
31062306a36Sopenharmony_ci	int				error;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (is_block) {
31362306a36Sopenharmony_ci		/* dir block format */
31462306a36Sopenharmony_ci		if (lblk != XFS_B_TO_FSBT(mp, XFS_DIR2_DATA_OFFSET))
31562306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
31662306a36Sopenharmony_ci		error = xfs_dir3_block_read(sc->tp, sc->ip, &bp);
31762306a36Sopenharmony_ci	} else {
31862306a36Sopenharmony_ci		/* dir data format */
31962306a36Sopenharmony_ci		error = xfs_dir3_data_read(sc->tp, sc->ip, lblk, 0, &bp);
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error))
32262306a36Sopenharmony_ci		goto out;
32362306a36Sopenharmony_ci	xchk_buffer_recheck(sc, bp);
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	/* XXX: Check xfs_dir3_data_hdr.pad is zero once we start setting it. */
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
32862306a36Sopenharmony_ci		goto out_buf;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	/* Do the bestfrees correspond to actual free space? */
33162306a36Sopenharmony_ci	bf = xfs_dir2_data_bestfree_p(mp, bp->b_addr);
33262306a36Sopenharmony_ci	smallest_bestfree = UINT_MAX;
33362306a36Sopenharmony_ci	for (dfp = &bf[0]; dfp < &bf[XFS_DIR2_DATA_FD_COUNT]; dfp++) {
33462306a36Sopenharmony_ci		offset = be16_to_cpu(dfp->offset);
33562306a36Sopenharmony_ci		if (offset == 0)
33662306a36Sopenharmony_ci			continue;
33762306a36Sopenharmony_ci		if (offset >= mp->m_dir_geo->blksize) {
33862306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
33962306a36Sopenharmony_ci			goto out_buf;
34062306a36Sopenharmony_ci		}
34162306a36Sopenharmony_ci		dup = bp->b_addr + offset;
34262306a36Sopenharmony_ci		tag = be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup));
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci		/* bestfree doesn't match the entry it points at? */
34562306a36Sopenharmony_ci		if (dup->freetag != cpu_to_be16(XFS_DIR2_DATA_FREE_TAG) ||
34662306a36Sopenharmony_ci		    be16_to_cpu(dup->length) != be16_to_cpu(dfp->length) ||
34762306a36Sopenharmony_ci		    tag != offset) {
34862306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
34962306a36Sopenharmony_ci			goto out_buf;
35062306a36Sopenharmony_ci		}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci		/* bestfree records should be ordered largest to smallest */
35362306a36Sopenharmony_ci		if (smallest_bestfree < be16_to_cpu(dfp->length)) {
35462306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
35562306a36Sopenharmony_ci			goto out_buf;
35662306a36Sopenharmony_ci		}
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci		smallest_bestfree = be16_to_cpu(dfp->length);
35962306a36Sopenharmony_ci		nr_bestfrees++;
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	/* Make sure the bestfrees are actually the best free spaces. */
36362306a36Sopenharmony_ci	offset = mp->m_dir_geo->data_entry_offset;
36462306a36Sopenharmony_ci	end = xfs_dir3_data_end_offset(mp->m_dir_geo, bp->b_addr);
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	/* Iterate the entries, stopping when we hit or go past the end. */
36762306a36Sopenharmony_ci	while (offset < end) {
36862306a36Sopenharmony_ci		dup = bp->b_addr + offset;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci		/* Skip real entries */
37162306a36Sopenharmony_ci		if (dup->freetag != cpu_to_be16(XFS_DIR2_DATA_FREE_TAG)) {
37262306a36Sopenharmony_ci			struct xfs_dir2_data_entry *dep = bp->b_addr + offset;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci			newlen = xfs_dir2_data_entsize(mp, dep->namelen);
37562306a36Sopenharmony_ci			if (newlen <= 0) {
37662306a36Sopenharmony_ci				xchk_fblock_set_corrupt(sc, XFS_DATA_FORK,
37762306a36Sopenharmony_ci						lblk);
37862306a36Sopenharmony_ci				goto out_buf;
37962306a36Sopenharmony_ci			}
38062306a36Sopenharmony_ci			offset += newlen;
38162306a36Sopenharmony_ci			continue;
38262306a36Sopenharmony_ci		}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci		/* Spot check this free entry */
38562306a36Sopenharmony_ci		tag = be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup));
38662306a36Sopenharmony_ci		if (tag != offset) {
38762306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
38862306a36Sopenharmony_ci			goto out_buf;
38962306a36Sopenharmony_ci		}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci		/*
39262306a36Sopenharmony_ci		 * Either this entry is a bestfree or it's smaller than
39362306a36Sopenharmony_ci		 * any of the bestfrees.
39462306a36Sopenharmony_ci		 */
39562306a36Sopenharmony_ci		xchk_directory_check_free_entry(sc, lblk, bf, dup);
39662306a36Sopenharmony_ci		if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
39762306a36Sopenharmony_ci			goto out_buf;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci		/* Move on. */
40062306a36Sopenharmony_ci		newlen = be16_to_cpu(dup->length);
40162306a36Sopenharmony_ci		if (newlen <= 0) {
40262306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
40362306a36Sopenharmony_ci			goto out_buf;
40462306a36Sopenharmony_ci		}
40562306a36Sopenharmony_ci		offset += newlen;
40662306a36Sopenharmony_ci		if (offset <= end)
40762306a36Sopenharmony_ci			nr_frees++;
40862306a36Sopenharmony_ci	}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	/* We're required to fill all the space. */
41162306a36Sopenharmony_ci	if (offset != end)
41262306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	/* Did we see at least as many free slots as there are bestfrees? */
41562306a36Sopenharmony_ci	if (nr_frees < nr_bestfrees)
41662306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
41762306a36Sopenharmony_ciout_buf:
41862306a36Sopenharmony_ci	xfs_trans_brelse(sc->tp, bp);
41962306a36Sopenharmony_ciout:
42062306a36Sopenharmony_ci	return error;
42162306a36Sopenharmony_ci}
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci/*
42462306a36Sopenharmony_ci * Does the free space length in the free space index block ($len) match
42562306a36Sopenharmony_ci * the longest length in the directory data block's bestfree array?
42662306a36Sopenharmony_ci * Assume that we've already checked that the data block's bestfree
42762306a36Sopenharmony_ci * array is in order.
42862306a36Sopenharmony_ci */
42962306a36Sopenharmony_ciSTATIC void
43062306a36Sopenharmony_cixchk_directory_check_freesp(
43162306a36Sopenharmony_ci	struct xfs_scrub		*sc,
43262306a36Sopenharmony_ci	xfs_dablk_t			lblk,
43362306a36Sopenharmony_ci	struct xfs_buf			*dbp,
43462306a36Sopenharmony_ci	unsigned int			len)
43562306a36Sopenharmony_ci{
43662306a36Sopenharmony_ci	struct xfs_dir2_data_free	*dfp;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	dfp = xfs_dir2_data_bestfree_p(sc->mp, dbp->b_addr);
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (len != be16_to_cpu(dfp->length))
44162306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	if (len > 0 && be16_to_cpu(dfp->offset) == 0)
44462306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci/* Check free space info in a directory leaf1 block. */
44862306a36Sopenharmony_ciSTATIC int
44962306a36Sopenharmony_cixchk_directory_leaf1_bestfree(
45062306a36Sopenharmony_ci	struct xfs_scrub		*sc,
45162306a36Sopenharmony_ci	struct xfs_da_args		*args,
45262306a36Sopenharmony_ci	xfs_dir2_db_t			last_data_db,
45362306a36Sopenharmony_ci	xfs_dablk_t			lblk)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	struct xfs_dir3_icleaf_hdr	leafhdr;
45662306a36Sopenharmony_ci	struct xfs_dir2_leaf_tail	*ltp;
45762306a36Sopenharmony_ci	struct xfs_dir2_leaf		*leaf;
45862306a36Sopenharmony_ci	struct xfs_buf			*dbp;
45962306a36Sopenharmony_ci	struct xfs_buf			*bp;
46062306a36Sopenharmony_ci	struct xfs_da_geometry		*geo = sc->mp->m_dir_geo;
46162306a36Sopenharmony_ci	__be16				*bestp;
46262306a36Sopenharmony_ci	__u16				best;
46362306a36Sopenharmony_ci	__u32				hash;
46462306a36Sopenharmony_ci	__u32				lasthash = 0;
46562306a36Sopenharmony_ci	__u32				bestcount;
46662306a36Sopenharmony_ci	unsigned int			stale = 0;
46762306a36Sopenharmony_ci	int				i;
46862306a36Sopenharmony_ci	int				error;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	/* Read the free space block. */
47162306a36Sopenharmony_ci	error = xfs_dir3_leaf_read(sc->tp, sc->ip, lblk, &bp);
47262306a36Sopenharmony_ci	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error))
47362306a36Sopenharmony_ci		return error;
47462306a36Sopenharmony_ci	xchk_buffer_recheck(sc, bp);
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	leaf = bp->b_addr;
47762306a36Sopenharmony_ci	xfs_dir2_leaf_hdr_from_disk(sc->ip->i_mount, &leafhdr, leaf);
47862306a36Sopenharmony_ci	ltp = xfs_dir2_leaf_tail_p(geo, leaf);
47962306a36Sopenharmony_ci	bestcount = be32_to_cpu(ltp->bestcount);
48062306a36Sopenharmony_ci	bestp = xfs_dir2_leaf_bests_p(ltp);
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	if (xfs_has_crc(sc->mp)) {
48362306a36Sopenharmony_ci		struct xfs_dir3_leaf_hdr	*hdr3 = bp->b_addr;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci		if (hdr3->pad != cpu_to_be32(0))
48662306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
48762306a36Sopenharmony_ci	}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	/*
49062306a36Sopenharmony_ci	 * There must be enough bestfree slots to cover all the directory data
49162306a36Sopenharmony_ci	 * blocks that we scanned.  It is possible for there to be a hole
49262306a36Sopenharmony_ci	 * between the last data block and i_disk_size.  This seems like an
49362306a36Sopenharmony_ci	 * oversight to the scrub author, but as we have been writing out
49462306a36Sopenharmony_ci	 * directories like this (and xfs_repair doesn't mind them) for years,
49562306a36Sopenharmony_ci	 * that's what we have to check.
49662306a36Sopenharmony_ci	 */
49762306a36Sopenharmony_ci	if (bestcount != last_data_db + 1) {
49862306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
49962306a36Sopenharmony_ci		goto out;
50062306a36Sopenharmony_ci	}
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	/* Is the leaf count even remotely sane? */
50362306a36Sopenharmony_ci	if (leafhdr.count > geo->leaf_max_ents) {
50462306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
50562306a36Sopenharmony_ci		goto out;
50662306a36Sopenharmony_ci	}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	/* Leaves and bests don't overlap in leaf format. */
50962306a36Sopenharmony_ci	if ((char *)&leafhdr.ents[leafhdr.count] > (char *)bestp) {
51062306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
51162306a36Sopenharmony_ci		goto out;
51262306a36Sopenharmony_ci	}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	/* Check hash value order, count stale entries.  */
51562306a36Sopenharmony_ci	for (i = 0; i < leafhdr.count; i++) {
51662306a36Sopenharmony_ci		hash = be32_to_cpu(leafhdr.ents[i].hashval);
51762306a36Sopenharmony_ci		if (i > 0 && lasthash > hash)
51862306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
51962306a36Sopenharmony_ci		lasthash = hash;
52062306a36Sopenharmony_ci		if (leafhdr.ents[i].address ==
52162306a36Sopenharmony_ci		    cpu_to_be32(XFS_DIR2_NULL_DATAPTR))
52262306a36Sopenharmony_ci			stale++;
52362306a36Sopenharmony_ci	}
52462306a36Sopenharmony_ci	if (leafhdr.stale != stale)
52562306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
52662306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
52762306a36Sopenharmony_ci		goto out;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	/* Check all the bestfree entries. */
53062306a36Sopenharmony_ci	for (i = 0; i < bestcount; i++, bestp++) {
53162306a36Sopenharmony_ci		best = be16_to_cpu(*bestp);
53262306a36Sopenharmony_ci		error = xfs_dir3_data_read(sc->tp, sc->ip,
53362306a36Sopenharmony_ci				xfs_dir2_db_to_da(args->geo, i),
53462306a36Sopenharmony_ci				XFS_DABUF_MAP_HOLE_OK,
53562306a36Sopenharmony_ci				&dbp);
53662306a36Sopenharmony_ci		if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk,
53762306a36Sopenharmony_ci				&error))
53862306a36Sopenharmony_ci			break;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci		if (!dbp) {
54162306a36Sopenharmony_ci			if (best != NULLDATAOFF) {
54262306a36Sopenharmony_ci				xchk_fblock_set_corrupt(sc, XFS_DATA_FORK,
54362306a36Sopenharmony_ci						lblk);
54462306a36Sopenharmony_ci				break;
54562306a36Sopenharmony_ci			}
54662306a36Sopenharmony_ci			continue;
54762306a36Sopenharmony_ci		}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci		if (best == NULLDATAOFF)
55062306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
55162306a36Sopenharmony_ci		else
55262306a36Sopenharmony_ci			xchk_directory_check_freesp(sc, lblk, dbp, best);
55362306a36Sopenharmony_ci		xfs_trans_brelse(sc->tp, dbp);
55462306a36Sopenharmony_ci		if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
55562306a36Sopenharmony_ci			break;
55662306a36Sopenharmony_ci	}
55762306a36Sopenharmony_ciout:
55862306a36Sopenharmony_ci	xfs_trans_brelse(sc->tp, bp);
55962306a36Sopenharmony_ci	return error;
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci/* Check free space info in a directory freespace block. */
56362306a36Sopenharmony_ciSTATIC int
56462306a36Sopenharmony_cixchk_directory_free_bestfree(
56562306a36Sopenharmony_ci	struct xfs_scrub		*sc,
56662306a36Sopenharmony_ci	struct xfs_da_args		*args,
56762306a36Sopenharmony_ci	xfs_dablk_t			lblk)
56862306a36Sopenharmony_ci{
56962306a36Sopenharmony_ci	struct xfs_dir3_icfree_hdr	freehdr;
57062306a36Sopenharmony_ci	struct xfs_buf			*dbp;
57162306a36Sopenharmony_ci	struct xfs_buf			*bp;
57262306a36Sopenharmony_ci	__u16				best;
57362306a36Sopenharmony_ci	unsigned int			stale = 0;
57462306a36Sopenharmony_ci	int				i;
57562306a36Sopenharmony_ci	int				error;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	/* Read the free space block */
57862306a36Sopenharmony_ci	error = xfs_dir2_free_read(sc->tp, sc->ip, lblk, &bp);
57962306a36Sopenharmony_ci	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error))
58062306a36Sopenharmony_ci		return error;
58162306a36Sopenharmony_ci	xchk_buffer_recheck(sc, bp);
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	if (xfs_has_crc(sc->mp)) {
58462306a36Sopenharmony_ci		struct xfs_dir3_free_hdr	*hdr3 = bp->b_addr;
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci		if (hdr3->pad != cpu_to_be32(0))
58762306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	/* Check all the entries. */
59162306a36Sopenharmony_ci	xfs_dir2_free_hdr_from_disk(sc->ip->i_mount, &freehdr, bp->b_addr);
59262306a36Sopenharmony_ci	for (i = 0; i < freehdr.nvalid; i++) {
59362306a36Sopenharmony_ci		best = be16_to_cpu(freehdr.bests[i]);
59462306a36Sopenharmony_ci		if (best == NULLDATAOFF) {
59562306a36Sopenharmony_ci			stale++;
59662306a36Sopenharmony_ci			continue;
59762306a36Sopenharmony_ci		}
59862306a36Sopenharmony_ci		error = xfs_dir3_data_read(sc->tp, sc->ip,
59962306a36Sopenharmony_ci				(freehdr.firstdb + i) * args->geo->fsbcount,
60062306a36Sopenharmony_ci				0, &dbp);
60162306a36Sopenharmony_ci		if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk,
60262306a36Sopenharmony_ci				&error))
60362306a36Sopenharmony_ci			goto out;
60462306a36Sopenharmony_ci		xchk_directory_check_freesp(sc, lblk, dbp, best);
60562306a36Sopenharmony_ci		xfs_trans_brelse(sc->tp, dbp);
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	if (freehdr.nused + stale != freehdr.nvalid)
60962306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
61062306a36Sopenharmony_ciout:
61162306a36Sopenharmony_ci	xfs_trans_brelse(sc->tp, bp);
61262306a36Sopenharmony_ci	return error;
61362306a36Sopenharmony_ci}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci/* Check free space information in directories. */
61662306a36Sopenharmony_ciSTATIC int
61762306a36Sopenharmony_cixchk_directory_blocks(
61862306a36Sopenharmony_ci	struct xfs_scrub	*sc)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	struct xfs_bmbt_irec	got;
62162306a36Sopenharmony_ci	struct xfs_da_args	args = {
62262306a36Sopenharmony_ci		.dp		= sc ->ip,
62362306a36Sopenharmony_ci		.whichfork	= XFS_DATA_FORK,
62462306a36Sopenharmony_ci		.geo		= sc->mp->m_dir_geo,
62562306a36Sopenharmony_ci		.trans		= sc->tp,
62662306a36Sopenharmony_ci	};
62762306a36Sopenharmony_ci	struct xfs_ifork	*ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK);
62862306a36Sopenharmony_ci	struct xfs_mount	*mp = sc->mp;
62962306a36Sopenharmony_ci	xfs_fileoff_t		leaf_lblk;
63062306a36Sopenharmony_ci	xfs_fileoff_t		free_lblk;
63162306a36Sopenharmony_ci	xfs_fileoff_t		lblk;
63262306a36Sopenharmony_ci	struct xfs_iext_cursor	icur;
63362306a36Sopenharmony_ci	xfs_dablk_t		dabno;
63462306a36Sopenharmony_ci	xfs_dir2_db_t		last_data_db = 0;
63562306a36Sopenharmony_ci	bool			found;
63662306a36Sopenharmony_ci	bool			is_block = false;
63762306a36Sopenharmony_ci	int			error;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	/* Ignore local format directories. */
64062306a36Sopenharmony_ci	if (ifp->if_format != XFS_DINODE_FMT_EXTENTS &&
64162306a36Sopenharmony_ci	    ifp->if_format != XFS_DINODE_FMT_BTREE)
64262306a36Sopenharmony_ci		return 0;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	lblk = XFS_B_TO_FSB(mp, XFS_DIR2_DATA_OFFSET);
64562306a36Sopenharmony_ci	leaf_lblk = XFS_B_TO_FSB(mp, XFS_DIR2_LEAF_OFFSET);
64662306a36Sopenharmony_ci	free_lblk = XFS_B_TO_FSB(mp, XFS_DIR2_FREE_OFFSET);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	/* Is this a block dir? */
64962306a36Sopenharmony_ci	error = xfs_dir2_isblock(&args, &is_block);
65062306a36Sopenharmony_ci	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error))
65162306a36Sopenharmony_ci		goto out;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	/* Iterate all the data extents in the directory... */
65462306a36Sopenharmony_ci	found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got);
65562306a36Sopenharmony_ci	while (found && !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) {
65662306a36Sopenharmony_ci		/* No more data blocks... */
65762306a36Sopenharmony_ci		if (got.br_startoff >= leaf_lblk)
65862306a36Sopenharmony_ci			break;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci		/*
66162306a36Sopenharmony_ci		 * Check each data block's bestfree data.
66262306a36Sopenharmony_ci		 *
66362306a36Sopenharmony_ci		 * Iterate all the fsbcount-aligned block offsets in
66462306a36Sopenharmony_ci		 * this directory.  The directory block reading code is
66562306a36Sopenharmony_ci		 * smart enough to do its own bmap lookups to handle
66662306a36Sopenharmony_ci		 * discontiguous directory blocks.  When we're done
66762306a36Sopenharmony_ci		 * with the extent record, re-query the bmap at the
66862306a36Sopenharmony_ci		 * next fsbcount-aligned offset to avoid redundant
66962306a36Sopenharmony_ci		 * block checks.
67062306a36Sopenharmony_ci		 */
67162306a36Sopenharmony_ci		for (lblk = roundup((xfs_dablk_t)got.br_startoff,
67262306a36Sopenharmony_ci				args.geo->fsbcount);
67362306a36Sopenharmony_ci		     lblk < got.br_startoff + got.br_blockcount;
67462306a36Sopenharmony_ci		     lblk += args.geo->fsbcount) {
67562306a36Sopenharmony_ci			last_data_db = xfs_dir2_da_to_db(args.geo, lblk);
67662306a36Sopenharmony_ci			error = xchk_directory_data_bestfree(sc, lblk,
67762306a36Sopenharmony_ci					is_block);
67862306a36Sopenharmony_ci			if (error)
67962306a36Sopenharmony_ci				goto out;
68062306a36Sopenharmony_ci		}
68162306a36Sopenharmony_ci		dabno = got.br_startoff + got.br_blockcount;
68262306a36Sopenharmony_ci		lblk = roundup(dabno, args.geo->fsbcount);
68362306a36Sopenharmony_ci		found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got);
68462306a36Sopenharmony_ci	}
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
68762306a36Sopenharmony_ci		goto out;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	/* Look for a leaf1 block, which has free info. */
69062306a36Sopenharmony_ci	if (xfs_iext_lookup_extent(sc->ip, ifp, leaf_lblk, &icur, &got) &&
69162306a36Sopenharmony_ci	    got.br_startoff == leaf_lblk &&
69262306a36Sopenharmony_ci	    got.br_blockcount == args.geo->fsbcount &&
69362306a36Sopenharmony_ci	    !xfs_iext_next_extent(ifp, &icur, &got)) {
69462306a36Sopenharmony_ci		if (is_block) {
69562306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
69662306a36Sopenharmony_ci			goto out;
69762306a36Sopenharmony_ci		}
69862306a36Sopenharmony_ci		error = xchk_directory_leaf1_bestfree(sc, &args, last_data_db,
69962306a36Sopenharmony_ci				leaf_lblk);
70062306a36Sopenharmony_ci		if (error)
70162306a36Sopenharmony_ci			goto out;
70262306a36Sopenharmony_ci	}
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
70562306a36Sopenharmony_ci		goto out;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	/* Scan for free blocks */
70862306a36Sopenharmony_ci	lblk = free_lblk;
70962306a36Sopenharmony_ci	found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got);
71062306a36Sopenharmony_ci	while (found && !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) {
71162306a36Sopenharmony_ci		/*
71262306a36Sopenharmony_ci		 * Dirs can't have blocks mapped above 2^32.
71362306a36Sopenharmony_ci		 * Single-block dirs shouldn't even be here.
71462306a36Sopenharmony_ci		 */
71562306a36Sopenharmony_ci		lblk = got.br_startoff;
71662306a36Sopenharmony_ci		if (lblk & ~0xFFFFFFFFULL) {
71762306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
71862306a36Sopenharmony_ci			goto out;
71962306a36Sopenharmony_ci		}
72062306a36Sopenharmony_ci		if (is_block) {
72162306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk);
72262306a36Sopenharmony_ci			goto out;
72362306a36Sopenharmony_ci		}
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci		/*
72662306a36Sopenharmony_ci		 * Check each dir free block's bestfree data.
72762306a36Sopenharmony_ci		 *
72862306a36Sopenharmony_ci		 * Iterate all the fsbcount-aligned block offsets in
72962306a36Sopenharmony_ci		 * this directory.  The directory block reading code is
73062306a36Sopenharmony_ci		 * smart enough to do its own bmap lookups to handle
73162306a36Sopenharmony_ci		 * discontiguous directory blocks.  When we're done
73262306a36Sopenharmony_ci		 * with the extent record, re-query the bmap at the
73362306a36Sopenharmony_ci		 * next fsbcount-aligned offset to avoid redundant
73462306a36Sopenharmony_ci		 * block checks.
73562306a36Sopenharmony_ci		 */
73662306a36Sopenharmony_ci		for (lblk = roundup((xfs_dablk_t)got.br_startoff,
73762306a36Sopenharmony_ci				args.geo->fsbcount);
73862306a36Sopenharmony_ci		     lblk < got.br_startoff + got.br_blockcount;
73962306a36Sopenharmony_ci		     lblk += args.geo->fsbcount) {
74062306a36Sopenharmony_ci			error = xchk_directory_free_bestfree(sc, &args,
74162306a36Sopenharmony_ci					lblk);
74262306a36Sopenharmony_ci			if (error)
74362306a36Sopenharmony_ci				goto out;
74462306a36Sopenharmony_ci		}
74562306a36Sopenharmony_ci		dabno = got.br_startoff + got.br_blockcount;
74662306a36Sopenharmony_ci		lblk = roundup(dabno, args.geo->fsbcount);
74762306a36Sopenharmony_ci		found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got);
74862306a36Sopenharmony_ci	}
74962306a36Sopenharmony_ciout:
75062306a36Sopenharmony_ci	return error;
75162306a36Sopenharmony_ci}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci/* Scrub a whole directory. */
75462306a36Sopenharmony_ciint
75562306a36Sopenharmony_cixchk_directory(
75662306a36Sopenharmony_ci	struct xfs_scrub	*sc)
75762306a36Sopenharmony_ci{
75862306a36Sopenharmony_ci	int			error;
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
76162306a36Sopenharmony_ci		return -ENOENT;
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	/* Plausible size? */
76462306a36Sopenharmony_ci	if (sc->ip->i_disk_size < xfs_dir2_sf_hdr_size(0)) {
76562306a36Sopenharmony_ci		xchk_ino_set_corrupt(sc, sc->ip->i_ino);
76662306a36Sopenharmony_ci		return 0;
76762306a36Sopenharmony_ci	}
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	/* Check directory tree structure */
77062306a36Sopenharmony_ci	error = xchk_da_btree(sc, XFS_DATA_FORK, xchk_dir_rec, NULL);
77162306a36Sopenharmony_ci	if (error)
77262306a36Sopenharmony_ci		return error;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
77562306a36Sopenharmony_ci		return 0;
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	/* Check the freespace. */
77862306a36Sopenharmony_ci	error = xchk_directory_blocks(sc);
77962306a36Sopenharmony_ci	if (error)
78062306a36Sopenharmony_ci		return error;
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_ci	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
78362306a36Sopenharmony_ci		return 0;
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_ci	/* Look up every name in this directory by hash. */
78662306a36Sopenharmony_ci	error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, NULL);
78762306a36Sopenharmony_ci	if (error == -ECANCELED)
78862306a36Sopenharmony_ci		error = 0;
78962306a36Sopenharmony_ci	return error;
79062306a36Sopenharmony_ci}
791