Commit 34c9382c authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

xfs: ask the dentry cache if it knows the parent of a directory



It's possible that the dentry cache can tell us the parent of a
directory.  Therefore, when repairing directory dot dot entries, query
the dcache as a last resort before scanning the entire filesystem.

A reviewer asks:

"How high is the chance that we actually have a valid dcache entry for a
file in a corrupted directory?"

There's a decent chance of this actually working.  Say you have a
1000-block directory foo, and block 980 gets corrupted.  Let's further
suppose that block 0 has a correct entry for ".." and "bar".  If someone
accesses /mnt/foo/bar, that will cause the dcache to create a dentry
from /mnt to /mnt/foo whose d_parent points back to /mnt.  If you then
want to rebuild the directory, XFS can obtain the parent from the dcache
without needing to wander into parent pointers or scan the filesystem to
find /mnt's connection to foo.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent cc22edab
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -208,6 +208,29 @@ xrep_dir_lookup_parent(
	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(
@@ -221,6 +244,12 @@ xrep_dir_find_parent(
		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);
+37 −1
Original line number Diff line number Diff line
@@ -53,7 +53,8 @@
 * must not read the scan results without re-taking @sc->ip's ILOCK.
 *
 * There are a few shortcuts that we can take to avoid scanning the entire
 * filesystem, such as noticing directory tree roots.
 * filesystem, such as noticing directory tree roots and querying the dentry
 * cache for parent information.
 */

struct xrep_findparent_info {
@@ -410,3 +411,38 @@ xrep_findparent_self_reference(

	return NULLFSINO;
}

/* Check the dentry cache to see if knows of a parent for the scrub target. */
xfs_ino_t
xrep_findparent_from_dcache(
	struct xfs_scrub	*sc)
{
	struct inode		*pip = NULL;
	struct dentry		*dentry, *parent;
	xfs_ino_t		ret = NULLFSINO;

	dentry = d_find_alias(VFS_I(sc->ip));
	if (!dentry)
		goto out;

	parent = dget_parent(dentry);
	if (!parent)
		goto out_dput;

	ASSERT(parent->d_sb == sc->ip->i_mount->m_super);

	pip = igrab(d_inode(parent));
	dput(parent);

	if (S_ISDIR(pip->i_mode)) {
		trace_xrep_findparent_from_dcache(sc->ip, XFS_I(pip)->i_ino);
		ret = XFS_I(pip)->i_ino;
	}

	xchk_irele(sc, XFS_I(pip));

out_dput:
	dput(dentry);
out:
	return ret;
}
+1 −0
Original line number Diff line number Diff line
@@ -45,5 +45,6 @@ void xrep_findparent_scan_finish_early(struct xrep_parent_scan_info *pscan,
int xrep_findparent_confirm(struct xfs_scrub *sc, xfs_ino_t *parent_ino);

xfs_ino_t xrep_findparent_self_reference(struct xfs_scrub *sc);
xfs_ino_t xrep_findparent_from_dcache(struct xfs_scrub *sc);

#endif /* __XFS_SCRUB_FINDPARENT_H__ */
+13 −0
Original line number Diff line number Diff line
@@ -118,7 +118,20 @@ xrep_parent_find_dotdot(
	 * then retake the ILOCK so that we can salvage directory entries.
	 */
	xchk_iunlock(sc, XFS_ILOCK_EXCL);

	/* Does the VFS dcache have an answer for us? */
	ino = xrep_findparent_from_dcache(sc);
	if (ino != NULLFSINO) {
		error = xrep_findparent_confirm(sc, &ino);
		if (!error && ino != NULLFSINO) {
			xrep_findparent_scan_finish_early(&rp->pscan, ino);
			goto out_relock;
		}
	}

	/* Scan the entire filesystem for a parent. */
	error = xrep_findparent_scan(&rp->pscan);
out_relock:
	xchk_ilock(sc, XFS_ILOCK_EXCL);

	return error;
+1 −0
Original line number Diff line number Diff line
@@ -2613,6 +2613,7 @@ DEFINE_EVENT(xrep_parent_salvage_class, name, \
	TP_ARGS(dp, ino))
DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_dir_salvaged_parent);
DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_findparent_dirent);
DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_findparent_from_dcache);

#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */