Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Darrick J. Wong 6423 98.18% 67 74.44%
Christoph Hellwig 47 0.72% 8 8.89%
David Chinner 31 0.47% 10 11.11%
Eric Sandeen 15 0.23% 1 1.11%
Russell Cattelan 11 0.17% 1 1.11%
Michal Marek 8 0.12% 1 1.11%
Nathan Scott 7 0.11% 2 2.22%
Total 6542 90


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2020-2024 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_defer.h"
#include "xfs_bit.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_inode.h"
#include "xfs_icache.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_bmap.h"
#include "xfs_quota.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
#include "xfs_bmap_util.h"
#include "xfs_exchmaps.h"
#include "xfs_exchrange.h"
#include "xfs_ag.h"
#include "xfs_parent.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/repair.h"
#include "scrub/tempfile.h"
#include "scrub/tempexch.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/xfblob.h"
#include "scrub/iscan.h"
#include "scrub/readdir.h"
#include "scrub/reap.h"
#include "scrub/findparent.h"
#include "scrub/orphanage.h"
#include "scrub/listxattr.h"

/*
 * Directory Repair
 * ================
 *
 * We repair directories by reading the directory data blocks looking for
 * directory entries that look salvageable (name passes verifiers, entry points
 * to a valid allocated inode, etc).  Each entry worth salvaging is stashed in
 * memory, and the stashed entries are periodically replayed into a temporary
 * directory to constrain memory use.  Batching the construction of the
 * temporary directory in this fashion reduces lock cycling of the directory
 * being repaired and the temporary directory, and will later become important
 * for parent pointer scanning.
 *
 * If parent pointers are enabled on this filesystem, we instead reconstruct
 * the directory by visiting each parent pointer of each file in the filesystem
 * and translating the relevant parent pointer records into dirents.  In this
 * case, it is advantageous to stash all directory entries created from parent
 * pointers for a single child file before replaying them into the temporary
 * directory.  To save memory, the live filesystem scan reuses the findparent
 * fields.  Directory repair chooses either parent pointer scanning or
 * directory entry salvaging, but not both.
 *
 * Directory entries added to the temporary directory do not elevate the link
 * counts of the inodes found.  When salvaging completes, the remaining stashed
 * entries are replayed to the temporary directory.  An atomic mapping exchange
 * is used to commit the new directory blocks to the directory being repaired.
 * This will disrupt readdir cursors.
 *
 * Locking Issues
 * --------------
 *
 * If /a, /a/b, and /c are all directories, the VFS does not take i_rwsem on
 * /a/b for a "mv /a/b /c/" operation.  This means that only b's ILOCK protects
 * b's dotdot update.  This is in contrast to every other dotdot update (link,
 * remove, mkdir).  If the repair code drops the ILOCK, it must either
 * revalidate the dotdot entry or use dirent hooks to capture updates from
 * other threads.
 */

/* Create a dirent in the tempdir. */
#define XREP_DIRENT_ADD		(1)

/* Remove a dirent from the tempdir. */
#define XREP_DIRENT_REMOVE	(2)

/* Directory entry to be restored in the new directory. */
struct xrep_dirent {
	/* Cookie for retrieval of the dirent name. */
	xfblob_cookie		name_cookie;

	/* Target inode number. */
	xfs_ino_t		ino;

	/* Length of the dirent name. */
	uint8_t			namelen;

	/* File type of the dirent. */
	uint8_t			ftype;

	/* XREP_DIRENT_{ADD,REMOVE} */
	uint8_t			action;
};

/*
 * Stash up to 8 pages of recovered dirent data in dir_entries and dir_names
 * before we write them to the temp dir.
 */
#define XREP_DIR_MAX_STASH_BYTES	(PAGE_SIZE * 8)

struct xrep_dir {
	struct xfs_scrub	*sc;

	/* Fixed-size array of xrep_dirent structures. */
	struct xfarray		*dir_entries;

	/* Blobs containing directory entry names. */
	struct xfblob		*dir_names;

	/* Information for exchanging data forks at the end. */
	struct xrep_tempexch	tx;

	/* Preallocated args struct for performing dir operations */
	struct xfs_da_args	args;

	/*
	 * Information used to scan the filesystem to find the inumber of the
	 * dotdot entry for this directory.  For directory salvaging when
	 * parent pointers are not enabled, we use the findparent_* functions
	 * on this object and access only the parent_ino field directly.
	 *
	 * When parent pointers are enabled, however, the pptr scanner uses the
	 * iscan, hooks, lock, and parent_ino fields of this object directly.
	 * @pscan.lock coordinates access to dir_entries, dir_names,
	 * parent_ino, subdirs, dirents, and args.  This reduces the memory
	 * requirements of this structure.
	 */
	struct xrep_parent_scan_info pscan;

	/*
	 * Context information for attaching this directory to the lost+found
	 * if this directory does not have a parent.
	 */
	struct xrep_adoption	adoption;

	/* How many subdirectories did we find? */
	uint64_t		subdirs;

	/* How many dirents did we find? */
	unsigned int		dirents;

	/* Should we move this directory to the orphanage? */
	bool			needs_adoption;

	/* Directory entry name, plus the trailing null. */
	struct xfs_name		xname;
	unsigned char		namebuf[MAXNAMELEN];
};

/* Tear down all the incore stuff we created. */
static void
xrep_dir_teardown(
	struct xfs_scrub	*sc)
{
	struct xrep_dir		*rd = sc->buf;

	xrep_findparent_scan_teardown(&rd->pscan);
	xfblob_destroy(rd->dir_names);
	xfarray_destroy(rd->dir_entries);
}

/* Set up for a directory repair. */
int
xrep_setup_directory(
	struct xfs_scrub	*sc)
{
	struct xrep_dir		*rd;
	int			error;

	xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);

	error = xrep_orphanage_try_create(sc);
	if (error)
		return error;

	error = xrep_tempfile_create(sc, S_IFDIR);
	if (error)
		return error;

	rd = kvzalloc(sizeof(struct xrep_dir), XCHK_GFP_FLAGS);
	if (!rd)
		return -ENOMEM;
	rd->sc = sc;
	rd->xname.name = rd->namebuf;
	sc->buf = rd;

	return 0;
}

/*
 * Look up the dotdot entry and confirm that it's really the parent.
 * Returns NULLFSINO if we don't know what to do.
 */
static inline xfs_ino_t
xrep_dir_lookup_parent(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	xfs_ino_t		ino;
	int			error;

	error = xfs_dir_lookup(sc->tp, sc->ip, &xfs_name_dotdot, &ino, NULL);
	if (error)
		return NULLFSINO;
	if (!xfs_verify_dir_ino(sc->mp, ino))
		return NULLFSINO;

	error = xrep_findparent_confirm(sc, &ino);
	if (error)
		return NULLFSINO;

	return ino;
}

/*
 * Look up '..' in the dentry cache and confirm that it's really the parent.
 * Returns NULLFSINO if the dcache misses or if the hit is implausible.
 */
static inline xfs_ino_t
xrep_dir_dcache_parent(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	xfs_ino_t		parent_ino;
	int			error;

	parent_ino = xrep_findparent_from_dcache(sc);
	if (parent_ino == NULLFSINO)
		return parent_ino;

	error = xrep_findparent_confirm(sc, &parent_ino);
	if (error)
		return NULLFSINO;

	return parent_ino;
}

