Contributors: 9
Author Tokens Token Proportion Commits Commit Proportion
Darrick J. Wong 5853 97.88% 46 64.79%
David Chinner 50 0.84% 10 14.08%
Christoph Hellwig 38 0.64% 7 9.86%
Russell Cattelan 14 0.23% 1 1.41%
Nathan Scott 10 0.17% 2 2.82%
Michal Marek 8 0.13% 1 1.41%
Timothy Shimmin 3 0.05% 1 1.41%
Eric Sandeen 2 0.03% 1 1.41%
Allison Henderson 2 0.03% 2 2.82%
Total 5980 71


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2018-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_btree.h"
#include "xfs_bit.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_inode.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_dir2.h"
#include "xfs_attr.h"
#include "xfs_attr_leaf.h"
#include "xfs_attr_sf.h"
#include "xfs_attr_remote.h"
#include "xfs_bmap.h"
#include "xfs_bmap_util.h"
#include "xfs_exchmaps.h"
#include "xfs_exchrange.h"
#include "xfs_acl.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/attr.h"
#include "scrub/reap.h"
#include "scrub/attr_repair.h"

/*
 * Extended Attribute Repair
 * =========================
 *
 * We repair extended attributes by reading the attr leaf blocks looking for
 * attributes entries that look salvageable (name passes verifiers, value can
 * be retrieved, etc).  Each extended attribute worth salvaging is stashed in
 * memory, and the stashed entries are periodically replayed into a temporary
 * file to constrain memory use.  Batching the construction of the temporary
 * extended attribute structure in this fashion reduces lock cycling of the
 * file being repaired and the temporary file.
 *
 * When salvaging completes, the remaining stashed attributes are replayed to
 * the temporary file.  An atomic file contents exchange is used to commit the
 * new xattr blocks to the file being repaired.  This will disrupt attrmulti
 * cursors.
 */

struct xrep_xattr_key {
	/* Cookie for retrieval of the xattr name. */
	xfblob_cookie		name_cookie;

	/* Cookie for retrieval of the xattr value. */
	xfblob_cookie		value_cookie;

	/* XFS_ATTR_* flags */
	int			flags;

	/* Length of the value and name. */
	uint32_t		valuelen;
	uint16_t		namelen;
};

/*
 * Stash up to 8 pages of attrs in xattr_records/xattr_blobs before we write
 * them to the temp file.
 */
#define XREP_XATTR_MAX_STASH_BYTES	(PAGE_SIZE * 8)

struct xrep_xattr {
	struct xfs_scrub	*sc;

	/* Information for exchanging attr fork mappings at the end. */
	struct xrep_tempexch	tx;

	/* xattr keys */
	struct xfarray		*xattr_records;

	/* xattr values */
	struct xfblob		*xattr_blobs;

	/* Number of attributes that we are salvaging. */
	unsigned long long	attrs_found;

	/* Can we flush stashed attrs to the tempfile? */
	bool			can_flush;

	/* Did the live update fail, and hence the repair is now out of date? */
	bool			live_update_aborted;

	/* Lock protecting parent pointer updates */
	struct mutex		lock;

	/* Fixed-size array of xrep_xattr_pptr structures. */
	struct xfarray		*pptr_recs;

	/* Blobs containing parent pointer names. */
	struct xfblob		*pptr_names;

	/* Hook to capture parent pointer updates. */
	struct xfs_dir_hook	dhook;

	/* Scratch buffer for capturing parent pointers. */
	struct xfs_da_args	pptr_args;

	/* Name buffer */
	struct xfs_name		xname;
	char			namebuf[MAXNAMELEN];
};

/* Create a parent pointer in the tempfile. */
#define XREP_XATTR_PPTR_ADD	(1)

/* Remove a parent pointer from the tempfile. */
#define XREP_XATTR_PPTR_REMOVE	(2)

/* A stashed parent pointer update. */
struct xrep_xattr_pptr {
	/* Cookie for retrieval of the pptr name. */
	xfblob_cookie		name_cookie;

	/* Parent pointer record. */
	struct xfs_parent_rec	pptr_rec;

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

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

/* Set up to recreate the extended attributes. */
int
xrep_setup_xattr(
	struct xfs_scrub	*sc)
{
	if (xfs_has_parent(sc->mp))
		xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);

	return xrep_tempfile_create(sc, S_IFREG);
}

/*
 * Decide if we want to salvage this attribute.  We don't bother with
 * incomplete or oversized keys or values.  The @value parameter can be null
 * for remote attrs.
 */
STATIC int
xrep_xattr_want_salvage(
	struct xrep_xattr	*rx,
	unsigned int		attr_flags,
	const void		*name,
	int			namelen,
	const void		*value,
	int			valuelen)
{
	if (attr_flags & XFS_ATTR_INCOMPLETE)
		return false;
	if (namelen > XATTR_NAME_MAX || namelen <= 0)
		return false;
	if (!xfs_attr_namecheck(attr_flags, name, namelen))
		return false;
	if (valuelen > XATTR_SIZE_MAX || valuelen < 0)
		return false;
	if (attr_flags & XFS_ATTR_PARENT)
		return xfs_parent_valuecheck(rx->sc->mp, value, valuelen);

	return true;
}

