Commit 0d2c636e authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

xfs: repair metadata directory file path connectivity



Fix disconnected or incorrect metadata directory paths.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent 87b7c205
Loading
Loading
Loading
Loading
+349 −2
Original line number Diff line number Diff line
@@ -16,10 +16,15 @@
#include "xfs_quota.h"
#include "xfs_qm.h"
#include "xfs_dir2.h"
#include "xfs_parent.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
#include "xfs_attr.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/readdir.h"
#include "scrub/repair.h"

/*
 * Metadata Directory Tree Paths
@@ -38,15 +43,28 @@ struct xchk_metapath {
	/* Name for lookup */
	struct xfs_name			xname;

	/* Path for this metadata file and the parent directory */
	/* Directory update for repairs */
	struct xfs_dir_update		du;

	/* Path down to this metadata file from the parent directory */
	const char			*path;
	const char			*parent_path;

	/* Directory parent of the metadata file. */
	struct xfs_inode		*dp;

	/* Locks held on dp */
	unsigned int			dp_ilock_flags;

	/* Transaction block reservations */
	unsigned int			link_resblks;
	unsigned int			unlink_resblks;

	/* Parent pointer updates */
	struct xfs_parent_args		link_ppargs;
	struct xfs_parent_args		unlink_ppargs;

	/* Scratchpads for removing links */
	struct xfs_da_args		pptr_args;
};

/* Release resources tracked in the buffer. */
@@ -172,3 +190,332 @@ xchk_metapath(
	xchk_trans_cancel(sc);
	return error;
}

#ifdef CONFIG_XFS_ONLINE_REPAIR
/* Create the dirent represented by the final component of the path. */
STATIC int
xrep_metapath_link(
	struct xchk_metapath	*mpath)
{
	struct xfs_scrub	*sc = mpath->sc;

	mpath->du.dp = mpath->dp;
	mpath->du.name = &mpath->xname;
	mpath->du.ip = sc->ip;

	if (xfs_has_parent(sc->mp))
		mpath->du.ppargs = &mpath->link_ppargs;
	else
		mpath->du.ppargs = NULL;

	trace_xrep_metapath_link(sc, mpath->path, mpath->dp, sc->ip->i_ino);

	return xfs_dir_add_child(sc->tp, mpath->link_resblks, &mpath->du);
}

/* Remove the dirent at the final component of the path. */
STATIC int
xrep_metapath_unlink(
	struct xchk_metapath	*mpath,
	xfs_ino_t		ino,
	struct xfs_inode	*ip)
{
	struct xfs_parent_rec	rec;
	struct xfs_scrub	*sc = mpath->sc;
	struct xfs_mount	*mp = sc->mp;
	int			error;

	trace_xrep_metapath_unlink(sc, mpath->path, mpath->dp, ino);

	if (!ip) {
		/* The child inode isn't allocated.  Junk the dirent. */
		xfs_trans_log_inode(sc->tp, mpath->dp, XFS_ILOG_CORE);
		return xfs_dir_removename(sc->tp, mpath->dp, &mpath->xname,
				ino, mpath->unlink_resblks);
	}

	mpath->du.dp = mpath->dp;
	mpath->du.name = &mpath->xname;
	mpath->du.ip = ip;
	mpath->du.ppargs = NULL;

	/* Figure out if we're removing a parent pointer too. */
	if (xfs_has_parent(mp)) {
		xfs_inode_to_parent_rec(&rec, ip);
		error = xfs_parent_lookup(sc->tp, ip, &mpath->xname, &rec,
				&mpath->pptr_args);
		switch (error) {
		case -ENOATTR:
			break;
		case 0:
			mpath->du.ppargs = &mpath->unlink_ppargs;
			break;
		default:
			return error;
		}
	}

	return xfs_dir_remove_child(sc->tp, mpath->unlink_resblks, &mpath->du);
}

/*
 * Try to create a dirent in @mpath->dp with the name @mpath->xname that points
 * to @sc->ip.  Returns:
 *
 * -EEXIST and an @alleged_child if the dirent that points to the wrong inode;
 * 0 if there is now a dirent pointing to @sc->ip; or
 * A negative errno on error.
 */