/* Try to find the parent of the directory being repaired. */
STATIC int
xrep_dir_find_parent(
	struct xrep_dir		*rd)
{
	xfs_ino_t		ino;

	ino = xrep_findparent_self_reference(rd->sc);
	if (ino != NULLFSINO) {
		xrep_findparent_scan_finish_early(&rd->pscan, ino);
		return 0;
	}

	ino = xrep_dir_dcache_parent(rd);
	if (ino != NULLFSINO) {
		xrep_findparent_scan_finish_early(&rd->pscan, ino);
		return 0;
	}

	ino = xrep_dir_lookup_parent(rd);
	if (ino != NULLFSINO) {
		xrep_findparent_scan_finish_early(&rd->pscan, ino);
		return 0;
	}

	/*
	 * A full filesystem scan is the last resort.  On a busy filesystem,
	 * the scan can fail with -EBUSY if we cannot grab IOLOCKs.  That means
	 * that we don't know what who the parent is, so we should return to
	 * userspace.
	 */
	return xrep_findparent_scan(&rd->pscan);
}

/*
 * Decide if we want to salvage this entry.  We don't bother with oversized
 * names or the dot entry.
 */
STATIC int
xrep_dir_want_salvage(
	struct xrep_dir		*rd,
	const char		*name,
	int			namelen,
	xfs_ino_t		ino)
{
	struct xfs_mount	*mp = rd->sc->mp;

	/* No pointers to ourselves or to garbage. */
	if (ino == rd->sc->ip->i_ino)
		return false;
	if (!xfs_verify_dir_ino(mp, ino))
		return false;

	/* No weird looking names or dot entries. */
	if (namelen >= MAXNAMELEN || namelen <= 0)
		return false;
	if (namelen == 1 && name[0] == '.')
		return false;
	if (!xfs_dir2_namecheck(name, namelen))
		return false;

	return true;
}

/*
 * Remember that we want to create a dirent in the tempdir.  These stashed
 * actions will be replayed later.
 */
STATIC int
xrep_dir_stash_createname(
	struct xrep_dir		*rd,
	const struct xfs_name	*name,
	xfs_ino_t		ino)
{
	struct xrep_dirent	dirent = {
		.action		= XREP_DIRENT_ADD,
		.ino		= ino,
		.namelen	= name->len,
		.ftype		= name->type,
	};
	int			error;

	trace_xrep_dir_stash_createname(rd->sc->tempip, name, ino);

	error = xfblob_storename(rd->dir_names, &dirent.name_cookie, name);
	if (error)
		return error;

	return xfarray_append(rd->dir_entries, &dirent);
}

/*
 * Remember that we want to remove a dirent from the tempdir.  These stashed
 * actions will be replayed later.
 */
STATIC int
xrep_dir_stash_removename(
	struct xrep_dir		*rd,
	const struct xfs_name	*name,
	xfs_ino_t		ino)
{
	struct xrep_dirent	dirent = {
		.action		= XREP_DIRENT_REMOVE,
		.ino		= ino,
		.namelen	= name->len,
		.ftype		= name->type,
	};
	int			error;

	trace_xrep_dir_stash_removename(rd->sc->tempip, name, ino);

	error = xfblob_storename(rd->dir_names, &dirent.name_cookie, name);
	if (error)
		return error;

	return xfarray_append(rd->dir_entries, &dirent);
}

/* Allocate an in-core record to hold entries while we rebuild the dir data. */
STATIC int
xrep_dir_salvage_entry(
	struct xrep_dir		*rd,
	unsigned char		*name,
	unsigned int		namelen,
	xfs_ino_t		ino)
{
	struct xfs_name		xname = {
		.name		= name,
	};
	struct xfs_scrub	*sc = rd->sc;
	struct xfs_inode	*ip;
	unsigned int		i = 0;
	int			error = 0;

	if (xchk_should_terminate(sc, &error))
		return error;

	/*
	 * Truncate the name to the first character that would trip namecheck.
	 * If we no longer have a name after that, ignore this entry.
	 */
	while (i < namelen && name[i] != 0 && name[i] != '/')
		i++;
	if (i == 0)
		return 0;
	xname.len = i;

	/* Ignore '..' entries; we already picked the new parent. */
	if (xname.len == 2 && name[0] == '.' && name[1] == '.') {
		trace_xrep_dir_salvaged_parent(sc->ip, ino);
		return 0;
	}

	trace_xrep_dir_salvage_entry(sc->ip, &xname, ino);

	/*
	 * Compute the ftype or dump the entry if we can't.  We don't lock the
	 * inode because inodes can't change type while we have a reference.
	 */
	error = xchk_iget(sc, ino, &ip);
	if (error)
		return 0;

	xname.type = xfs_mode_to_ftype(VFS_I(ip)->i_mode);
	xchk_irele(sc, ip);

	return xrep_dir_stash_createname(rd, &xname, ino);
}

/* Record a shortform directory entry for later reinsertion. */
STATIC int
xrep_dir_salvage_sf_entry(
	struct xrep_dir			*rd,
	struct xfs_dir2_sf_hdr		*sfp,
	struct xfs_dir2_sf_entry	*sfep)
{
	xfs_ino_t			ino;

	ino = xfs_dir2_sf_get_ino(rd->sc->mp, sfp, sfep);
	if (!xrep_dir_want_salvage(rd, sfep->name, sfep->namelen, ino))
		return 0;

	return xrep_dir_salvage_entry(rd, sfep->name, sfep->namelen, ino);
}

/* Record a regular directory entry for later reinsertion. */
STATIC int
xrep_dir_salvage_data_entry(
	struct xrep_dir			*rd,
	struct xfs_dir2_data_entry	*dep)
{
	xfs_ino_t			ino;

	ino = be64_to_cpu(dep->inumber);
	if (!xrep_dir_want_salvage(rd, dep->name, dep->namelen, ino))
		return 0;

	return xrep_dir_salvage_entry(rd, dep->name, dep->namelen, ino);
}

/* Try to recover block/data format directory entries. */
STATIC int
xrep_dir_recover_data(
	struct xrep_dir		*rd,
	struct xfs_buf		*bp)
{
	struct xfs_da_geometry	*geo = rd->sc->mp->m_dir_geo;
	unsigned int		offset;
	unsigned int		end;
	int			error = 0;

	/*
	 * Loop over the data portion of the block.
	 * Each object is a real entry (dep) or an unused one (dup).
	 */
	offset = geo->data_entry_offset;
	end = min_t(unsigned int, BBTOB(bp->b_length),
			xfs_dir3_data_end_offset(geo, bp->b_addr));

	while (offset < end) {
		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;

		if (xchk_should_terminate(rd->sc, &error))
			return error;

		/* Skip unused entries. */
		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
			offset += be16_to_cpu(dup->length);
			continue;
		}

		/* Don't walk off the end of the block. */
		offset += xfs_dir2_data_entsize(rd->sc->mp, dep->namelen);
		if (offset > end)
			break;

		/* Ok, let's save this entry. */
		error = xrep_dir_salvage_data_entry(rd, dep);
		if (error)
			return error;

	}

	return 0;
}

/* Try to recover shortform directory entries. */
STATIC int
xrep_dir_recover_sf(
	struct xrep_dir			*rd)
{
	struct xfs_dir2_sf_hdr		*hdr;
	struct xfs_dir2_sf_entry	*sfep;
	struct xfs_dir2_sf_entry	*next;
	struct xfs_ifork		*ifp;
	xfs_ino_t			ino;
	unsigned char			*end;
	int				error = 0;