/* Allocate an in-core record to hold xattrs while we rebuild the xattr data. */
STATIC int
xrep_xattr_salvage_key(
	struct xrep_xattr	*rx,
	int			flags,
	unsigned char		*name,
	int			namelen,
	unsigned char		*value,
	int			valuelen)
{
	struct xrep_xattr_key	key = {
		.valuelen	= valuelen,
		.flags		= flags & XFS_ATTR_NSP_ONDISK_MASK,
	};
	unsigned int		i = 0;
	int			error = 0;

	if (xchk_should_terminate(rx->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 attribute.
	 */
	if (flags & XFS_ATTR_PARENT) {
		key.namelen = namelen;

		trace_xrep_xattr_salvage_pptr(rx->sc->ip, flags, name,
				key.namelen, value, valuelen);
	} else {
		while (i < namelen && name[i] != 0)
			i++;
		if (i == 0)
			return 0;
		key.namelen = i;

		trace_xrep_xattr_salvage_rec(rx->sc->ip, flags, name,
				key.namelen, valuelen);
	}

	error = xfblob_store(rx->xattr_blobs, &key.name_cookie, name,
			key.namelen);
	if (error)
		return error;

	error = xfblob_store(rx->xattr_blobs, &key.value_cookie, value,
			key.valuelen);
	if (error)
		return error;

	error = xfarray_append(rx->xattr_records, &key);
	if (error)
		return error;

	rx->attrs_found++;
	return 0;
}

/*
 * Record a shortform extended attribute key & value for later reinsertion
 * into the inode.
 */
STATIC int
xrep_xattr_salvage_sf_attr(
	struct xrep_xattr		*rx,
	struct xfs_attr_sf_hdr		*hdr,
	struct xfs_attr_sf_entry	*sfe)
{
	struct xfs_scrub		*sc = rx->sc;
	struct xchk_xattr_buf		*ab = sc->buf;
	unsigned char			*name = sfe->nameval;
	unsigned char			*value = &sfe->nameval[sfe->namelen];

	if (!xchk_xattr_set_map(sc, ab->usedmap, (char *)name - (char *)hdr,
			sfe->namelen))
		return 0;

	if (!xchk_xattr_set_map(sc, ab->usedmap, (char *)value - (char *)hdr,
			sfe->valuelen))
		return 0;

	if (!xrep_xattr_want_salvage(rx, sfe->flags, sfe->nameval,
			sfe->namelen, value, sfe->valuelen))
		return 0;

	return xrep_xattr_salvage_key(rx, sfe->flags, sfe->nameval,
			sfe->namelen, value, sfe->valuelen);
}

/*
 * Record a local format extended attribute key & value for later reinsertion
 * into the inode.
 */
STATIC int
xrep_xattr_salvage_local_attr(
	struct xrep_xattr		*rx,
	struct xfs_attr_leaf_entry	*ent,
	unsigned int			nameidx,
	const char			*buf_end,
	struct xfs_attr_leaf_name_local	*lentry)
{
	struct xchk_xattr_buf		*ab = rx->sc->buf;
	unsigned char			*value;
	unsigned int			valuelen;
	unsigned int			namesize;

	/*
	 * Decode the leaf local entry format.  If something seems wrong, we
	 * junk the attribute.
	 */
	value = &lentry->nameval[lentry->namelen];
	valuelen = be16_to_cpu(lentry->valuelen);
	namesize = xfs_attr_leaf_entsize_local(lentry->namelen, valuelen);
	if ((char *)lentry + namesize > buf_end)
		return 0;
	if (!xrep_xattr_want_salvage(rx, ent->flags, lentry->nameval,
			lentry->namelen, value, valuelen))
		return 0;
	if (!xchk_xattr_set_map(rx->sc, ab->usedmap, nameidx, namesize))
		return 0;

	/* Try to save this attribute. */
	return xrep_xattr_salvage_key(rx, ent->flags, lentry->nameval,
			lentry->namelen, value, valuelen);
}

/*
 * Record a remote format extended attribute key & value for later reinsertion
 * into the inode.
 */
STATIC int
xrep_xattr_salvage_remote_attr(
	struct xrep_xattr		*rx,
	struct xfs_attr_leaf_entry	*ent,
	unsigned int			nameidx,
	const char			*buf_end,
	struct xfs_attr_leaf_name_remote *rentry,
	unsigned int			ent_idx,
	struct xfs_buf			*leaf_bp)
{
	struct xchk_xattr_buf		*ab = rx->sc->buf;
	struct xfs_da_args		args = {
		.trans			= rx->sc->tp,
		.dp			= rx->sc->ip,
		.index			= ent_idx,
		.geo			= rx->sc->mp->m_attr_geo,
		.owner			= rx->sc->ip->i_ino,
		.attr_filter		= ent->flags & XFS_ATTR_NSP_ONDISK_MASK,
		.namelen		= rentry->namelen,
		.name			= rentry->name,
		.value			= ab->value,
		.valuelen		= be32_to_cpu(rentry->valuelen),
	};
	unsigned int			namesize;
	int				error;

	/*
	 * Decode the leaf remote entry format.  If something seems wrong, we
	 * junk the attribute.  Note that we should never find a zero-length
	 * remote attribute value.
	 */
	namesize = xfs_attr_leaf_entsize_remote(rentry->namelen);
	if ((char *)rentry + namesize > buf_end)
		return 0;
	if (args.valuelen == 0 ||
	    !xrep_xattr_want_salvage(rx, ent->flags, rentry->name,
			rentry->namelen, NULL, args.valuelen))
		return 0;
	if (!xchk_xattr_set_map(rx->sc, ab->usedmap, nameidx, namesize))
		return 0;

	/*
	 * Enlarge the buffer (if needed) to hold the value that we're trying
	 * to salvage from the old extended attribute data.
	 */
	error = xchk_setup_xattr_buf(rx->sc, args.valuelen);
	if (error == -ENOMEM)
		error = -EDEADLOCK;
	if (error)
		return error;

	/* Look up the remote value and stash it for reconstruction. */
	error = xfs_attr3_leaf_getvalue(leaf_bp, &args);
	if (error || args.rmtblkno == 0)
		goto err_free;

	error = xfs_attr_rmtval_get(&args);
	if (error)
		goto err_free;

	/* Try to save this attribute. */
	error = xrep_xattr_salvage_key(rx, ent->flags, rentry->name,
			rentry->namelen, ab->value, args.valuelen);
err_free:
	/* remote value was garbage, junk it */
	if (error == -EFSBADCRC || error == -EFSCORRUPTED)
		error = 0;
	return error;
}

/* Extract every xattr key that we can from this attr fork block. */
STATIC int
xrep_xattr_recover_leaf(
	struct xrep_xattr		*rx,
	struct xfs_buf			*bp)
{
	struct xfs_attr3_icleaf_hdr	leafhdr;
	struct xfs_scrub		*sc = rx->sc;
	struct xfs_mount		*mp = sc->mp;
	struct xfs_attr_leafblock	*leaf;
	struct xfs_attr_leaf_name_local	*lentry;
	struct xfs_attr_leaf_name_remote *rentry;
	struct xfs_attr_leaf_entry	*ent;
	struct xfs_attr_leaf_entry	*entries;
	struct xchk_xattr_buf		*ab = rx->sc->buf;
	char				*buf_end;
	size_t				off;
	unsigned int			nameidx;
	unsigned int			hdrsize;
	int				i;
	int				error = 0;

	bitmap_zero(ab->usedmap, mp->m_attr_geo->blksize);

	/* Check the leaf header */
	leaf = bp->b_addr;
	xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf);
	hdrsize = xfs_attr3_leaf_hdr_size(leaf);
	xchk_xattr_set_map(sc, ab->usedmap, 0, hdrsize);
	entries = xfs_attr3_leaf_entryp(leaf);

	buf_end = (char *)bp->b_addr + mp->m_attr_geo->blksize;
	for (i = 0, ent = entries; i < leafhdr.count; ent++, i++) {
		if (xchk_should_terminate(sc, &error))
			return error;

		/* Skip key if it conflicts with something else? */
		off = (char *)ent - (char *)leaf;
		if (!xchk_xattr_set_map(sc, ab->usedmap, off,
				sizeof(xfs_attr_leaf_entry_t)))
			continue;

		/* Check the name information. */
		nameidx = be16_to_cpu(ent->nameidx);
		if (nameidx < leafhdr.firstused ||
		    nameidx >= mp->m_attr_geo->blksize)
			continue;

		if (ent->flags & XFS_ATTR_LOCAL) {
			lentry = xfs_attr3_leaf_name_local(leaf, i);
			error = xrep_xattr_salvage_local_attr(rx, ent, nameidx,
					buf_end, lentry);
		} else {
			rentry = xfs_attr3_leaf_name_remote(leaf, i);
			error = xrep_xattr_salvage_remote_attr(rx, ent, nameidx,
					buf_end, rentry, i, bp);
		}
		if (error)
			return error;
	}

	return 0;
}

/* Try to recover shortform attrs. */
STATIC int
xrep_xattr_recover_sf(
	struct xrep_xattr		*rx)
{
	struct xfs_scrub		*sc = rx->sc;
	struct xchk_xattr_buf		*ab = sc->buf;
	struct xfs_attr_sf_hdr		*hdr;
	struct xfs_attr_sf_entry	*sfe;
	struct xfs_attr_sf_entry	*next;
	struct xfs_ifork		*ifp;
	unsigned char			*end;
	int				i;
	int				error = 0;

	ifp = xfs_ifork_ptr(rx->sc->ip, XFS_ATTR_FORK);
	hdr = ifp->if_data;

	bitmap_zero(ab->usedmap, ifp->if_bytes);
	end = (unsigned char *)ifp->if_data + ifp->if_bytes;
	xchk_xattr_set_map(sc, ab->usedmap, 0, sizeof(*hdr));

	sfe = xfs_attr_sf_firstentry(hdr);
	if ((unsigned char *)sfe > end)
		return 0;

	for (i = 0; i < hdr->count; i++) {
		if (xchk_should_terminate(sc, &error))
			return error;

		next = xfs_attr_sf_nextentry(sfe);
		if ((unsigned char *)next > end)
			break;

		if (xchk_xattr_set_map(sc, ab->usedmap,
				(char *)sfe - (char *)hdr,
				sizeof(struct xfs_attr_sf_entry))) {
			/*
			 * No conflicts with the sf entry; let's save this
			 * attribute.
			 */
			error = xrep_xattr_salvage_sf_attr(rx, hdr, sfe);
			if (error)
				return error;
		}

		sfe = next;
	}

	return 0;
}

/*
 * Try to return a buffer of xattr data for a given physical extent.
 *
 * Because the buffer cache get function complains if it finds a buffer
 * matching the block number but not matching the length, we must be careful to
 * look for incore buffers (up to the maximum length of a remote value) that
 * could be hiding anywhere in the physical range.  If we find an incore
 * buffer, we can pass that to the caller.  Optionally, read a single block and
 * pass that back.
 *
 * Note the subtlety that remote attr value blocks for which there is no incore
 * buffer will be passed to the callback one block at a time.  These buffers
 * will not have any ops attached and must be staled to prevent aliasing with
 * multiblock buffers once we drop the ILOCK.
 */
STATIC int
xrep_xattr_find_buf(
	struct xfs_mount	*mp,
	xfs_fsblock_t		fsbno,
	xfs_extlen_t		max_len,
	bool			can_read,
	struct xfs_buf		**bpp)
{
	struct xrep_bufscan	scan = {
		.daddr		= XFS_FSB_TO_DADDR(mp, fsbno),
		.max_sectors	= xrep_bufscan_max_sectors(mp, max_len),
		.daddr_step	= XFS_FSB_TO_BB(mp, 1),
	};
	struct xfs_buf		*bp;

	while ((bp = xrep_bufscan_advance(mp, &scan)) != NULL) {
		*bpp = bp;
		return 0;
	}

	if (!can_read) {
		*bpp = NULL;
		return 0;
	}

	return xfs_buf_read(mp->m_ddev_targp, scan.daddr, XFS_FSB_TO_BB(mp, 1),
			XBF_TRYLOCK, bpp, NULL);
}

/*
 * Deal with a buffer that we found during our walk of the attr fork.
 *
 * Attribute leaf and node blocks are simple -- they're a single block, so we
 * can walk them one at a time and we never have to worry about discontiguous
 * multiblock buffers like we do for directories.
 *
 * Unfortunately, remote attr blocks add a lot of complexity here.  Each disk
 * block is totally self contained, in the sense that the v5 header provides no
 * indication that there could be more data in the next block.  The incore
 * buffers can span multiple blocks, though they never cross extent records.
 * However, they don't necessarily start or end on an extent record boundary.
 * Therefore, we need a special buffer find function to walk the buffer cache
 * for us.
 *
 * The caller must hold the ILOCK on the file being repaired.  We use
 * XBF_TRYLOCK here to skip any locked buffer on the assumption that we don't
 * own the block and don't want to hang the system on a potentially garbage
 * buffer.
 */
STATIC int
xrep_xattr_recover_block(
	struct xrep_xattr	*rx,
	xfs_dablk_t		dabno,
	xfs_fsblock_t		fsbno,
	xfs_extlen_t		max_len,
	xfs_extlen_t		*actual_len)
{
	struct xfs_da_blkinfo	*info;
	struct xfs_buf		*bp;
	int			error;

	error = xrep_xattr_find_buf(rx->sc->mp, fsbno, max_len, true, &bp);
	if (error)
		return error;
	info = bp->b_addr;
	*actual_len = XFS_BB_TO_FSB(rx->sc->mp, bp->b_length);

	trace_xrep_xattr_recover_leafblock(rx->sc->ip, dabno,
			be16_to_cpu(info->magic));

	/*
	 * If the buffer has the right magic number for an attr leaf block and
	 * passes a structure check (we don't care about checksums), salvage
	 * as much as we can from the block. */
	if (info->magic == cpu_to_be16(XFS_ATTR3_LEAF_MAGIC) &&
	    xrep_buf_verify_struct(bp, &xfs_attr3_leaf_buf_ops) &&
	    xfs_attr3_leaf_header_check(bp, rx->sc->ip->i_ino) == NULL)
		error = xrep_xattr_recover_leaf(rx, bp);

	/*
	 * If the buffer didn't already have buffer ops set, it was read in by
	 * the _find_buf function and could very well be /part/ of a multiblock
	 * remote block.  Mark it stale so that it doesn't hang around in
	 * memory to cause problems.
	 */
	if (bp->b_ops == NULL)
		xfs_buf_stale(bp);

	xfs_buf_relse(bp);
	return error;
}

/* Insert one xattr key/value. */
STATIC int
xrep_xattr_insert_rec(
	struct xrep_xattr		*rx,
	const struct xrep_xattr_key	*key)
{
	struct xfs_da_args		args = {
		.dp			= rx->sc->tempip,
		.attr_filter		= key->flags,
		.namelen		= key->namelen,
		.valuelen		= key->valuelen,
		.owner			= rx->sc->ip->i_ino,
		.geo			= rx->sc->mp->m_attr_geo,
		.whichfork		= XFS_ATTR_FORK,
		.op_flags		= XFS_DA_OP_OKNOENT,
	};
	struct xchk_xattr_buf		*ab = rx->sc->buf;
	int				error;

	/*
	 * Grab pointers to the scrub buffer so that we can use them to insert
	 * attrs into the temp file.
	 */
	args.name = ab->name;
	args.value = ab->value;

	/*
	 * The attribute name is stored near the end of the in-core buffer,
	 * though we reserve one more byte to ensure null termination.
	 */
	ab->name[XATTR_NAME_MAX] = 0;

	error = xfblob_load(rx->xattr_blobs, key->name_cookie, ab->name,
			key->namelen);
	if (error)
		return error;

	error = xfblob_free(rx->xattr_blobs, key->name_cookie);
	if (error)
		return error;

	error = xfblob_load(rx->xattr_blobs, key->value_cookie, args.value,
			key->valuelen);
	if (error)
		return error;

	error = xfblob_free(rx->xattr_blobs, key->value_cookie);
	if (error)
		return error;

	ab->name[key->namelen] = 0;

	if (key->flags & XFS_ATTR_PARENT) {
		trace_xrep_xattr_insert_pptr(rx->sc->tempip, key->flags,
				ab->name, key->namelen, ab->value,
				key->valuelen);
		args.op_flags |= XFS_DA_OP_LOGGED;
	} else {
		trace_xrep_xattr_insert_rec(rx->sc->tempip, key->flags,
				ab->name, key->namelen, key->valuelen);
	}

	/*
	 * xfs_attr_set creates and commits its own transaction.  If the attr
	 * already exists, we'll just drop it during the rebuild.
	 */
	xfs_attr_sethash(&args);
	error = xfs_attr_set(&args, XFS_ATTRUPDATE_CREATE, false);
	if (error == -EEXIST)
		error = 0;

	return error;
}

/*
 * Periodically flush salvaged attributes to the temporary file.  This is done
 * to reduce the memory requirements of the xattr rebuild because files can
 * contain millions of attributes.
 */
STATIC int
xrep_xattr_flush_stashed(
	struct xrep_xattr	*rx)
{
	xfarray_idx_t		array_cur;
	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 xattr salvaging to avoid livelocking if there
	 * are cycles in the xattr structures.  We hold ILOCK_EXCL on both
	 * the inode being repaired, though it is not ijoined to the scrub
	 * transaction.
	 *
	 * To constrain kernel memory use, we occasionally flush salvaged
	 * xattrs from the xfarray and xfblob structures into the temporary
	 * file in preparation for exchanging the xattr structures at the end.
	 * Updating the temporary file requires a transaction, so we commit the
	 * scrub transaction and drop the two ILOCKs so that xfs_attr_set can
	 * allocate whatever transaction it wants.
	 *
	 * We still hold IOLOCK_EXCL on the inode being repaired, which
	 * prevents anyone from modifying the damaged xattr data while we
	 * repair it.
	 */
	error = xrep_trans_commit(rx->sc);
	if (error)
		return error;
	xchk_iunlock(rx->sc, XFS_ILOCK_EXCL);

	/*
	 * Take the IOLOCK of the temporary file while we modify xattrs.  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(rx->sc);
	if (error)
		return error;

	/* Add all the salvaged attrs to the temporary file. */
	foreach_xfarray_idx(rx->xattr_records, array_cur) {
		struct xrep_xattr_key	key;

		error = xfarray_load(rx->xattr_records, array_cur, &key);
		if (error)
			return error;

		error = xrep_xattr_insert_rec(rx, &key);
		if (error)
			return error;
	}

	/* Empty out both arrays now that we've added the entries. */
	xfarray_truncate(rx->xattr_records);
	xfblob_truncate(rx->xattr_blobs);

	xrep_tempfile_iounlock(rx->sc);

	/* Recreate the salvage transaction and relock the inode. */
	error = xchk_trans_alloc(rx->sc, 0);
	if (error)
		return error;
	xchk_ilock(rx->sc, XFS_ILOCK_EXCL);
	return 0;
}

/* Decide if we've stashed too much xattr data in memory. */
static inline bool
xrep_xattr_want_flush_stashed(
	struct xrep_xattr	*rx)
{
	unsigned long long	bytes;

	if (!rx->can_flush)
		return false;

	bytes = xfarray_bytes(rx->xattr_records) +
		xfblob_bytes(rx->xattr_blobs);
	return bytes > XREP_XATTR_MAX_STASH_BYTES;
}

/*
 * Did we observe rename changing parent pointer xattrs while we were flushing
 * salvaged attrs?
 */
static inline bool
xrep_xattr_saw_pptr_conflict(
	struct xrep_xattr	*rx)
{
	bool			ret;

	ASSERT(rx->can_flush);

	if (!xfs_has_parent(rx->sc->mp))
		return false;

	xfs_assert_ilocked(rx->sc->ip, XFS_ILOCK_EXCL);

	mutex_lock(&rx->lock);
	ret = xfarray_bytes(rx->pptr_recs) > 0;
	mutex_unlock(&rx->lock);

	return ret;
}

/*
 * Reset the entire repair state back to initial conditions, now that we've
 * detected a parent pointer update to the attr structure while we were
 * flushing salvaged attrs.  See the locking notes in dir_repair.c for more
 * information on why this is all necessary.
 */
STATIC int
xrep_xattr_full_reset(
	struct xrep_xattr	*rx)
{
	struct xfs_scrub	*sc = rx->sc;
	struct xfs_attr_sf_hdr	*hdr;
	struct xfs_ifork	*ifp = &sc->tempip->i_af;
	int			error;

	trace_xrep_xattr_full_reset(sc->ip, sc->tempip);

	/* The temporary file's data fork had better not be in btree format. */
	if (sc->tempip->i_df.if_format == XFS_DINODE_FMT_BTREE) {
		ASSERT(0);
		return -EIO;
	}

	/*
	 * We begin in transaction context with sc->ip ILOCKed but not joined
	 * to the transaction.  To reset to the initial state, we must hold
	 * sc->ip's ILOCK to prevent rename from updating parent pointer
	 * information and the tempfile's ILOCK to clear its contents.
	 */
	xchk_iunlock(rx->sc, XFS_ILOCK_EXCL);
	xrep_tempfile_ilock_both(sc);
	xfs_trans_ijoin(sc->tp, sc->ip, 0);
	xfs_trans_ijoin(sc->tp, sc->tempip, 0);

	/*
	 * Free all the blocks of the attr fork of the temp file, and reset
	 * it back to local format.
	 */
	if (xfs_ifork_has_extents(&sc->tempip->i_af)) {
		error = xrep_reap_ifork(sc, sc->tempip, XFS_ATTR_FORK);
		if (error)
			return error;

		ASSERT(ifp->if_bytes == 0);
		ifp->if_format = XFS_DINODE_FMT_LOCAL;
		xfs_idata_realloc(sc->tempip, sizeof(*hdr), XFS_ATTR_FORK);
	}

	/* Reinitialize the attr fork to an empty shortform structure. */
	hdr = ifp->if_data;
	memset(hdr, 0, sizeof(*hdr));
	hdr->totsize = cpu_to_be16(sizeof(*hdr));
	xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE | XFS_ILOG_ADATA);

	/*
	 * Roll this transaction to commit our reset ondisk.  The tempfile
	 * should no longer be joined to the transaction, so we drop its ILOCK.
	 * This should leave us in transaction context with sc->ip ILOCKed but
	 * not joined to the transaction.
	 */
	error = xrep_roll_trans(sc);
	if (error)
		return error;
	xrep_tempfile_iunlock(sc);

	/*
	 * Erase any accumulated parent pointer updates now that we've erased
	 * the tempfile's attr fork.  We're resetting the entire repair state
	 * back to where we were initially, except now we won't flush salvaged
	 * xattrs until the very end.
	 */
	mutex_lock(&rx->lock);
	xfarray_truncate(rx->pptr_recs);
	xfblob_truncate(rx->pptr_names);
	mutex_unlock(&rx->lock);

	rx->can_flush = false;
	rx->attrs_found = 0;

	ASSERT(xfarray_bytes(rx->xattr_records) == 0);
	ASSERT(xfblob_bytes(rx->xattr_blobs) == 0);
	return 0;
}