STATIC int
xrep_metapath_try_link(
	struct xchk_metapath	*mpath,
	xfs_ino_t		*alleged_child)
{
	struct xfs_scrub	*sc = mpath->sc;
	xfs_ino_t		ino;
	int			error;

	/* Allocate transaction, lock inodes, join to transaction. */
	error = xchk_trans_alloc(sc, mpath->link_resblks);
	if (error)
		return error;

	error = xchk_metapath_ilock_both(mpath);
	if (error) {
		xchk_trans_cancel(sc);
		return error;
	}
	xfs_trans_ijoin(sc->tp, mpath->dp, 0);
	xfs_trans_ijoin(sc->tp, sc->ip, 0);

	error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
	trace_xrep_metapath_lookup(sc, mpath->path, mpath->dp, ino);
	if (error == -ENOENT) {
		/*
		 * There is no dirent in the directory.  Create an entry
		 * pointing to @sc->ip.
		 */
		error = xrep_metapath_link(mpath);
		if (error)
			goto out_cancel;

		error = xrep_trans_commit(sc);
		xchk_metapath_iunlock(mpath);
		return error;
	}
	if (error)
		goto out_cancel;

	if (ino == sc->ip->i_ino) {
		/* The dirent already points to @sc->ip; we're done. */
		error = 0;
		goto out_cancel;
	}

	/*
	 * The dirent points elsewhere; pass that back so that the caller
	 * can try to remove the dirent.
	 */
	*alleged_child = ino;
	error = -EEXIST;

out_cancel:
	xchk_trans_cancel(sc);
	xchk_metapath_iunlock(mpath);
	return error;
}

/*
 * Take the ILOCK on the metadata directory parent and a bad child, if one is
 * supplied.  We do not know that the metadata directory is not corrupt, so we
 * lock the parent and try to lock the child.  Returns 0 if successful, or
 * -EINTR to abort the repair.  The lock state of @dp is not recorded in @mpath.
 */
STATIC int
xchk_metapath_ilock_parent_and_child(
	struct xchk_metapath	*mpath,
	struct xfs_inode	*ip)
{
	struct xfs_scrub	*sc = mpath->sc;
	int			error = 0;

	while (true) {
		xfs_ilock(mpath->dp, XFS_ILOCK_EXCL);
		if (!ip || xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))
			return 0;
		xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);

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

		delay(1);
	}

	ASSERT(0);
	return -EINTR;
}

/*
 * Try to remove a dirent in @mpath->dp with the name @mpath->xname that points
 * to @alleged_child.  Returns:
 *
 * 0 if there is no longer a dirent;
 * -EEXIST if the dirent points to @sc->ip;
 * -EAGAIN and an updated @alleged_child if the dirent points elsewhere; or
 * A negative errno for any other error.
 */
STATIC int
xrep_metapath_try_unlink(
	struct xchk_metapath	*mpath,
	xfs_ino_t		*alleged_child)
{
	struct xfs_scrub	*sc = mpath->sc;
	struct xfs_inode	*ip = NULL;
	xfs_ino_t		ino;
	int			error;

	ASSERT(*alleged_child != sc->ip->i_ino);

	trace_xrep_metapath_try_unlink(sc, mpath->path, mpath->dp,
			*alleged_child);

	/*
	 * Allocate transaction, grab the alleged child inode, lock inodes,
	 * join to transaction.
	 */
	error = xchk_trans_alloc(sc, mpath->unlink_resblks);
	if (error)
		return error;

	error = xchk_iget(sc, *alleged_child, &ip);
	if (error == -EINVAL || error == -ENOENT) {
		/* inode number is bogus, junk the dirent */
		error = 0;
	}
	if (error) {
		xchk_trans_cancel(sc);
		return error;
	}

	error = xchk_metapath_ilock_parent_and_child(mpath, ip);
	if (error) {
		xchk_trans_cancel(sc);
		return error;
	}
	xfs_trans_ijoin(sc->tp, mpath->dp, 0);
	if (ip)
		xfs_trans_ijoin(sc->tp, ip, 0);

	error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
	trace_xrep_metapath_lookup(sc, mpath->path, mpath->dp, ino);
	if (error == -ENOENT) {
		/*
		 * There is no dirent in the directory anymore.  We're ready to
		 * try the link operation again.
		 */
		error = 0;
		goto out_cancel;
	}
	if (error)
		goto out_cancel;

	if (ino == sc->ip->i_ino) {
		/* The dirent already points to @sc->ip; we're done. */
		error = -EEXIST;
		goto out_cancel;
	}

	/*
	 * The dirent does not point to the alleged child.  Update the caller
	 * and signal that we want to be called again.
	 */
	if (ino != *alleged_child) {
		*alleged_child = ino;
		error = -EAGAIN;
		goto out_cancel;
	}

	/* Remove the link to the child. */
	error = xrep_metapath_unlink(mpath, ino, ip);
	if (error)
		goto out_cancel;

	error = xrep_trans_commit(sc);
	goto out_unlock;

out_cancel:
	xchk_trans_cancel(sc);
out_unlock:
	xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
	if (ip) {
		xfs_iunlock(ip, XFS_ILOCK_EXCL);
		xchk_irele(sc, ip);
	}
	return error;
}