	ifp = xfs_ifork_ptr(rd->sc->ip, XFS_DATA_FORK);
	hdr = ifp->if_data;
	end = (unsigned char *)ifp->if_data + ifp->if_bytes;

	ino = xfs_dir2_sf_get_parent_ino(hdr);
	trace_xrep_dir_salvaged_parent(rd->sc->ip, ino);

	sfep = xfs_dir2_sf_firstentry(hdr);
	while ((unsigned char *)sfep < end) {
		if (xchk_should_terminate(rd->sc, &error))
			return error;

		next = xfs_dir2_sf_nextentry(rd->sc->mp, hdr, sfep);
		if ((unsigned char *)next > end)
			break;

		/* Ok, let's save this entry. */
		error = xrep_dir_salvage_sf_entry(rd, hdr, sfep);
		if (error)
			return error;

		sfep = next;
	}

	return 0;
}

/*
 * Try to figure out the format of this directory from the data fork mappings
 * and the directory size.  If we can be reasonably sure of format, we can be
 * more aggressive in salvaging directory entries.  On return, @magic_guess
 * will be set to DIR3_BLOCK_MAGIC if we think this is a "block format"
 * directory; DIR3_DATA_MAGIC if we think this is a "data format" directory,
 * and 0 if we can't tell.
 */
STATIC void
xrep_dir_guess_format(
	struct xrep_dir		*rd,
	__be32			*magic_guess)
{
	struct xfs_inode	*dp = rd->sc->ip;
	struct xfs_mount	*mp = rd->sc->mp;
	struct xfs_da_geometry	*geo = mp->m_dir_geo;
	xfs_fileoff_t		last;
	int			error;

	ASSERT(xfs_has_crc(mp));

	*magic_guess = 0;

	/*
	 * If there's a single directory block and the directory size is
	 * exactly one block, this has to be a single block format directory.
	 */
	error = xfs_bmap_last_offset(dp, &last, XFS_DATA_FORK);
	if (!error && XFS_FSB_TO_B(mp, last) == geo->blksize &&
	    dp->i_disk_size == geo->blksize) {
		*magic_guess = cpu_to_be32(XFS_DIR3_BLOCK_MAGIC);
		return;
	}

	/*
	 * If the last extent before the leaf offset matches the directory
	 * size and the directory size is larger than 1 block, this is a
	 * data format directory.
	 */
	last = geo->leafblk;
	error = xfs_bmap_last_before(rd->sc->tp, dp, &last, XFS_DATA_FORK);
	if (!error &&
	    XFS_FSB_TO_B(mp, last) > geo->blksize &&
	    XFS_FSB_TO_B(mp, last) == dp->i_disk_size) {
		*magic_guess = cpu_to_be32(XFS_DIR3_DATA_MAGIC);
		return;
	}
}

/* Recover directory entries from a specific directory block. */
STATIC int
xrep_dir_recover_dirblock(
	struct xrep_dir		*rd,
	__be32			magic_guess,
	xfs_dablk_t		dabno)
{
	struct xfs_dir2_data_hdr *hdr;
	struct xfs_buf		*bp;
	__be32			oldmagic;
	int			error;

	/*
	 * Try to read buffer.  We invalidate them in the next step so we don't
	 * bother to set a buffer type or ops.
	 */
	error = xfs_da_read_buf(rd->sc->tp, rd->sc->ip, dabno,
			XFS_DABUF_MAP_HOLE_OK, &bp, XFS_DATA_FORK, NULL);
	if (error || !bp)
		return error;

	hdr = bp->b_addr;
	oldmagic = hdr->magic;

	trace_xrep_dir_recover_dirblock(rd->sc->ip, dabno,
			be32_to_cpu(hdr->magic), be32_to_cpu(magic_guess));

	/*
	 * If we're sure of the block's format, proceed with the salvage
	 * operation using the specified magic number.
	 */
	if (magic_guess) {
		hdr->magic = magic_guess;
		goto recover;
	}

	/*
	 * If we couldn't guess what type of directory this is, then we will
	 * only salvage entries from directory blocks that match the magic
	 * number and pass verifiers.
	 */
	switch (hdr->magic) {
	case cpu_to_be32(XFS_DIR2_BLOCK_MAGIC):
	case cpu_to_be32(XFS_DIR3_BLOCK_MAGIC):
		if (!xrep_buf_verify_struct(bp, &xfs_dir3_block_buf_ops))
			goto out;
		if (xfs_dir3_block_header_check(bp, rd->sc->ip->i_ino) != NULL)
			goto out;
		break;
	case cpu_to_be32(XFS_DIR2_DATA_MAGIC):
	case cpu_to_be32(XFS_DIR3_DATA_MAGIC):
		if (!xrep_buf_verify_struct(bp, &xfs_dir3_data_buf_ops))
			goto out;
		if (xfs_dir3_data_header_check(bp, rd->sc->ip->i_ino) != NULL)
			goto out;
		break;
	default:
		goto out;
	}

recover:
	error = xrep_dir_recover_data(rd, bp);

out:
	hdr->magic = oldmagic;
	xfs_trans_brelse(rd->sc->tp, bp);
	return error;
}

static inline void
xrep_dir_init_args(
	struct xrep_dir		*rd,
	struct xfs_inode	*dp,
	const struct xfs_name	*name)
{
	memset(&rd->args, 0, sizeof(struct xfs_da_args));
	rd->args.geo = rd->sc->mp->m_dir_geo;
	rd->args.whichfork = XFS_DATA_FORK;
	rd->args.owner = rd->sc->ip->i_ino;
	rd->args.trans = rd->sc->tp;
	rd->args.dp = dp;
	if (!name)
		return;
	rd->args.name = name->name;
	rd->args.namelen = name->len;
	rd->args.filetype = name->type;
	rd->args.hashval = xfs_dir2_hashname(rd->sc->mp, name);
}

/* Replay a stashed createname into the temporary directory. */
STATIC int
xrep_dir_replay_createname(
	struct xrep_dir		*rd,
	const struct xfs_name	*name,
	xfs_ino_t		inum,
	xfs_extlen_t		total)
{
	struct xfs_scrub	*sc = rd->sc;
	struct xfs_inode	*dp = rd->sc->tempip;
	int			error;

	ASSERT(S_ISDIR(VFS_I(dp)->i_mode));

	error = xfs_dir_ino_validate(sc->mp, inum);
	if (error)
		return error;

	trace_xrep_dir_replay_createname(dp, name, inum);

	xrep_dir_init_args(rd, dp, name);
	rd->args.inumber = inum;
	rd->args.total = total;
	rd->args.op_flags = XFS_DA_OP_ADDNAME | XFS_DA_OP_OKNOENT;
	return xfs_dir_createname_args(&rd->args);
}

/* Replay a stashed removename onto the temporary directory. */
STATIC int
xrep_dir_replay_removename(
	struct xrep_dir		*rd,
	const struct xfs_name	*name,
	xfs_extlen_t		total)
{
	struct xfs_inode	*dp = rd->args.dp;

	ASSERT(S_ISDIR(VFS_I(dp)->i_mode));

	xrep_dir_init_args(rd, dp, name);
	rd->args.op_flags = 0;
	rd->args.total = total;

	trace_xrep_dir_replay_removename(dp, name, 0);
	return xfs_dir_removename_args(&rd->args);
}

/*
 * Add this stashed incore directory entry to the temporary directory.
 * The caller must hold the tempdir's IOLOCK, must not hold any ILOCKs, and
 * must not be in transaction context.
 */