/* Extract as many attribute keys and values as we can. */
STATIC int
xrep_xattr_recover(
	struct xrep_xattr	*rx)
{
	struct xfs_bmbt_irec	got;
	struct xfs_scrub	*sc = rx->sc;
	struct xfs_da_geometry	*geo = sc->mp->m_attr_geo;
	xfs_fileoff_t		offset;
	xfs_extlen_t		len;
	xfs_dablk_t		dabno;
	int			nmap;
	int			error;

restart:
	/*
	 * Iterate each xattr leaf block in the attr fork to scan them for any
	 * attributes that we might salvage.
	 */
	for (offset = 0;
	     offset < XFS_MAX_FILEOFF;
	     offset = got.br_startoff + got.br_blockcount) {
		nmap = 1;
		error = xfs_bmapi_read(sc->ip, offset, XFS_MAX_FILEOFF - offset,
				&got, &nmap, XFS_BMAPI_ATTRFORK);
		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 += len) {
			xfs_fileoff_t	curr_offset = dabno - got.br_startoff;
			xfs_extlen_t	maxlen;

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

			maxlen = min_t(xfs_filblks_t, INT_MAX,
					got.br_blockcount - curr_offset);
			error = xrep_xattr_recover_block(rx, dabno,
					curr_offset + got.br_startblock,
					maxlen, &len);
			if (error)
				return error;

			if (xrep_xattr_want_flush_stashed(rx)) {
				error = xrep_xattr_flush_stashed(rx);
				if (error)
					return error;

				if (xrep_xattr_saw_pptr_conflict(rx)) {
					error = xrep_xattr_full_reset(rx);
					if (error)
						return error;

					goto restart;
				}
			}
		}
	}

	return 0;
}

