mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 20:22:09 +00:00
xfs: deferred scrub of parent pointers
If the trylock-based dirent check fails, retain those parent pointers and check them at the end. This may involve dropping the locks on the file being scanned, so yay. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
parent
0d29a20fbd
commit
8ad345306d
@ -177,6 +177,7 @@ xfs-y += $(addprefix scrub/, \
|
||||
scrub.o \
|
||||
symlink.o \
|
||||
xfarray.o \
|
||||
xfblob.o \
|
||||
xfile.o \
|
||||
)
|
||||
|
||||
@ -218,7 +219,6 @@ xfs-y += $(addprefix scrub/, \
|
||||
rmap_repair.o \
|
||||
symlink_repair.o \
|
||||
tempfile.o \
|
||||
xfblob.o \
|
||||
)
|
||||
|
||||
xfs-$(CONFIG_XFS_RT) += $(addprefix scrub/, \
|
||||
|
@ -23,6 +23,9 @@
|
||||
#include "scrub/tempfile.h"
|
||||
#include "scrub/repair.h"
|
||||
#include "scrub/listxattr.h"
|
||||
#include "scrub/xfile.h"
|
||||
#include "scrub/xfarray.h"
|
||||
#include "scrub/xfblob.h"
|
||||
#include "scrub/trace.h"
|
||||
|
||||
/* Set us up to scrub parents. */
|
||||
@ -211,6 +214,18 @@ out_rele:
|
||||
* forward to the child file.
|
||||
*/
|
||||
|
||||
/* Deferred parent pointer entry that we saved for later. */
|
||||
struct xchk_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;
|
||||
};
|
||||
|
||||
struct xchk_pptrs {
|
||||
struct xfs_scrub *sc;
|
||||
|
||||
@ -219,6 +234,22 @@ struct xchk_pptrs {
|
||||
|
||||
/* Parent of this directory. */
|
||||
xfs_ino_t parent_ino;
|
||||
|
||||
/* Fixed-size array of xchk_pptr structures. */
|
||||
struct xfarray *pptr_entries;
|
||||
|
||||
/* Blobs containing parent pointer names. */
|
||||
struct xfblob *pptr_names;
|
||||
|
||||
/* Scratch buffer for scanning pptr xattrs */
|
||||
struct xfs_da_args pptr_args;
|
||||
|
||||
/* If we've cycled the ILOCK, we must revalidate all deferred pptrs. */
|
||||
bool need_revalidate;
|
||||
|
||||
/* Name buffer */
|
||||
struct xfs_name xname;
|
||||
char namebuf[MAXNAMELEN];
|
||||
};
|
||||
|
||||
/* Does this parent pointer match the dotdot entry? */
|
||||
@ -461,8 +492,25 @@ xchk_parent_scan_attr(
|
||||
/* Try to lock the inode. */
|
||||
lockmode = xchk_parent_lock_dir(sc, dp);
|
||||
if (!lockmode) {
|
||||
xchk_set_incomplete(sc);
|
||||
error = -ECANCELED;
|
||||
struct xchk_pptr save_pp = {
|
||||
.pptr_rec = *pptr_rec, /* struct copy */
|
||||
.namelen = namelen,
|
||||
};
|
||||
|
||||
/* Couldn't lock the inode, so save the pptr for later. */
|
||||
trace_xchk_parent_defer(sc->ip, &xname, dp->i_ino);
|
||||
|
||||
error = xfblob_storename(pp->pptr_names, &save_pp.name_cookie,
|
||||
&xname);
|
||||
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
|
||||
&error))
|
||||
goto out_rele;
|
||||
|
||||
error = xfarray_append(pp->pptr_entries, &save_pp);
|
||||
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
|
||||
&error))
|
||||
goto out_rele;
|
||||
|
||||
goto out_rele;
|
||||
}
|
||||
|
||||
@ -477,6 +525,162 @@ out_rele:
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Revalidate a parent pointer that we collected in the past but couldn't check
|
||||
* because of lock contention. Returns 0 if the parent pointer is still valid,
|
||||
* -ENOENT if it has gone away on us, or a negative errno.
|
||||
*/
|
||||
STATIC int
|
||||
xchk_parent_revalidate_pptr(
|
||||
struct xchk_pptrs *pp,
|
||||
const struct xfs_name *xname,
|
||||
struct xfs_parent_rec *pptr)
|
||||
{
|
||||
struct xfs_scrub *sc = pp->sc;
|
||||
int error;
|
||||
|
||||
error = xfs_parent_lookup(sc->tp, sc->ip, xname, pptr, &pp->pptr_args);
|
||||
if (error == -ENOATTR) {
|
||||
/* Parent pointer went away, nothing to revalidate. */
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check a parent pointer the slow way, which means we cycle locks a bunch
|
||||
* and put up with revalidation until we get it done.
|
||||
*/
|
||||
STATIC int
|
||||
xchk_parent_slow_pptr(
|
||||
struct xchk_pptrs *pp,
|
||||
const struct xfs_name *xname,
|
||||
struct xfs_parent_rec *pptr)
|
||||
{
|
||||
struct xfs_scrub *sc = pp->sc;
|
||||
struct xfs_inode *dp = NULL;
|
||||
unsigned int lockmode;
|
||||
int error;
|
||||
|
||||
/* Check that the deferred parent pointer still exists. */
|
||||
if (pp->need_revalidate) {
|
||||
error = xchk_parent_revalidate_pptr(pp, xname, pptr);
|
||||
if (error == -ENOENT)
|
||||
return 0;
|
||||
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
|
||||
&error))
|
||||
return error;
|
||||
}
|
||||
|
||||
error = xchk_parent_iget(pp, pptr, &dp);
|
||||
if (error)
|
||||
return error;
|
||||
if (!dp)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If we can grab both IOLOCK and ILOCK of the alleged parent, we
|
||||
* can proceed with the validation.
|
||||
*/
|
||||
lockmode = xchk_parent_lock_dir(sc, dp);
|
||||
if (lockmode) {
|
||||
trace_xchk_parent_slowpath(sc->ip, xname, dp->i_ino);
|
||||
goto check_dirent;
|
||||
}
|
||||
|
||||
/*
|
||||
* We couldn't lock the parent dir. Drop all the locks and try to
|
||||
* get them again, one at a time.
|
||||
*/
|
||||
xchk_iunlock(sc, sc->ilock_flags);
|
||||
pp->need_revalidate = true;
|
||||
|
||||
trace_xchk_parent_ultraslowpath(sc->ip, xname, dp->i_ino);
|
||||
|
||||
error = xchk_dir_trylock_for_pptrs(sc, dp, &lockmode);
|
||||
if (error)
|
||||
goto out_rele;
|
||||
|
||||
/* Revalidate the parent pointer now that we cycled locks. */
|
||||
error = xchk_parent_revalidate_pptr(pp, xname, pptr);
|
||||
if (error == -ENOENT) {
|
||||
error = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
|
||||
goto out_unlock;
|
||||
|
||||
check_dirent:
|
||||
error = xchk_parent_dirent(pp, xname, dp);
|
||||
out_unlock:
|
||||
xfs_iunlock(dp, lockmode);
|
||||
out_rele:
|
||||
xchk_irele(sc, dp);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Check all the parent pointers that we deferred the first time around. */
|
||||
STATIC int
|
||||
xchk_parent_finish_slow_pptrs(
|
||||
struct xchk_pptrs *pp)
|
||||
{
|
||||
xfarray_idx_t array_cur;
|
||||
int error;
|
||||
|
||||
foreach_xfarray_idx(pp->pptr_entries, array_cur) {
|
||||
struct xchk_pptr pptr;
|
||||
|
||||
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
||||
return 0;
|
||||
|
||||
error = xfarray_load(pp->pptr_entries, array_cur, &pptr);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = xfblob_loadname(pp->pptr_names, pptr.name_cookie,
|
||||
&pp->xname, pptr.namelen);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = xchk_parent_slow_pptr(pp, &pp->xname, &pptr.pptr_rec);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Empty out both xfiles now that we've checked everything. */
|
||||
xfarray_truncate(pp->pptr_entries);
|
||||
xfblob_truncate(pp->pptr_names);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Count the number of parent pointers. */
|
||||
STATIC int
|
||||
xchk_parent_count_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 xchk_pptrs *pp = priv;
|
||||
int error;
|
||||
|
||||
if (!(attr_flags & XFS_ATTR_PARENT))
|
||||
return 0;
|
||||
|
||||
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
|
||||
valuelen, NULL, NULL);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
pp->pptrs_found++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare the number of parent pointers to the link count. For
|
||||
* non-directories these should be the same. For unlinked directories the
|
||||
@ -487,6 +691,23 @@ xchk_parent_count_pptrs(
|
||||
struct xchk_pptrs *pp)
|
||||
{
|
||||
struct xfs_scrub *sc = pp->sc;
|
||||
int error;
|
||||
|
||||
/*
|
||||
* If we cycled the ILOCK while cross-checking parent pointers with
|
||||
* dirents, then we need to recalculate the number of parent pointers.
|
||||
*/
|
||||
if (pp->need_revalidate) {
|
||||
pp->pptrs_found = 0;
|
||||
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_count_pptr, pp);
|
||||
if (error == -EFSCORRUPTED) {
|
||||
/* Found a bad parent pointer */
|
||||
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
|
||||
return 0;
|
||||
}
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
|
||||
if (sc->ip == sc->mp->m_rootip)
|
||||
@ -511,23 +732,51 @@ xchk_parent_pptr(
|
||||
struct xfs_scrub *sc)
|
||||
{
|
||||
struct xchk_pptrs *pp;
|
||||
char *descr;
|
||||
int error;
|
||||
|
||||
pp = kvzalloc(sizeof(struct xchk_pptrs), XCHK_GFP_FLAGS);
|
||||
if (!pp)
|
||||
return -ENOMEM;
|
||||
pp->sc = sc;
|
||||
pp->xname.name = pp->namebuf;
|
||||
|
||||
/*
|
||||
* Set up some staging memory for parent pointers that we can't check
|
||||
* due to locking contention.
|
||||
*/
|
||||
descr = xchk_xfile_ino_descr(sc, "slow parent pointer entries");
|
||||
error = xfarray_create(descr, 0, sizeof(struct xchk_pptr),
|
||||
&pp->pptr_entries);
|
||||
kfree(descr);
|
||||
if (error)
|
||||
goto out_pp;
|
||||
|
||||
descr = xchk_xfile_ino_descr(sc, "slow parent pointer names");
|
||||
error = xfblob_create(descr, &pp->pptr_names);
|
||||
kfree(descr);
|
||||
if (error)
|
||||
goto out_entries;
|
||||
|
||||
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_attr, pp);
|
||||
if (error == -ECANCELED) {
|
||||
error = 0;
|
||||
goto out_pp;
|
||||
goto out_names;
|
||||
}
|
||||
if (error)
|
||||
goto out_pp;
|
||||
goto out_names;
|
||||
|
||||
error = xchk_parent_finish_slow_pptrs(pp);
|
||||
if (error == -ETIMEDOUT) {
|
||||
/* Couldn't grab a lock, scrub was marked incomplete */
|
||||
error = 0;
|
||||
goto out_names;
|
||||
}
|
||||
if (error)
|
||||
goto out_names;
|
||||
|
||||
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
||||
goto out_pp;
|
||||
goto out_names;
|
||||
|
||||
/*
|
||||
* For subdirectories, make sure the dotdot entry references the same
|
||||
@ -545,7 +794,7 @@ xchk_parent_pptr(
|
||||
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
|
||||
error = xchk_parent_pptr_and_dotdot(pp);
|
||||
if (error)
|
||||
goto out_pp;
|
||||
goto out_names;
|
||||
}
|
||||
|
||||
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
||||
@ -558,8 +807,12 @@ xchk_parent_pptr(
|
||||
*/
|
||||
error = xchk_parent_count_pptrs(pp);
|
||||
if (error)
|
||||
goto out_pp;
|
||||
goto out_names;
|
||||
|
||||
out_names:
|
||||
xfblob_destroy(pp->pptr_names);
|
||||
out_entries:
|
||||
xfarray_destroy(pp->pptr_entries);
|
||||
out_pp:
|
||||
kvfree(pp);
|
||||
return error;
|
||||
|
@ -1544,6 +1544,9 @@ DEFINE_EVENT(xchk_pptr_class, name, \
|
||||
DEFINE_XCHK_PPTR_EVENT(xchk_dir_defer);
|
||||
DEFINE_XCHK_PPTR_EVENT(xchk_dir_slowpath);
|
||||
DEFINE_XCHK_PPTR_EVENT(xchk_dir_ultraslowpath);
|
||||
DEFINE_XCHK_PPTR_EVENT(xchk_parent_defer);
|
||||
DEFINE_XCHK_PPTR_EVENT(xchk_parent_slowpath);
|
||||
DEFINE_XCHK_PPTR_EVENT(xchk_parent_ultraslowpath);
|
||||
|
||||
/* repair tracepoints */
|
||||
#if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)
|
||||
|
Loading…
Reference in New Issue
Block a user