STATIC int
xrep_dir_replay_update(
	struct xrep_dir			*rd,
	const struct xfs_name		*xname,
	const struct xrep_dirent	*dirent)
{
	struct xfs_mount		*mp = rd->sc->mp;
#ifdef DEBUG
	xfs_ino_t			ino;
#endif
	uint				resblks;
	int				error;

	resblks = xfs_link_space_res(mp, xname->len);
	error = xchk_trans_alloc(rd->sc, resblks);
	if (error)
		return error;

	/* Lock the temporary directory and join it to the transaction */
	xrep_tempfile_ilock(rd->sc);
	xfs_trans_ijoin(rd->sc->tp, rd->sc->tempip, 0);

	switch (dirent->action) {
	case XREP_DIRENT_ADD:
		/*
		 * Create a replacement dirent in the temporary directory.
		 * Note that _createname doesn't check for existing entries.
		 * There shouldn't be any in the temporary dir, but we'll
		 * verify this in debug mode.
		 */
#ifdef DEBUG
		error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
		if (error != -ENOENT) {
			ASSERT(error != -ENOENT);
			goto out_cancel;
		}
#endif

		error = xrep_dir_replay_createname(rd, xname, dirent->ino,
				resblks);
		if (error)
			goto out_cancel;

		if (xname->type == XFS_DIR3_FT_DIR)
			rd->subdirs++;
		rd->dirents++;
		break;
	case XREP_DIRENT_REMOVE:
		/*
		 * Remove a dirent from the temporary directory.  Note that
		 * _removename doesn't check the inode target of the exist
		 * entry.  There should be a perfect match in the temporary
		 * dir, but we'll verify this in debug mode.
		 */
#ifdef DEBUG
		error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
		if (error) {
			ASSERT(error != 0);
			goto out_cancel;
		}
		if (ino != dirent->ino) {
			ASSERT(ino == dirent->ino);
			error = -EIO;
			goto out_cancel;
		}
#endif

		error = xrep_dir_replay_removename(rd, xname, resblks);
		if (error)
			goto out_cancel;

		if (xname->type == XFS_DIR3_FT_DIR)
			rd->subdirs--;
		rd->dirents--;
		break;
	default:
		ASSERT(0);
		error = -EIO;
		goto out_cancel;
	}

	/* Commit and unlock. */
	error = xrep_trans_commit(rd->sc);
	if (error)
		return error;

	xrep_tempfile_iunlock(rd->sc);
	return 0;
out_cancel:
	xchk_trans_cancel(rd->sc);
	xrep_tempfile_iunlock(rd->sc);
	return error;
}

/*
 * Flush stashed incore dirent updates that have been recorded by the scanner.
 * This is done to reduce the memory requirements of the directory rebuild,
 * since directories can contain up to 32GB of directory data.
 *
 * Caller must not hold transactions or ILOCKs.  Caller must hold the tempdir
 * IOLOCK.
 */
STATIC int
xrep_dir_replay_updates(
	struct xrep_dir		*rd)
{
	xfarray_idx_t		array_cur;
	int			error;

	/* Add all the salvaged dirents to the temporary directory. */
	mutex_lock(&rd->pscan.lock);
	foreach_xfarray_idx(rd->dir_entries, array_cur) {
		struct xrep_dirent	dirent;

		error = xfarray_load(rd->dir_entries, array_cur, &dirent);
		if (error)
			goto out_unlock;

		error = xfblob_loadname(rd->dir_names, dirent.name_cookie,
				&rd->xname, dirent.namelen);
		if (error)
			goto out_unlock;
		rd->xname.type = dirent.ftype;
		mutex_unlock(&rd->pscan.lock);

		error = xrep_dir_replay_update(rd, &rd->xname, &dirent);
		if (error)
			return error;
		mutex_lock(&rd->pscan.lock);
	}

	/* Empty out both arrays now that we've added the entries. */
	xfarray_truncate(rd->dir_entries);
	xfblob_truncate(rd->dir_names);
	mutex_unlock(&rd->pscan.lock);
	return 0;
out_unlock:
	mutex_unlock(&rd->pscan.lock);
	return error;
}

/*
 * Periodically flush stashed directory entries to the temporary dir.  This
 * is done to reduce the memory requirements of the directory rebuild, since
 * directories can contain up to 32GB of directory data.
 */
STATIC int
xrep_dir_flush_stashed(
	struct xrep_dir		*rd)
{
	int			error;

	/*
	 * Entering this function, the scrub context has a reference to the
	 * inode being repaired, the temporary file, and a scrub transaction
	 * that we use during dirent salvaging to avoid livelocking if there
	 * are cycles in the directory structures.  We hold ILOCK_EXCL on both
	 * the inode being repaired and the temporary file, though they are
	 * not ijoined to the scrub transaction.
	 *
	 * To constrain kernel memory use, we occasionally write salvaged
	 * dirents from the xfarray and xfblob structures into the temporary
	 * directory in preparation for exchanging the directory structures at
	 * the end.  Updating the temporary file requires a transaction, so we
	 * commit the scrub transaction and drop the two ILOCKs so that
	 * we can allocate whatever transaction we want.
	 *
	 * We still hold IOLOCK_EXCL on the inode being repaired, which
	 * prevents anyone from accessing the damaged directory data while we
	 * repair it.
	 */
	error = xrep_trans_commit(rd->sc);
	if (error)
		return error;
	xchk_iunlock(rd->sc, XFS_ILOCK_EXCL);

	/*
	 * Take the IOLOCK of the temporary file while we modify dirents.  This
	 * isn't strictly required because the temporary file is never revealed
	 * to userspace, but we follow the same locking rules.  We still hold
	 * sc->ip's IOLOCK.
	 */
	error = xrep_tempfile_iolock_polled(rd->sc);
	if (error)
		return error;

	/* Write to the tempdir all the updates that we've stashed. */
	error = xrep_dir_replay_updates(rd);
	xrep_tempfile_iounlock(rd->sc);
	if (error)
		return error;

	/*
	 * Recreate the salvage transaction and relock the dir we're salvaging.
	 */
	error = xchk_trans_alloc(rd->sc, 0);
	if (error)
		return error;
	xchk_ilock(rd->sc, XFS_ILOCK_EXCL);
	return 0;
}

/* Decide if we've stashed too much dirent data in memory. */
static inline bool
xrep_dir_want_flush_stashed(
	struct xrep_dir		*rd)
{
	unsigned long long	bytes;

	bytes = xfarray_bytes(rd->dir_entries) + xfblob_bytes(rd->dir_names);
	return bytes > XREP_DIR_MAX_STASH_BYTES;
}

/* Extract as many directory entries as we can. */
STATIC int
xrep_dir_recover(
	struct xrep_dir		*rd)
{
	struct xfs_bmbt_irec	got;
	struct xfs_scrub	*sc = rd->sc;
	struct xfs_da_geometry	*geo = sc->mp->m_dir_geo;
	xfs_fileoff_t		offset;
	xfs_dablk_t		dabno;
	__be32			magic_guess;
	int			nmap;
	int			error;

	xrep_dir_guess_format(rd, &magic_guess);