/*
 * Reset the extended attribute fork to a state where we can start re-adding
 * the salvaged attributes.
 */
STATIC int
xrep_xattr_fork_remove(
	struct xfs_scrub	*sc,
	struct xfs_inode	*ip)
{
	struct xfs_attr_sf_hdr	*hdr;
	struct xfs_ifork	*ifp = xfs_ifork_ptr(ip, XFS_ATTR_FORK);

	/*
	 * If the data fork is in btree format, we can't change di_forkoff
	 * because we could run afoul of the rule that the data fork isn't
	 * supposed to be in btree format if there's enough space in the fork
	 * that it could have used extents format.  Instead, reinitialize the
	 * attr fork to have a shortform structure with zero attributes.
	 */
	if (ip->i_df.if_format == XFS_DINODE_FMT_BTREE) {
		ifp->if_format = XFS_DINODE_FMT_LOCAL;
		hdr = xfs_idata_realloc(ip, (int)sizeof(*hdr) - ifp->if_bytes,
				XFS_ATTR_FORK);
		hdr->count = 0;
		hdr->totsize = cpu_to_be16(sizeof(*hdr));
		xfs_trans_log_inode(sc->tp, ip,
				XFS_ILOG_CORE | XFS_ILOG_ADATA);
		return 0;
	}

