Commit 77ede5f4 authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

xfs: walk directory parent pointers to determine backref count



If the filesystem has parent pointers enabled, walk the parent pointers
of subdirectories to determine the true backref count.  In theory each
subdir should have a single parent reachable via dotdot, but in the case
of (corrupt) subdirs with multiple parents, we need to keep the link
counts high enough that the directory loop detector will be able to
correct the multiple parents problems.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent 8ad34530
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -212,6 +212,7 @@ static inline bool xchk_skip_xref(struct xfs_scrub_metadata *sm)
}

bool xchk_dir_looks_zapped(struct xfs_inode *dp);
bool xchk_pptr_looks_zapped(struct xfs_inode *ip);

#ifdef CONFIG_XFS_ONLINE_REPAIR
/* Decide if a repair is required. */
+84 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_ag.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/repair.h"
@@ -29,6 +30,7 @@
#include "scrub/trace.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
#include "scrub/listxattr.h"

/*
 * Live Inode Link Count Checking
@@ -272,12 +274,17 @@ xchk_nlinks_collect_dirent(
	 * number of parents of the root directory.
	 *
	 * Otherwise, increment the number of backrefs pointing back to ino.
	 *
	 * If the filesystem has parent pointers, we walk the pptrs to
	 * determine the backref count.
	 */
	if (dotdot) {
		if (dp == sc->mp->m_rootip)
			error = xchk_nlinks_update_incore(xnc, ino, 1, 0, 0);
		else
		else if (!xfs_has_parent(sc->mp))
			error = xchk_nlinks_update_incore(xnc, ino, 0, 1, 0);
		else
			error = 0;
		if (error)
			goto out_unlock;
	}
@@ -314,6 +321,61 @@ xchk_nlinks_collect_dirent(
	return error;
}

/* Bump the backref count for the inode referenced by this parent pointer. */
STATIC int
xchk_nlinks_collect_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,
	};
	struct xchk_nlink_ctrs		*xnc = priv;
	const struct xfs_parent_rec	*pptr_rec = value;
	xfs_ino_t			parent_ino;
	int				error;

	/* Update the shadow link counts if we haven't already failed. */

	if (xchk_iscan_aborted(&xnc->collect_iscan)) {
		error = -ECANCELED;
		goto out_incomplete;
	}

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

	error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
			valuelen, &parent_ino, NULL);
	if (error)
		return error;

	trace_xchk_nlinks_collect_pptr(sc->mp, ip, &xname, pptr_rec);

	mutex_lock(&xnc->lock);

	error = xchk_nlinks_update_incore(xnc, parent_ino, 0, 1, 0);
	if (error)
		goto out_unlock;

	mutex_unlock(&xnc->lock);
	return 0;

out_unlock:
	mutex_unlock(&xnc->lock);
	xchk_iscan_abort(&xnc->collect_iscan);
out_incomplete:
	xchk_set_incomplete(sc);
	return error;
}

/* Walk a directory to bump the observed link counts of the children. */
STATIC int
xchk_nlinks_collect_dir(
@@ -360,6 +422,27 @@ xchk_nlinks_collect_dir(
	if (error)
		goto out_abort;

	/* Walk the parent pointers to get real backref counts. */
	if (xfs_has_parent(sc->mp)) {
		/*
		 * 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(dp)) {
			error = -EBUSY;
			goto out_unlock;
		}

		error = xchk_xattr_walk(sc, dp, xchk_nlinks_collect_pptr, xnc);
		if (error == -ECANCELED) {
			error = 0;
			goto out_unlock;
		}
		if (error)
			goto out_abort;
	}

	xchk_iscan_mark_visited(&xnc->collect_iscan, dp);
	goto out_unlock;

+2 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@
#include "xfs_ialloc.h"
#include "xfs_sb.h"
#include "xfs_ag.h"
#include "xfs_dir2.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/repair.h"
+61 −0
Original line number Diff line number Diff line
@@ -873,3 +873,64 @@ xchk_parent(

	return error;
}

/*
 * Decide if this file's extended attributes (and therefore its parent
 * pointers) have been zapped to satisfy the inode and ifork verifiers.
 * Checking and repairing should be postponed until the extended attribute
 * structure is fixed.
 */
bool
xchk_pptr_looks_zapped(
	struct xfs_inode	*ip)
{
	struct xfs_mount	*mp = ip->i_mount;
	struct inode		*inode = VFS_I(ip);

	ASSERT(xfs_has_parent(mp));

	/*
	 * Temporary files that cannot be linked into the directory tree do not
	 * have attr forks because they cannot ever have parents.
	 */
	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
		return false;

	/*
	 * Directory tree roots do not have parents, so the expected outcome
	 * of a parent pointer scan is always the empty set.  It's safe to scan
	 * them even if the attr fork was zapped.
	 */
	if (ip == mp->m_rootip)
		return false;

	/*
	 * Metadata inodes are all rooted in the superblock and do not have
	 * any parents.  Hence the attr fork will not be initialized, but
	 * there are no parent pointers that might have been zapped.
	 */
	if (xfs_is_metadata_inode(ip))
		return false;

	/*
	 * Linked and linkable non-rootdir files should always have an
	 * attribute fork because that is where parent pointers are
	 * stored.  If the fork is absent, something is amiss.
	 */
	if (!xfs_inode_has_attr_fork(ip))
		return true;

	/* Repair zapped this file's attr fork a short time ago */
	if (xfs_ifork_zapped(ip, XFS_ATTR_FORK))
		return true;

	/*
	 * If the dinode repair found a bad attr fork, it will reset the fork
	 * to extents format with zero records and wait for the bmapbta
	 * scrubber to reconstruct the block mappings.  The extended attribute
	 * structure always contain some content when parent pointers are
	 * enabled, so this is a clear sign of a zapped attr fork.
	 */
	return ip->i_af.if_format == XFS_DINODE_FMT_EXTENTS &&
	       ip->i_af.if_nextents == 0;
}
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include "xfs_da_format.h"
#include "xfs_dir2.h"
#include "xfs_rmap.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
Loading