	/* Iterate each directory data block in the data fork. */
	for (offset = 0;
	     offset < geo->leafblk;
	     offset = got.br_startoff + got.br_blockcount) {
		nmap = 1;
		error = xfs_bmapi_read(sc->ip, offset, geo->leafblk - offset,
				&got, &nmap, 0);
		if (error)
			return error;
		if (nmap != 1)
			return -EFSCORRUPTED;
		if (!xfs_bmap_is_written_extent(&got))
			continue;

		for (dabno = round_up(got.br_startoff, geo->fsbcount);
		     dabno < got.br_startoff + got.br_blockcount;
		     dabno += geo->fsbcount) {
			if (xchk_should_terminate(rd->sc, &error))
				return error;

			error = xrep_dir_recover_dirblock(rd,
					magic_guess, dabno);
			if (error)
				return error;

			/* Flush dirents to constrain memory usage. */
			if (xrep_dir_want_flush_stashed(rd)) {
				error = xrep_dir_flush_stashed(rd);
				if (error)
					return error;
			}
		}
	}

	return 0;
}

/*
 * Find all the directory entries for this inode by scraping them out of the
 * directory leaf blocks by hand, and flushing them into the temp dir.
 */
STATIC int
xrep_dir_find_entries(
	struct xrep_dir		*rd)
{
	struct xfs_inode	*dp = rd->sc->ip;
	int			error;

	/*
	 * Salvage directory entries from the old directory, and write them to
	 * the temporary directory.
	 */
	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
		error = xrep_dir_recover_sf(rd);
	} else {
		error = xfs_iread_extents(rd->sc->tp, dp, XFS_DATA_FORK);
		if (error)
			return error;

		error = xrep_dir_recover(rd);
	}
	if (error)
		return error;

	return xrep_dir_flush_stashed(rd);
}

/* Scan all files in the filesystem for dirents. */
STATIC int
xrep_dir_salvage_entries(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	int			error;

	/*
	 * Drop the ILOCK on this directory so that we can scan for this
	 * directory's parent.  Figure out who is going to be the parent of
	 * this directory, then retake the ILOCK so that we can salvage
	 * directory entries.
	 */
	xchk_iunlock(sc, XFS_ILOCK_EXCL);
	error = xrep_dir_find_parent(rd);
	xchk_ilock(sc, XFS_ILOCK_EXCL);
	if (error)
		return error;

	/*
	 * Collect directory entries by parsing raw leaf blocks to salvage
	 * whatever we can.  When we're done, free the staging memory before
	 * exchanging the directories to reduce memory usage.
	 */
	error = xrep_dir_find_entries(rd);
	if (error)
		return error;

	/*
	 * Cancel the repair transaction and drop the ILOCK so that we can
	 * (later) use the atomic mapping exchange functions to compute the
	 * correct block reservations and re-lock the inodes.
	 *
	 * We still hold IOLOCK_EXCL (aka i_rwsem) which will prevent directory
	 * modifications, but there's nothing to prevent userspace from reading
	 * the directory until we're ready for the exchange operation.  Reads
	 * will return -EIO without shutting down the fs, so we're ok with
	 * that.
	 *
	 * The VFS can change dotdot on us, but the findparent scan will keep
	 * our incore parent inode up to date.  See the note on locking issues
	 * for more details.
	 */
	error = xrep_trans_commit(sc);
	if (error)
		return error;

	xchk_iunlock(sc, XFS_ILOCK_EXCL);
	return 0;
}


/*
 * Examine a parent pointer of a file.  If it leads us back to the directory
 * that we're rebuilding, create an incore dirent from the parent pointer and
 * stash it.
 */
STATIC int
xrep_dir_scan_pptr(
	struct xfs_scrub		*sc,
	struct xfs_inode		*ip,
	unsigned int			attr_flags,
	const unsigned char		*name,
	unsigned int			namelen,
	const void			*value,
	unsigned int			valuelen,
	void				*priv)
{
	struct xfs_name			xname = {
		.name			= name,
		.len			= namelen,
		.type			= xfs_mode_to_ftype(VFS_I(ip)->i_mode),
	};
	xfs_ino_t			parent_ino;
	uint32_t			parent_gen;
	struct xrep_dir			*rd = priv;
	int				error;

	if (!(attr_flags & XFS_ATTR_PARENT))
		return 0;

	/*
	 * Ignore parent pointers that point back to a different dir, list the
	 * wrong generation number, or are invalid.
	 */
	error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
			valuelen, &parent_ino, &parent_gen);
	if (error)
		return error;

	if (parent_ino != sc->ip->i_ino ||
	    parent_gen != VFS_I(sc->ip)->i_generation)
		return 0;

	mutex_lock(&rd->pscan.lock);
	error = xrep_dir_stash_createname(rd, &xname, ip->i_ino);
	mutex_unlock(&rd->pscan.lock);
	return error;
}

/*
 * If this child dirent points to the directory being repaired, remember that
 * fact so that we can reset the dotdot entry if necessary.
 */
STATIC int
xrep_dir_scan_dirent(
	struct xfs_scrub	*sc,
	struct xfs_inode	*dp,
	xfs_dir2_dataptr_t	dapos,
	const struct xfs_name	*name,
	xfs_ino_t		ino,
	void			*priv)
{
	struct xrep_dir		*rd = priv;

	/* Dirent doesn't point to this directory. */
	if (ino != rd->sc->ip->i_ino)
		return 0;

	/* Ignore garbage inum. */
	if (!xfs_verify_dir_ino(rd->sc->mp, ino))
		return 0;

	/* No weird looking names. */
	if (name->len >= MAXNAMELEN || name->len <= 0)
		return 0;

	/* Don't pick up dot or dotdot entries; we only want child dirents. */
	if (xfs_dir2_samename(name, &xfs_name_dotdot) ||
	    xfs_dir2_samename(name, &xfs_name_dot))
		return 0;

	trace_xrep_dir_stash_createname(sc->tempip, &xfs_name_dotdot,
			dp->i_ino);

	xrep_findparent_scan_found(&rd->pscan, dp->i_ino);
	return 0;
}

/*
 * Decide if we want to look for child dirents or parent pointers in this file.
 * Skip the dir being repaired and any files being used to stage repairs.
 */
static inline bool
xrep_dir_want_scan(
	struct xrep_dir		*rd,
	const struct xfs_inode	*ip)
{
	return ip != rd->sc->ip && !xrep_is_tempfile(ip);
}

/*
 * Take ILOCK on a file that we want to scan.
 *
 * Select ILOCK_EXCL if the file is a directory with an unloaded data bmbt or
 * has an unloaded attr bmbt.  Otherwise, take ILOCK_SHARED.
 */
static inline unsigned int
xrep_dir_scan_ilock(
	struct xrep_dir		*rd,
	struct xfs_inode	*ip)
{
	uint			lock_mode = XFS_ILOCK_SHARED;

	/* Need to take the shared ILOCK to advance the iscan cursor. */
	if (!xrep_dir_want_scan(rd, ip))
		goto lock;

	if (S_ISDIR(VFS_I(ip)->i_mode) && xfs_need_iread_extents(&ip->i_df)) {
		lock_mode = XFS_ILOCK_EXCL;
		goto lock;
	}

	if (xfs_inode_has_attr_fork(ip) && xfs_need_iread_extents(&ip->i_af))
		lock_mode = XFS_ILOCK_EXCL;

lock:
	xfs_ilock(ip, lock_mode);
	return lock_mode;
}

/*
 * Scan this file for relevant child dirents or parent pointers that point to
 * the directory we're rebuilding.
 */
STATIC int
xrep_dir_scan_file(
	struct xrep_dir		*rd,
	struct xfs_inode	*ip)
{
	unsigned int		lock_mode;
	int			error = 0;