	/* If we still have attr fork extents, something's wrong. */
	if (ifp->if_nextents != 0) {
		struct xfs_iext_cursor	icur;
		struct xfs_bmbt_irec	irec;
		unsigned int		i = 0;

		xfs_emerg(sc->mp,
	"inode 0x%llx attr fork still has %llu attr extents, format %d?!",
				ip->i_ino, ifp->if_nextents, ifp->if_format);
		for_each_xfs_iext(ifp, &icur, &irec) {
			xfs_err(sc->mp,
	"[%u]: startoff %llu startblock %llu blockcount %llu state %u",
					i++, irec.br_startoff,
					irec.br_startblock, irec.br_blockcount,
					irec.br_state);
		}
		ASSERT(0);
		return -EFSCORRUPTED;
	}

	xfs_attr_fork_remove(ip, sc->tp);
	return 0;
}

/*
 * Free all the attribute fork blocks of the file being repaired and delete the
 * fork.  The caller must ILOCK the scrub file and join it to the transaction.
 * This function returns with the inode joined to a clean transaction.
 */
int
xrep_xattr_reset_fork(
	struct xfs_scrub	*sc)
{
	int			error;

	trace_xrep_xattr_reset_fork(sc->ip, sc->ip);

	/* Unmap all the attr blocks. */
	if (xfs_ifork_has_extents(&sc->ip->i_af)) {
		error = xrep_reap_ifork(sc, sc->ip, XFS_ATTR_FORK);
		if (error)
			return error;
	}