/*
 * Make sure the metadata directory path points to the child being examined.
 *
 * Repair needs to be able to create a directory structure, create its own
 * transactions, and take ILOCKs.  This function /must/ be called after all
 * other repairs have completed.
 */
int
xrep_metapath(
	struct xfs_scrub	*sc)
{
	struct xchk_metapath	*mpath = sc->buf;
	struct xfs_mount	*mp = sc->mp;
	int			error = 0;

	/* Just probing, nothing to repair. */
	if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
		return 0;

	/* Parent required to do anything else. */
	if (mpath->dp == NULL)
		return -EFSCORRUPTED;

	/*
	 * Make sure the child file actually has an attr fork to receive a new
	 * parent pointer if the fs has parent pointers.
	 */
	if (xfs_has_parent(mp)) {
		error = xfs_attr_add_fork(sc->ip,
				sizeof(struct xfs_attr_sf_hdr), 1);
		if (error)
			return error;
	}

	/* Compute block reservation required to unlink and link a file. */
	mpath->unlink_resblks = xfs_remove_space_res(mp, MAXNAMELEN);
	mpath->link_resblks = xfs_link_space_res(mp, MAXNAMELEN);

	do {
		xfs_ino_t	alleged_child;

		/* Re-establish the link, or tell us which inode to remove. */
		error = xrep_metapath_try_link(mpath, &alleged_child);
		if (!error)
			return 0;
		if (error != -EEXIST)
			return error;

		/*
		 * Remove an incorrect link to an alleged child, or tell us
		 * which inode to remove.
		 */
		do {
			error = xrep_metapath_try_unlink(mpath, &alleged_child);
		} while (error == -EAGAIN);
		if (error == -EEXIST) {
			/* Link established; we're done. */
			error = 0;
			break;
		}
	} while (!error);

	return error;
}
#endif /* CONFIG_XFS_ONLINE_REPAIR */
+3 −0
Original line number Diff line number Diff line
@@ -134,6 +134,7 @@ int xrep_directory(struct xfs_scrub *sc);
int xrep_parent(struct xfs_scrub *sc);
int xrep_symlink(struct xfs_scrub *sc);
int xrep_dirtree(struct xfs_scrub *sc);
int xrep_metapath(struct xfs_scrub *sc);

#ifdef CONFIG_XFS_RT
int xrep_rtbitmap(struct xfs_scrub *sc);
@@ -208,6 +209,7 @@ xrep_setup_nothing(
#define xrep_setup_parent		xrep_setup_nothing
#define xrep_setup_nlinks		xrep_setup_nothing
#define xrep_setup_dirtree		xrep_setup_nothing
#define xrep_setup_metapath		xrep_setup_nothing

#define xrep_setup_inode(sc, imap)	((void)0)

@@ -243,6 +245,7 @@ static inline int xrep_setup_symlink(struct xfs_scrub *sc, unsigned int *x)
#define xrep_parent			xrep_notsupported
#define xrep_symlink			xrep_notsupported
#define xrep_dirtree			xrep_notsupported
#define xrep_metapath			xrep_notsupported

#endif /* CONFIG_XFS_ONLINE_REPAIR */

+1 −1
Original line number Diff line number Diff line
@@ -447,7 +447,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
		.setup	= xchk_setup_metapath,
		.scrub	= xchk_metapath,
		.has	= xfs_has_metadir,
		.repair	= xrep_notsupported,
		.repair	= xrep_metapath,
	},
};

+5 −0
Original line number Diff line number Diff line
@@ -3598,6 +3598,11 @@ DEFINE_XCHK_DIRTREE_EVENT(xrep_dirtree_delete_path);
DEFINE_XCHK_DIRTREE_EVENT(xrep_dirtree_create_adoption);
DEFINE_XCHK_DIRTREE_EVALUATE_EVENT(xrep_dirtree_decided_fate);

DEFINE_XCHK_METAPATH_EVENT(xrep_metapath_lookup);
DEFINE_XCHK_METAPATH_EVENT(xrep_metapath_try_unlink);
DEFINE_XCHK_METAPATH_EVENT(xrep_metapath_unlink);
DEFINE_XCHK_METAPATH_EVENT(xrep_metapath_link);

#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */

#endif /* _TRACE_XFS_SCRUB_TRACE_H */