	lock_mode = xrep_dir_scan_ilock(rd, ip);

	if (!xrep_dir_want_scan(rd, ip))
		goto scan_done;

	/*
	 * If the extended attributes look as though they has been zapped by
	 * the inode record repair code, we cannot scan for parent pointers.
	 */
	if (xchk_pptr_looks_zapped(ip)) {
		error = -EBUSY;
		goto scan_done;
	}

	error = xchk_xattr_walk(rd->sc, ip, xrep_dir_scan_pptr, NULL, rd);
	if (error)
		goto scan_done;

	if (S_ISDIR(VFS_I(ip)->i_mode)) {
		/*
		 * If the directory looks as though it has been zapped by the
		 * inode record repair code, we cannot scan for child dirents.
		 */
		if (xchk_dir_looks_zapped(ip)) {
			error = -EBUSY;
			goto scan_done;
		}

		error = xchk_dir_walk(rd->sc, ip, xrep_dir_scan_dirent, rd);
		if (error)
			goto scan_done;
	}

scan_done:
	xchk_iscan_mark_visited(&rd->pscan.iscan, ip);
	xfs_iunlock(ip, lock_mode);
	return error;
}

/*
 * Scan all files in the filesystem for parent pointers that we can turn into
 * replacement dirents, and a dirent that we can use to set the dotdot pointer.
 */
STATIC int
xrep_dir_scan_dirtree(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	struct xfs_inode	*ip;
	int			error;

	/* Roots of directory trees are their own parents. */
	if (sc->ip == sc->mp->m_rootip)
		xrep_findparent_scan_found(&rd->pscan, sc->ip->i_ino);

	/*
	 * Filesystem scans are time consuming.  Drop the directory ILOCK and
	 * all other resources for the duration of the scan and hope for the
	 * best.  The live update hooks will keep our scan information up to
	 * date even though we've dropped the locks.
	 */
	xchk_trans_cancel(sc);
	if (sc->ilock_flags & (XFS_ILOCK_SHARED | XFS_ILOCK_EXCL))
		xchk_iunlock(sc, sc->ilock_flags & (XFS_ILOCK_SHARED |
						    XFS_ILOCK_EXCL));
	error = xchk_trans_alloc_empty(sc);
	if (error)
		return error;

	while ((error = xchk_iscan_iter(&rd->pscan.iscan, &ip)) == 1) {
		bool		flush;

		error = xrep_dir_scan_file(rd, ip);
		xchk_irele(sc, ip);
		if (error)
			break;

		/* Flush stashed dirent updates to constrain memory usage. */
		mutex_lock(&rd->pscan.lock);
		flush = xrep_dir_want_flush_stashed(rd);
		mutex_unlock(&rd->pscan.lock);
		if (flush) {
			xchk_trans_cancel(sc);

			error = xrep_tempfile_iolock_polled(sc);
			if (error)
				break;

			error = xrep_dir_replay_updates(rd);
			xrep_tempfile_iounlock(sc);
			if (error)
				break;

			error = xchk_trans_alloc_empty(sc);
			if (error)
				break;
		}

		if (xchk_should_terminate(sc, &error))
			break;
	}
	xchk_iscan_iter_finish(&rd->pscan.iscan);
	if (error) {
		/*
		 * If we couldn't grab an inode that was busy with a state
		 * change, change the error code so that we exit to userspace
		 * as quickly as possible.
		 */
		if (error == -EBUSY)
			return -ECANCELED;
		return error;
	}

	/*
	 * Cancel the empty transaction so that we can (later) use the atomic
	 * file mapping exchange functions to lock files and commit the new
	 * directory.
	 */
	xchk_trans_cancel(rd->sc);
	return 0;
}

/*
 * Capture dirent updates being made by other threads which are relevant to the
 * directory being repaired.
 */
STATIC int
xrep_dir_live_update(
	struct notifier_block		*nb,
	unsigned long			action,
	void				*data)
{
	struct xfs_dir_update_params	*p = data;
	struct xrep_dir			*rd;
	struct xfs_scrub		*sc;
	int				error = 0;

	rd = container_of(nb, struct xrep_dir, pscan.dhook.dirent_hook.nb);
	sc = rd->sc;

	/*
	 * This thread updated a child dirent in the directory that we're
	 * rebuilding.  Stash the update for replay against the temporary
	 * directory.
	 */
	if (p->dp->i_ino == sc->ip->i_ino &&
	    xchk_iscan_want_live_update(&rd->pscan.iscan, p->ip->i_ino)) {
		mutex_lock(&rd->pscan.lock);
		if (p->delta > 0)
			error = xrep_dir_stash_createname(rd, p->name,
					p->ip->i_ino);
		else
			error = xrep_dir_stash_removename(rd, p->name,
					p->ip->i_ino);
		mutex_unlock(&rd->pscan.lock);
		if (error)
			goto out_abort;
	}

	/*
	 * This thread updated another directory's child dirent that points to
	 * the directory that we're rebuilding, so remember the new dotdot
	 * target.
	 */
	if (p->ip->i_ino == sc->ip->i_ino &&
	    xchk_iscan_want_live_update(&rd->pscan.iscan, p->dp->i_ino)) {
		if (p->delta > 0) {
			trace_xrep_dir_stash_createname(sc->tempip,
					&xfs_name_dotdot,
					p->dp->i_ino);

			xrep_findparent_scan_found(&rd->pscan, p->dp->i_ino);
		} else {
			trace_xrep_dir_stash_removename(sc->tempip,
					&xfs_name_dotdot,
					rd->pscan.parent_ino);

			xrep_findparent_scan_found(&rd->pscan, NULLFSINO);
		}
	}

	return NOTIFY_DONE;
out_abort:
	xchk_iscan_abort(&rd->pscan.iscan);
	return NOTIFY_DONE;
}

/*
 * Free all the directory blocks and reset the data fork.  The caller must
 * join the inode to the transaction.  This function returns with the inode
 * joined to a clean scrub transaction.
 */
STATIC int
xrep_dir_reset_fork(
	struct xrep_dir		*rd,
	xfs_ino_t		parent_ino)
{
	struct xfs_scrub	*sc = rd->sc;
	struct xfs_ifork	*ifp = xfs_ifork_ptr(sc->tempip, XFS_DATA_FORK);
	int			error;

	/* Unmap all the directory buffers. */
	if (xfs_ifork_has_extents(ifp)) {
		error = xrep_reap_ifork(sc, sc->tempip, XFS_DATA_FORK);
		if (error)
			return error;
	}

	trace_xrep_dir_reset_fork(sc->tempip, parent_ino);

	/* Reset the data fork to an empty data fork. */
	xfs_idestroy_fork(ifp);
	ifp->if_bytes = 0;
	sc->tempip->i_disk_size = 0;

	/* Reinitialize the short form directory. */
	xrep_dir_init_args(rd, sc->tempip, NULL);
	return xfs_dir2_sf_create(&rd->args, parent_ino);
}

/*
 * Prepare both inodes' directory forks for exchanging mappings.  Promote the
 * tempfile from short format to leaf format, and if the file being repaired
 * has a short format data fork, turn it into an empty extent list.
 */