	error = xrep_xattr_fork_remove(sc, sc->ip);
	if (error)
		return error;

	return xfs_trans_roll_inode(&sc->tp, sc->ip);
}

/*
 * Free all the attribute fork blocks of the temporary file and delete the attr
 * fork.  The caller must ILOCK the tempfile and join it to the transaction.
 * This function returns with the inode joined to a clean scrub transaction.
 */
int
xrep_xattr_reset_tempfile_fork(
	struct xfs_scrub	*sc)
{
	int			error;

	trace_xrep_xattr_reset_fork(sc->ip, sc->tempip);

	/*
	 * Wipe out the attr fork of the temp file so that regular inode
	 * inactivation won't trip over the corrupt attr fork.
	 */
	if (xfs_ifork_has_extents(&sc->tempip->i_af)) {
		error = xrep_reap_ifork(sc, sc->tempip, XFS_ATTR_FORK);
		if (error)
			return error;
	}

	return xrep_xattr_fork_remove(sc, sc->tempip);
}

/*
 * Find all the extended attributes for this inode by scraping them out of the
 * attribute key blocks by hand, and flushing them into the temp file.
 * When we're done, free the staging memory before exchanging the xattr
 * structures to reduce memory usage.
 */
STATIC int
xrep_xattr_salvage_attributes(
	struct xrep_xattr	*rx)
{
	struct xfs_inode	*ip = rx->sc->ip;
	int			error;

	/* Short format xattrs are easy! */
	if (rx->sc->ip->i_af.if_format == XFS_DINODE_FMT_LOCAL) {
		error = xrep_xattr_recover_sf(rx);
		if (error)
			return error;

		return xrep_xattr_flush_stashed(rx);
	}

	/*
	 * For non-inline xattr structures, the salvage function scans the
	 * buffer cache looking for potential attr leaf blocks.  The scan
	 * requires the ability to lock any buffer found and runs independently
	 * of any transaction <-> buffer item <-> buffer linkage.  Therefore,
	 * roll the transaction to ensure there are no buffers joined.  We hold
	 * the ILOCK independently of the transaction.
	 */
	error = xfs_trans_roll(&rx->sc->tp);
	if (error)
		return error;

	error = xfs_iread_extents(rx->sc->tp, ip, XFS_ATTR_FORK);
	if (error)
		return error;

	error = xrep_xattr_recover(rx);
	if (error)
		return error;

	return xrep_xattr_flush_stashed(rx);
}

/*
 * Add this stashed incore parent pointer to the temporary file.  The caller
 * must hold the tempdir's IOLOCK, must not hold any ILOCKs, and must not be in
 * transaction context.
 */
STATIC int
xrep_xattr_replay_pptr_update(
	struct xrep_xattr		*rx,
	const struct xfs_name		*xname,
	struct xrep_xattr_pptr		*pptr)
{
	struct xfs_scrub		*sc = rx->sc;
	int				error;

	switch (pptr->action) {
	case XREP_XATTR_PPTR_ADD:
		/* Create parent pointer. */
		trace_xrep_xattr_replay_parentadd(sc->tempip, xname,
				&pptr->pptr_rec);

		error = xfs_parent_set(sc->tempip, sc->ip->i_ino, xname,
				&pptr->pptr_rec, &rx->pptr_args);
		ASSERT(error != -EEXIST);
		return error;
	case XREP_XATTR_PPTR_REMOVE:
		/* Remove parent pointer. */
		trace_xrep_xattr_replay_parentremove(sc->tempip, xname,
				&pptr->pptr_rec);

		error = xfs_parent_unset(sc->tempip, sc->ip->i_ino, xname,
				&pptr->pptr_rec, &rx->pptr_args);
		ASSERT(error != -ENOATTR);
		return error;
	}

	ASSERT(0);
	return -EIO;
}

/*
 * Flush stashed parent pointer updates that have been recorded by the scanner.
 * This is done to reduce the memory requirements of the xattr rebuild, since
 * files can have a lot of hardlinks and the fs can be busy.
 *
 * Caller must not hold transactions or ILOCKs.  Caller must hold the tempfile
 * IOLOCK.
 */
STATIC int
xrep_xattr_replay_pptr_updates(
	struct xrep_xattr	*rx)
{
	xfarray_idx_t		array_cur;
	int			error;