STATIC int
xrep_dir_swap_prep(
	struct xfs_scrub	*sc,
	bool			temp_local,
	bool			ip_local)
{
	int			error;

	/*
	 * If the tempfile's directory is in shortform format, convert that to
	 * a single leaf extent so that we can use the atomic mapping exchange.
	 */
	if (temp_local) {
		struct xfs_da_args	args = {
			.dp		= sc->tempip,
			.geo		= sc->mp->m_dir_geo,
			.whichfork	= XFS_DATA_FORK,
			.trans		= sc->tp,
			.total		= 1,
			.owner		= sc->ip->i_ino,
		};

		error = xfs_dir2_sf_to_block(&args);
		if (error)
			return error;

		/*
		 * Roll the deferred log items to get us back to a clean
		 * transaction.
		 */
		error = xfs_defer_finish(&sc->tp);
		if (error)
			return error;
	}

	/*
	 * If the file being repaired had a shortform data fork, convert that
	 * to an empty extent list in preparation for the atomic mapping
	 * exchange.
	 */
	if (ip_local) {
		struct xfs_ifork	*ifp;

		ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK);
		xfs_idestroy_fork(ifp);
		ifp->if_format = XFS_DINODE_FMT_EXTENTS;
		ifp->if_nextents = 0;
		ifp->if_bytes = 0;
		ifp->if_data = NULL;
		ifp->if_height = 0;

		xfs_trans_log_inode(sc->tp, sc->ip,
				XFS_ILOG_CORE | XFS_ILOG_DDATA);
	}

	return 0;
}

/*
 * Replace the inode number of a directory entry.
 */
static int
xrep_dir_replace(
	struct xrep_dir		*rd,
	struct xfs_inode	*dp,
	const struct xfs_name	*name,
	xfs_ino_t		inum,
	xfs_extlen_t		total)
{
	struct xfs_scrub	*sc = rd->sc;
	int			error;

	ASSERT(S_ISDIR(VFS_I(dp)->i_mode));

	error = xfs_dir_ino_validate(sc->mp, inum);
	if (error)
		return error;

	xrep_dir_init_args(rd, dp, name);
	rd->args.inumber = inum;
	rd->args.total = total;
	return xfs_dir_replace_args(&rd->args);
}

/*
 * Reset the link count of this directory and adjust the unlinked list pointers
 * as needed.
 */
STATIC int
xrep_dir_set_nlink(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	struct xfs_inode	*dp = sc->ip;
	struct xfs_perag	*pag;
	unsigned int		new_nlink = min_t(unsigned long long,
						  rd->subdirs + 2,
						  XFS_NLINK_PINNED);
	int			error;

	/*
	 * The directory is not on the incore unlinked list, which means that
	 * it needs to be reachable via the directory tree.  Update the nlink
	 * with our observed link count.  If the directory has no parent, it
	 * will be moved to the orphanage.
	 */
	if (!xfs_inode_on_unlinked_list(dp))
		goto reset_nlink;

	/*
	 * The directory is on the unlinked list and we did not find any
	 * dirents.  Set the link count to zero and let the directory
	 * inactivate when the last reference drops.
	 */
	if (rd->dirents == 0) {
		rd->needs_adoption = false;
		new_nlink = 0;
		goto reset_nlink;
	}

	/*
	 * The directory is on the unlinked list and we found dirents.  This
	 * directory needs to be reachable via the directory tree.  Remove the
	 * dir from the unlinked list and update nlink with the observed link
	 * count.  If the directory has no parent, it will be moved to the
	 * orphanage.
	 */
	pag = xfs_perag_get(sc->mp, XFS_INO_TO_AGNO(sc->mp, dp->i_ino));
	if (!pag) {
		ASSERT(0);
		return -EFSCORRUPTED;
	}

	error = xfs_iunlink_remove(sc->tp, pag, dp);
	xfs_perag_put(pag);
	if (error)
		return error;

reset_nlink:
	if (VFS_I(dp)->i_nlink != new_nlink)
		set_nlink(VFS_I(dp), new_nlink);
	return 0;
}

/*
 * Finish replaying stashed dirent updates, allocate a transaction for
 * exchanging data fork mappings, and take the ILOCKs of both directories
 * before we commit the new directory structure.
 */
STATIC int
xrep_dir_finalize_tempdir(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	int			error;

	if (!xfs_has_parent(sc->mp))
		return xrep_tempexch_trans_alloc(sc, XFS_DATA_FORK, &rd->tx);

	/*
	 * Repair relies on the ILOCK to quiesce all possible dirent updates.
	 * Replay all queued dirent updates into the tempdir before exchanging
	 * the contents, even if that means dropping the ILOCKs and the
	 * transaction.
	 */
	do {
		error = xrep_dir_replay_updates(rd);
		if (error)
			return error;

		error = xrep_tempexch_trans_alloc(sc, XFS_DATA_FORK, &rd->tx);
		if (error)
			return error;

		if (xfarray_length(rd->dir_entries) == 0)
			break;

		xchk_trans_cancel(sc);
		xrep_tempfile_iunlock_both(sc);
	} while (!xchk_should_terminate(sc, &error));
	return error;
}

/* Exchange the temporary directory's data fork with the one being repaired. */
STATIC int
xrep_dir_swap(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	bool			ip_local, temp_local;
	int			error = 0;

	/*
	 * If we never found the parent for this directory, temporarily assign
	 * the root dir as the parent; we'll move this to the orphanage after
	 * exchanging the dir contents.  We hold the ILOCK of the dir being
	 * repaired, so we're not worried about racy updates of dotdot.
	 */
	ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
	if (rd->pscan.parent_ino == NULLFSINO) {
		rd->needs_adoption = true;
		rd->pscan.parent_ino = rd->sc->mp->m_sb.sb_rootino;
	}

	/*
	 * Reset the temporary directory's '..' entry to point to the parent
	 * that we found.  The temporary directory was created with the root
	 * directory as the parent, so we can skip this if repairing a
	 * subdirectory of the root.
	 *
	 * It's also possible that this replacement could also expand a sf
	 * tempdir into block format.
	 */
	if (rd->pscan.parent_ino != sc->mp->m_rootip->i_ino) {
		error = xrep_dir_replace(rd, rd->sc->tempip, &xfs_name_dotdot,
				rd->pscan.parent_ino, rd->tx.req.resblks);
		if (error)
			return error;
	}

	/*
	 * Changing the dot and dotdot entries could have changed the shape of
	 * the directory, so we recompute these.
	 */
	ip_local = sc->ip->i_df.if_format == XFS_DINODE_FMT_LOCAL;
	temp_local = sc->tempip->i_df.if_format == XFS_DINODE_FMT_LOCAL;

	/*
	 * If the both files have a local format data fork and the rebuilt
	 * directory data would fit in the repaired file's data fork, copy
	 * the contents from the tempfile and update the directory link count.
	 * We're done now.
	 */
	if (ip_local && temp_local &&
	    sc->tempip->i_disk_size <= xfs_inode_data_fork_size(sc->ip)) {
		xrep_tempfile_copyout_local(sc, XFS_DATA_FORK);
		return xrep_dir_set_nlink(rd);
	}

	/*
	 * Clean the transaction before we start working on exchanging
	 * directory contents.
	 */
	error = xrep_tempfile_roll_trans(rd->sc);
	if (error)
		return error;

	/* Otherwise, make sure both data forks are in block-mapping mode. */
	error = xrep_dir_swap_prep(sc, temp_local, ip_local);
	if (error)
		return error;

	/*
	 * Set nlink of the directory in the same transaction sequence that
	 * (atomically) commits the new directory data.
	 */
	error = xrep_dir_set_nlink(rd);
	if (error)
		return error;

	return xrep_tempexch_contents(sc, &rd->tx);
}

/*
 * Exchange the new directory contents (which we created in the tempfile) with
 * the directory being repaired.
 */
STATIC int
xrep_dir_rebuild_tree(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	int			error;

	trace_xrep_dir_rebuild_tree(sc->ip, rd->pscan.parent_ino);

	/*
	 * Take the IOLOCK on the temporary file so that we can run dir
	 * operations with the same locks held as we would for a normal file.
	 * We still hold sc->ip's IOLOCK.
	 */
	error = xrep_tempfile_iolock_polled(rd->sc);
	if (error)
		return error;

	/*
	 * Allocate transaction, lock inodes, and make sure that we've replayed
	 * all the stashed dirent updates to the tempdir.  After this point,
	 * we're ready to exchange data fork mappings.
	 */
	error = xrep_dir_finalize_tempdir(rd);
	if (error)
		return error;

	if (xchk_iscan_aborted(&rd->pscan.iscan))
		return -ECANCELED;

	/*
	 * Exchange the tempdir's data fork with the file being repaired.  This
	 * recreates the transaction and re-takes the ILOCK in the scrub
	 * context.
	 */
	error = xrep_dir_swap(rd);
	if (error)
		return error;

	/*
	 * Release the old directory blocks and reset the data fork of the temp
	 * directory to an empty shortform directory because inactivation does
	 * nothing for directories.
	 */
	error = xrep_dir_reset_fork(rd, sc->mp->m_rootip->i_ino);
	if (error)
		return error;

	/*
	 * Roll to get a transaction without any inodes joined to it.  Then we
	 * can drop the tempfile's ILOCK and IOLOCK before doing more work on
	 * the scrub target directory.
	 */
	error = xfs_trans_roll(&sc->tp);
	if (error)
		return error;

	xrep_tempfile_iunlock(sc);
	xrep_tempfile_iounlock(sc);
	return 0;
}

/* Set up the filesystem scan so we can regenerate directory entries. */
STATIC int
xrep_dir_setup_scan(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	char			*descr;
	int			error;

	/* Set up some staging memory for salvaging dirents. */
	descr = xchk_xfile_ino_descr(sc, "directory entries");
	error = xfarray_create(descr, 0, sizeof(struct xrep_dirent),
			&rd->dir_entries);
	kfree(descr);
	if (error)
		return error;

	descr = xchk_xfile_ino_descr(sc, "directory entry names");
	error = xfblob_create(descr, &rd->dir_names);
	kfree(descr);
	if (error)
		goto out_xfarray;

	if (xfs_has_parent(sc->mp))
		error = __xrep_findparent_scan_start(sc, &rd->pscan,
				xrep_dir_live_update);
	else
		error = xrep_findparent_scan_start(sc, &rd->pscan);
	if (error)
		goto out_xfblob;

	return 0;

out_xfblob:
	xfblob_destroy(rd->dir_names);
	rd->dir_names = NULL;
out_xfarray:
	xfarray_destroy(rd->dir_entries);
	rd->dir_entries = NULL;
	return error;
}

/*
 * Move the current file to the orphanage.
 *
 * Caller must hold IOLOCK_EXCL on @sc->ip, and no other inode locks.  Upon
 * successful return, the scrub transaction will have enough extra reservation
 * to make the move; it will hold IOLOCK_EXCL and ILOCK_EXCL of @sc->ip and the
 * orphanage; and both inodes will be ijoined.
 */
STATIC int
xrep_dir_move_to_orphanage(
	struct xrep_dir		*rd)
{
	struct xfs_scrub	*sc = rd->sc;
	xfs_ino_t		orig_parent, new_parent;
	int			error;

	/*
	 * We are about to drop the ILOCK on sc->ip to lock the orphanage and
	 * prepare for the adoption.  Therefore, look up the old dotdot entry
	 * for sc->ip so that we can compare it after we re-lock sc->ip.
	 */
	error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &orig_parent);
	if (error)
		return error;

	/*
	 * Drop the ILOCK on the scrub target and commit the transaction.
	 * Adoption computes its own resource requirements and gathers the
	 * necessary components.
	 */
	error = xrep_trans_commit(sc);
	if (error)
		return error;
	xchk_iunlock(sc, XFS_ILOCK_EXCL);

	/* If we can take the orphanage's iolock then we're ready to move. */
	if (!xrep_orphanage_ilock_nowait(sc, XFS_IOLOCK_EXCL)) {
		xchk_iunlock(sc, sc->ilock_flags);
		error = xrep_orphanage_iolock_two(sc);
		if (error)
			return error;
	}

	/* Grab transaction and ILOCK the two files. */
	error = xrep_adoption_trans_alloc(sc, &rd->adoption);
	if (error)
		return error;

	error = xrep_adoption_compute_name(&rd->adoption, &rd->xname);
	if (error)
		return error;

	/*
	 * Now that we've reacquired the ILOCK on sc->ip, look up the dotdot
	 * entry again.  If the parent changed or the child was unlinked while
	 * the child directory was unlocked, we don't need to move the child to
	 * the orphanage after all.
	 */
	error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &new_parent);
	if (error)
		return error;

	/*
	 * Attach to the orphanage if we still have a linked directory and it
	 * hasn't been moved.
	 */
	if (orig_parent == new_parent && VFS_I(sc->ip)->i_nlink > 0) {
		error = xrep_adoption_move(&rd->adoption);
		if (error)
			return error;
	}

	/*
	 * Launder the scrub transaction so we can drop the orphanage ILOCK
	 * and IOLOCK.  Return holding the scrub target's ILOCK and IOLOCK.
	 */
	error = xrep_adoption_trans_roll(&rd->adoption);
	if (error)
		return error;

	xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
	xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
	return 0;
}

/*
 * Repair the directory metadata.
 *
 * XXX: Directory entry buffers can be multiple fsblocks in size.  The buffer
 * cache in XFS can't handle aliased multiblock buffers, so this might
 * misbehave if the directory blocks are crosslinked with other filesystem
 * metadata.
 *
 * XXX: Is it necessary to check the dcache for this directory to make sure
 * that we always recreate every cached entry?
 */
int
xrep_directory(
	struct xfs_scrub	*sc)
{
	struct xrep_dir		*rd = sc->buf;
	int			error;

	/* The rmapbt is required to reap the old data fork. */
	if (!xfs_has_rmapbt(sc->mp))
		return -EOPNOTSUPP;
	/* We require atomic file exchange range to rebuild anything. */
	if (!xfs_has_exchange_range(sc->mp))
		return -EOPNOTSUPP;

	error = xrep_dir_setup_scan(rd);
	if (error)
		return error;

	if (xfs_has_parent(sc->mp))
		error = xrep_dir_scan_dirtree(rd);
	else
		error = xrep_dir_salvage_entries(rd);
	if (error)
		goto out_teardown;

	/* Last chance to abort before we start committing fixes. */
	if (xchk_should_terminate(sc, &error))
		goto out_teardown;

	error = xrep_dir_rebuild_tree(rd);
	if (error)
		goto out_teardown;

	if (rd->needs_adoption) {
		if (!xrep_orphanage_can_adopt(rd->sc))
			error = -EFSCORRUPTED;
		else
			error = xrep_dir_move_to_orphanage(rd);
		if (error)
			goto out_teardown;
	}

out_teardown:
	xrep_dir_teardown(sc);
	return error;
}