	mutex_lock(&rx->lock);
	foreach_xfarray_idx(rx->pptr_recs, array_cur) {
		struct xrep_xattr_pptr	pptr;

		error = xfarray_load(rx->pptr_recs, array_cur, &pptr);
		if (error)
			goto out_unlock;

		error = xfblob_loadname(rx->pptr_names, pptr.name_cookie,
				&rx->xname, pptr.namelen);
		if (error)
			goto out_unlock;
		mutex_unlock(&rx->lock);

		error = xrep_xattr_replay_pptr_update(rx, &rx->xname, &pptr);
		if (error)
			return error;

		mutex_lock(&rx->lock);
	}

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

/*
 * Remember that we want to create a parent pointer in the tempfile.  These
 * stashed actions will be replayed later.
 */
STATIC int
xrep_xattr_stash_parentadd(
	struct xrep_xattr	*rx,
	const struct xfs_name	*name,
	const struct xfs_inode	*dp)
{
	struct xrep_xattr_pptr	pptr = {
		.action		= XREP_XATTR_PPTR_ADD,
		.namelen	= name->len,
	};
	int			error;

	trace_xrep_xattr_stash_parentadd(rx->sc->tempip, dp, name);

	xfs_inode_to_parent_rec(&pptr.pptr_rec, dp);
	error = xfblob_storename(rx->pptr_names, &pptr.name_cookie, name);
	if (error)
		return error;

	return xfarray_append(rx->pptr_recs, &pptr);
}

/*
 * Remember that we want to remove a parent pointer from the tempfile.  These
 * stashed actions will be replayed later.
 */
STATIC int
xrep_xattr_stash_parentremove(
	struct xrep_xattr	*rx,
	const struct xfs_name	*name,
	const struct xfs_inode	*dp)
{
	struct xrep_xattr_pptr	pptr = {
		.action		= XREP_XATTR_PPTR_REMOVE,
		.namelen	= name->len,
	};
	int			error;

	trace_xrep_xattr_stash_parentremove(rx->sc->tempip, dp, name);

	xfs_inode_to_parent_rec(&pptr.pptr_rec, dp);
	error = xfblob_storename(rx->pptr_names, &pptr.name_cookie, name);
	if (error)
		return error;

	return xfarray_append(rx->pptr_recs, &pptr);
}

/*
 * Capture dirent updates being made by other threads.  We will have to replay
 * the parent pointer updates before exchanging attr forks.
 */
STATIC int
xrep_xattr_live_dirent_update(
	struct notifier_block		*nb,
	unsigned long			action,
	void				*data)
{
	struct xfs_dir_update_params	*p = data;
	struct xrep_xattr		*rx;
	struct xfs_scrub		*sc;
	int				error;

	rx = container_of(nb, struct xrep_xattr, dhook.dirent_hook.nb);
	sc = rx->sc;

	/*
	 * This thread updated a dirent that points to the file that we're
	 * repairing, so stash the update for replay against the temporary
	 * file.
	 */
	if (p->ip->i_ino != sc->ip->i_ino)
		return NOTIFY_DONE;

	mutex_lock(&rx->lock);
	if (p->delta > 0)
		error = xrep_xattr_stash_parentadd(rx, p->name, p->dp);
	else
		error = xrep_xattr_stash_parentremove(rx, p->name, p->dp);
	if (error)
		rx->live_update_aborted = true;
	mutex_unlock(&rx->lock);
	return NOTIFY_DONE;
}

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

	/*
	 * If the tempfile's attributes are 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_attr_geo,
			.whichfork	= XFS_ATTR_FORK,
			.trans		= sc->tp,
			.total		= 1,
			.owner		= sc->ip->i_ino,
		};

		error = xfs_attr_shortform_to_leaf(&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 attribute 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_ATTR_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_ADATA);
	}

	return 0;
}

/* Exchange the temporary file's attribute fork with the one being repaired. */
int
xrep_xattr_swap(
	struct xfs_scrub	*sc,
	struct xrep_tempexch	*tx)
{
	bool			ip_local, temp_local;
	int			error = 0;

	ip_local = sc->ip->i_af.if_format == XFS_DINODE_FMT_LOCAL;
	temp_local = sc->tempip->i_af.if_format == XFS_DINODE_FMT_LOCAL;

	/*
	 * If the both files have a local format attr fork and the rebuilt
	 * xattr data would fit in the repaired file's attr fork, just copy
	 * the contents from the tempfile and declare ourselves done.
	 */
	if (ip_local && temp_local) {
		int	forkoff;
		int	newsize;

		newsize = xfs_attr_sf_totsize(sc->tempip);
		forkoff = xfs_attr_shortform_bytesfit(sc->ip, newsize);
		if (forkoff > 0) {
			sc->ip->i_forkoff = forkoff;
			xrep_tempfile_copyout_local(sc, XFS_ATTR_FORK);
			return 0;
		}
	}

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

	return xrep_tempexch_contents(sc, tx);
}

/*
 * Finish replaying stashed parent pointer updates, allocate a transaction for
 * exchanging extent mappings, and take the ILOCKs of both files before we
 * commit the new extended attribute structure.
 */
STATIC int
xrep_xattr_finalize_tempfile(
	struct xrep_xattr	*rx)
{
	struct xfs_scrub	*sc = rx->sc;
	int			error;

	if (!xfs_has_parent(sc->mp))
		return xrep_tempexch_trans_alloc(sc, XFS_ATTR_FORK, &rx->tx);

	/*
	 * Repair relies on the ILOCK to quiesce all possible xattr updates.
	 * Replay all queued parent pointer updates into the tempfile before
	 * exchanging the contents, even if that means dropping the ILOCKs and
	 * the transaction.
	 */
	do {
		error = xrep_xattr_replay_pptr_updates(rx);
		if (error)
			return error;

		error = xrep_tempexch_trans_alloc(sc, XFS_ATTR_FORK, &rx->tx);
		if (error)
			return error;

		if (xfarray_length(rx->pptr_recs) == 0)
			break;

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

/*
 * Exchange the new extended attribute data (which we created in the tempfile)
 * with the file being repaired.
 */
STATIC int
xrep_xattr_rebuild_tree(
	struct xrep_xattr	*rx)
{
	struct xfs_scrub	*sc = rx->sc;
	int			error;

	/*
	 * If we didn't find any attributes to salvage, repair the file by
	 * zapping its attr fork.
	 */
	if (rx->attrs_found == 0) {
		xfs_trans_ijoin(sc->tp, sc->ip, 0);
		error = xrep_xattr_reset_fork(sc);
		if (error)
			return error;

		goto forget_acls;
	}

	trace_xrep_xattr_rebuild_tree(sc->ip, sc->tempip);

	/*
	 * Commit the repair transaction and drop the ILOCKs so that we can use
	 * the atomic file content exchange helper functions to compute the
	 * correct resource reservations.
	 *
	 * We still hold IOLOCK_EXCL (aka i_rwsem) which will prevent xattr
	 * modifications, but there's nothing to prevent userspace from reading
	 * the attributes until we're ready for the exchange operation.  Reads
	 * will return -EIO without shutting down the fs, so we're ok with
	 * that.
	 */
	error = xrep_trans_commit(sc);
	if (error)
		return error;

	xchk_iunlock(sc, XFS_ILOCK_EXCL);

	/*
	 * Take the IOLOCK on the temporary file so that we can run xattr
	 * 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(rx->sc);
	if (error)
		return error;

	/*
	 * Allocate transaction, lock inodes, and make sure that we've replayed
	 * all the stashed parent pointer updates to the temp file.  After this
	 * point, we're ready to exchange attr fork mappings.
	 */
	error = xrep_xattr_finalize_tempfile(rx);
	if (error)
		return error;

	/*
	 * Exchange the blocks mapped by the tempfile's attr fork with the file
	 * being repaired.  The old attr blocks will then be attached to the
	 * tempfile, so reap its attr fork.
	 */
	error = xrep_xattr_swap(sc, &rx->tx);
	if (error)
		return error;

	error = xrep_xattr_reset_tempfile_fork(sc);
	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 file.
	 */
	error = xfs_trans_roll(&sc->tp);
	if (error)
		return error;

	xrep_tempfile_iunlock(sc);
	xrep_tempfile_iounlock(sc);

forget_acls:
	/* Invalidate cached ACLs now that we've reloaded all the xattrs. */
	xfs_forget_acl(VFS_I(sc->ip), SGI_ACL_FILE);
	xfs_forget_acl(VFS_I(sc->ip), SGI_ACL_DEFAULT);
	return 0;
}

/* Tear down all the incore scan stuff we created. */
STATIC void
xrep_xattr_teardown(
	struct xrep_xattr	*rx)
{
	if (xfs_has_parent(rx->sc->mp))
		xfs_dir_hook_del(rx->sc->mp, &rx->dhook);
	if (rx->pptr_names)
		xfblob_destroy(rx->pptr_names);
	if (rx->pptr_recs)
		xfarray_destroy(rx->pptr_recs);
	xfblob_destroy(rx->xattr_blobs);
	xfarray_destroy(rx->xattr_records);
	mutex_destroy(&rx->lock);
	kfree(rx);
}

/* Set up the filesystem scan so we can regenerate extended attributes. */
STATIC int
xrep_xattr_setup_scan(
	struct xfs_scrub	*sc,
	struct xrep_xattr	**rxp)
{
	struct xrep_xattr	*rx;
	char			*descr;
	int			max_len;
	int			error;

	rx = kzalloc(sizeof(struct xrep_xattr), XCHK_GFP_FLAGS);
	if (!rx)
		return -ENOMEM;
	rx->sc = sc;
	rx->can_flush = true;
	rx->xname.name = rx->namebuf;

	mutex_init(&rx->lock);

	/*
	 * Allocate enough memory to handle loading local attr values from the
	 * xfblob data while flushing stashed attrs to the temporary file.
	 * We only realloc the buffer when salvaging remote attr values.
	 */
	max_len = xfs_attr_leaf_entsize_local_max(sc->mp->m_attr_geo->blksize);
	error = xchk_setup_xattr_buf(rx->sc, max_len);
	if (error == -ENOMEM)
		error = -EDEADLOCK;
	if (error)
		goto out_rx;

	/* Set up some staging for salvaged attribute keys and values */
	descr = xchk_xfile_ino_descr(sc, "xattr keys");
	error = xfarray_create(descr, 0, sizeof(struct xrep_xattr_key),
			&rx->xattr_records);
	kfree(descr);
	if (error)
		goto out_rx;

	descr = xchk_xfile_ino_descr(sc, "xattr names");
	error = xfblob_create(descr, &rx->xattr_blobs);
	kfree(descr);
	if (error)
		goto out_keys;

	if (xfs_has_parent(sc->mp)) {
		ASSERT(sc->flags & XCHK_FSGATES_DIRENTS);

		descr = xchk_xfile_ino_descr(sc,
				"xattr retained parent pointer entries");
		error = xfarray_create(descr, 0,
				sizeof(struct xrep_xattr_pptr),
				&rx->pptr_recs);
		kfree(descr);
		if (error)
			goto out_values;

		descr = xchk_xfile_ino_descr(sc,
				"xattr retained parent pointer names");
		error = xfblob_create(descr, &rx->pptr_names);
		kfree(descr);
		if (error)
			goto out_pprecs;

		xfs_dir_hook_setup(&rx->dhook, xrep_xattr_live_dirent_update);
		error = xfs_dir_hook_add(sc->mp, &rx->dhook);
		if (error)
			goto out_ppnames;
	}

	*rxp = rx;
	return 0;
out_ppnames:
	xfblob_destroy(rx->pptr_names);
out_pprecs:
	xfarray_destroy(rx->pptr_recs);
out_values:
	xfblob_destroy(rx->xattr_blobs);
out_keys:
	xfarray_destroy(rx->xattr_records);
out_rx:
	mutex_destroy(&rx->lock);
	kfree(rx);
	return error;
}

/*
 * Repair the extended attribute metadata.
 *
 * XXX: Remote attribute value buffers encompass the entire (up to 64k) buffer.
 * The buffer cache in XFS can't handle aliased multiblock buffers, so this
 * might misbehave if the attr fork is crosslinked with other filesystem
 * metadata.
 */
int
xrep_xattr(
	struct xfs_scrub	*sc)
{
	struct xrep_xattr	*rx = NULL;
	int			error;

	if (!xfs_inode_hasattr(sc->ip))
		return -ENOENT;

	/* The rmapbt is required to reap the old attr 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_xattr_setup_scan(sc, &rx);
	if (error)
		return error;

	ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);

	error = xrep_xattr_salvage_attributes(rx);
	if (error)
		goto out_scan;

	if (rx->live_update_aborted) {
		error = -EIO;
		goto out_scan;
	}

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

	error = xrep_xattr_rebuild_tree(rx);
	if (error)
		goto out_scan;

out_scan:
	xrep_xattr_teardown(rx);
	return error;
}