3f31406aef
Repair corruptions in the directory tree itself. Cycles are broken by removing an incoming parent->child link. Multiply-owned directories are fixed by pruning the extra parent -> child links Disconnected subtrees are reconnected to the lost and found. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
822 lines
19 KiB
C
822 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2023-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_trans_space.h"
|
|
#include "xfs_mount.h"
|
|
#include "xfs_log_format.h"
|
|
#include "xfs_trans.h"
|
|
#include "xfs_inode.h"
|
|
#include "xfs_icache.h"
|
|
#include "xfs_dir2.h"
|
|
#include "xfs_dir2_priv.h"
|
|
#include "xfs_attr.h"
|
|
#include "xfs_parent.h"
|
|
#include "scrub/scrub.h"
|
|
#include "scrub/common.h"
|
|
#include "scrub/bitmap.h"
|
|
#include "scrub/ino_bitmap.h"
|
|
#include "scrub/xfile.h"
|
|
#include "scrub/xfarray.h"
|
|
#include "scrub/xfblob.h"
|
|
#include "scrub/listxattr.h"
|
|
#include "scrub/trace.h"
|
|
#include "scrub/repair.h"
|
|
#include "scrub/orphanage.h"
|
|
#include "scrub/dirtree.h"
|
|
#include "scrub/readdir.h"
|
|
|
|
/*
|
|
* Directory Tree Structure Repairs
|
|
* ================================
|
|
*
|
|
* If we decide that the directory being scanned is participating in a
|
|
* directory loop, the only change we can make is to remove directory entries
|
|
* pointing down to @sc->ip. If that leaves it with no parents, the directory
|
|
* should be adopted by the orphanage.
|
|
*/
|
|
|
|
/* Set up to repair directory loops. */
|
|
int
|
|
xrep_setup_dirtree(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
return xrep_orphanage_try_create(sc);
|
|
}
|
|
|
|
/* Change the outcome of this path. */
|
|
static inline void
|
|
xrep_dirpath_set_outcome(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirpath *path,
|
|
enum xchk_dirpath_outcome outcome)
|
|
{
|
|
trace_xrep_dirpath_set_outcome(dl->sc, path->path_nr, path->nr_steps,
|
|
outcome);
|
|
|
|
path->outcome = outcome;
|
|
}
|
|
|
|
/* Delete all paths. */
|
|
STATIC void
|
|
xrep_dirtree_delete_all_paths(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirtree_outcomes *oc)
|
|
{
|
|
struct xchk_dirpath *path;
|
|
|
|
xchk_dirtree_for_each_path(dl, path) {
|
|
switch (path->outcome) {
|
|
case XCHK_DIRPATH_CORRUPT:
|
|
case XCHK_DIRPATH_LOOP:
|
|
oc->suspect--;
|
|
oc->bad++;
|
|
xrep_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
|
|
break;
|
|
case XCHK_DIRPATH_OK:
|
|
oc->good--;
|
|
oc->bad++;
|
|
xrep_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(oc->suspect == 0);
|
|
ASSERT(oc->good == 0);
|
|
}
|
|
|
|
/* Since this is the surviving path, set the dotdot entry to this value. */
|
|
STATIC void
|
|
xrep_dirpath_retain_parent(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirpath *path)
|
|
{
|
|
struct xchk_dirpath_step step;
|
|
int error;
|
|
|
|
error = xfarray_load(dl->path_steps, path->first_step, &step);
|
|
if (error)
|
|
return;
|
|
|
|
dl->parent_ino = be64_to_cpu(step.pptr_rec.p_ino);
|
|
}
|
|
|
|
/* Find the one surviving path so we know how to set dotdot. */
|
|
STATIC void
|
|
xrep_dirtree_find_surviving_path(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirtree_outcomes *oc)
|
|
{
|
|
struct xchk_dirpath *path;
|
|
bool foundit = false;
|
|
|
|
xchk_dirtree_for_each_path(dl, path) {
|
|
switch (path->outcome) {
|
|
case XCHK_DIRPATH_CORRUPT:
|
|
case XCHK_DIRPATH_LOOP:
|
|
case XCHK_DIRPATH_OK:
|
|
if (!foundit) {
|
|
xrep_dirpath_retain_parent(dl, path);
|
|
foundit = true;
|
|
continue;
|
|
}
|
|
ASSERT(foundit == false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(oc->suspect + oc->good == 1);
|
|
}
|
|
|
|
/* Delete all paths except for the one good one. */
|
|
STATIC void
|
|
xrep_dirtree_keep_one_good_path(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirtree_outcomes *oc)
|
|
{
|
|
struct xchk_dirpath *path;
|
|
bool foundit = false;
|
|
|
|
xchk_dirtree_for_each_path(dl, path) {
|
|
switch (path->outcome) {
|
|
case XCHK_DIRPATH_CORRUPT:
|
|
case XCHK_DIRPATH_LOOP:
|
|
oc->suspect--;
|
|
oc->bad++;
|
|
xrep_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
|
|
break;
|
|
case XCHK_DIRPATH_OK:
|
|
if (!foundit) {
|
|
xrep_dirpath_retain_parent(dl, path);
|
|
foundit = true;
|
|
continue;
|
|
}
|
|
oc->good--;
|
|
oc->bad++;
|
|
xrep_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(oc->suspect == 0);
|
|
ASSERT(oc->good < 2);
|
|
}
|
|
|
|
/* Delete all paths except for one suspect one. */
|
|
STATIC void
|
|
xrep_dirtree_keep_one_suspect_path(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirtree_outcomes *oc)
|
|
{
|
|
struct xchk_dirpath *path;
|
|
bool foundit = false;
|
|
|
|
xchk_dirtree_for_each_path(dl, path) {
|
|
switch (path->outcome) {
|
|
case XCHK_DIRPATH_CORRUPT:
|
|
case XCHK_DIRPATH_LOOP:
|
|
if (!foundit) {
|
|
xrep_dirpath_retain_parent(dl, path);
|
|
foundit = true;
|
|
continue;
|
|
}
|
|
oc->suspect--;
|
|
oc->bad++;
|
|
xrep_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
|
|
break;
|
|
case XCHK_DIRPATH_OK:
|
|
ASSERT(0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(oc->suspect == 1);
|
|
ASSERT(oc->good == 0);
|
|
}
|
|
|
|
/*
|
|
* Figure out what to do with the paths we tried to find. Returns -EDEADLOCK
|
|
* if the scan results have become stale.
|
|
*/
|
|
STATIC void
|
|
xrep_dirtree_decide_fate(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirtree_outcomes *oc)
|
|
{
|
|
xchk_dirtree_evaluate(dl, oc);
|
|
|
|
/* Parentless directories should not have any paths at all. */
|
|
if (xchk_dirtree_parentless(dl)) {
|
|
xrep_dirtree_delete_all_paths(dl, oc);
|
|
return;
|
|
}
|
|
|
|
/* One path is exactly the number of paths we want. */
|
|
if (oc->good + oc->suspect == 1) {
|
|
xrep_dirtree_find_surviving_path(dl, oc);
|
|
return;
|
|
}
|
|
|
|
/* Zero paths means we should reattach the subdir to the orphanage. */
|
|
if (oc->good + oc->suspect == 0) {
|
|
if (dl->sc->orphanage)
|
|
oc->needs_adoption = true;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, this subdirectory has too many parents. If there's at
|
|
* least one good path, keep it and delete the others.
|
|
*/
|
|
if (oc->good > 0) {
|
|
xrep_dirtree_keep_one_good_path(dl, oc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* There are no good paths and there are too many suspect paths.
|
|
* Keep the first suspect path and delete the rest.
|
|
*/
|
|
xrep_dirtree_keep_one_suspect_path(dl, oc);
|
|
}
|
|
|
|
/*
|
|
* Load the first step of this path into @step and @dl->xname/pptr
|
|
* for later repair work.
|
|
*/
|
|
STATIC int
|
|
xrep_dirtree_prep_path(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirpath *path,
|
|
struct xchk_dirpath_step *step)
|
|
{
|
|
int error;
|
|
|
|
error = xfarray_load(dl->path_steps, path->first_step, step);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xfblob_loadname(dl->path_names, step->name_cookie, &dl->xname,
|
|
step->name_len);
|
|
if (error)
|
|
return error;
|
|
|
|
dl->pptr_rec = step->pptr_rec; /* struct copy */
|
|
return 0;
|
|
}
|
|
|
|
/* Delete the VFS dentry for a removed child. */
|
|
STATIC int
|
|
xrep_dirtree_purge_dentry(
|
|
struct xchk_dirtree *dl,
|
|
struct xfs_inode *dp,
|
|
const struct xfs_name *name)
|
|
{
|
|
struct qstr qname = QSTR_INIT(name->name, name->len);
|
|
struct dentry *parent_dentry, *child_dentry;
|
|
int error = 0;
|
|
|
|
/*
|
|
* Find the dentry for the parent directory. If there isn't one, we're
|
|
* done. Caller already holds i_rwsem for parent and child.
|
|
*/
|
|
parent_dentry = d_find_alias(VFS_I(dp));
|
|
if (!parent_dentry)
|
|
return 0;
|
|
|
|
/* The VFS thinks the parent is a directory, right? */
|
|
if (!d_is_dir(parent_dentry)) {
|
|
ASSERT(d_is_dir(parent_dentry));
|
|
error = -EFSCORRUPTED;
|
|
goto out_dput_parent;
|
|
}
|
|
|
|
/*
|
|
* Try to find the dirent pointing to the child. If there isn't one,
|
|
* we're done.
|
|
*/
|
|
qname.hash = full_name_hash(parent_dentry, name->name, name->len);
|
|
child_dentry = d_lookup(parent_dentry, &qname);
|
|
if (!child_dentry) {
|
|
error = 0;
|
|
goto out_dput_parent;
|
|
}
|
|
|
|
trace_xrep_dirtree_delete_child(dp->i_mount, child_dentry);
|
|
|
|
/* Child is not a directory? We're screwed. */
|
|
if (!d_is_dir(child_dentry)) {
|
|
ASSERT(d_is_dir(child_dentry));
|
|
error = -EFSCORRUPTED;
|
|
goto out_dput_child;
|
|
}
|
|
|
|
/* Replace the child dentry with a negative one. */
|
|
d_delete(child_dentry);
|
|
|
|
out_dput_child:
|
|
dput(child_dentry);
|
|
out_dput_parent:
|
|
dput(parent_dentry);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Prepare to delete a link by taking the IOLOCK of the parent and the child
|
|
* (scrub target). Caller must hold IOLOCK_EXCL on @sc->ip. Returns 0 if we
|
|
* took both locks, or a negative errno if we couldn't lock the parent in time.
|
|
*/
|
|
static inline int
|
|
xrep_dirtree_unlink_iolock(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp)
|
|
{
|
|
int error;
|
|
|
|
ASSERT(sc->ilock_flags & XFS_IOLOCK_EXCL);
|
|
|
|
if (xfs_ilock_nowait(dp, XFS_IOLOCK_EXCL))
|
|
return 0;
|
|
|
|
xchk_iunlock(sc, XFS_IOLOCK_EXCL);
|
|
do {
|
|
xfs_ilock(dp, XFS_IOLOCK_EXCL);
|
|
if (xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
|
|
break;
|
|
xfs_iunlock(dp, XFS_IOLOCK_EXCL);
|
|
|
|
if (xchk_should_terminate(sc, &error)) {
|
|
xchk_ilock(sc, XFS_IOLOCK_EXCL);
|
|
return error;
|
|
}
|
|
|
|
delay(1);
|
|
} while (1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Remove a link from the directory tree and update the dcache. Returns
|
|
* -ESTALE if the scan data are now out of date.
|
|
*/
|
|
STATIC int
|
|
xrep_dirtree_unlink(
|
|
struct xchk_dirtree *dl,
|
|
struct xfs_inode *dp,
|
|
struct xchk_dirpath *path,
|
|
struct xchk_dirpath_step *step)
|
|
{
|
|
struct xfs_scrub *sc = dl->sc;
|
|
struct xfs_mount *mp = sc->mp;
|
|
xfs_ino_t dotdot_ino;
|
|
xfs_ino_t parent_ino = dl->parent_ino;
|
|
unsigned int resblks;
|
|
int dontcare;
|
|
int error;
|
|
|
|
/* Take IOLOCK_EXCL of the parent and child. */
|
|
error = xrep_dirtree_unlink_iolock(sc, dp);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Create the transaction that we need to sever the path. Ignore
|
|
* EDQUOT and ENOSPC being returned via nospace_error because the
|
|
* directory code can handle a reservationless update.
|
|
*/
|
|
resblks = xfs_remove_space_res(mp, step->name_len);
|
|
error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip,
|
|
&resblks, &sc->tp, &dontcare);
|
|
if (error)
|
|
goto out_iolock;
|
|
|
|
/*
|
|
* Cancel if someone invalidate the paths while we were trying to get
|
|
* the ILOCK.
|
|
*/
|
|
mutex_lock(&dl->lock);
|
|
if (dl->stale) {
|
|
mutex_unlock(&dl->lock);
|
|
error = -ESTALE;
|
|
goto out_trans_cancel;
|
|
}
|
|
xrep_dirpath_set_outcome(dl, path, XREP_DIRPATH_DELETING);
|
|
mutex_unlock(&dl->lock);
|
|
|
|
trace_xrep_dirtree_delete_path(dl->sc, sc->ip, path->path_nr,
|
|
&dl->xname, &dl->pptr_rec);
|
|
|
|
/*
|
|
* Decide if we need to reset the dotdot entry. Rules:
|
|
*
|
|
* - If there's a surviving parent, we want dotdot to point there.
|
|
* - If we don't have any surviving parents, then point dotdot at the
|
|
* root dir.
|
|
* - If dotdot is already set to the value we want, pass in NULLFSINO
|
|
* for no change necessary.
|
|
*
|
|
* Do this /before/ we dirty anything, in case the dotdot lookup
|
|
* fails.
|
|
*/
|
|
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &dotdot_ino);
|
|
if (error)
|
|
goto out_trans_cancel;
|
|
if (parent_ino == NULLFSINO)
|
|
parent_ino = dl->root_ino;
|
|
if (dotdot_ino == parent_ino)
|
|
parent_ino = NULLFSINO;
|
|
|
|
/* Drop the link from sc->ip's dotdot entry. */
|
|
error = xfs_droplink(sc->tp, dp);
|
|
if (error)
|
|
goto out_trans_cancel;
|
|
|
|
/* Reset the dotdot entry to a surviving parent. */
|
|
if (parent_ino != NULLFSINO) {
|
|
error = xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot,
|
|
parent_ino, 0);
|
|
if (error)
|
|
goto out_trans_cancel;
|
|
}
|
|
|
|
/* Drop the link from dp to sc->ip. */
|
|
error = xfs_droplink(sc->tp, sc->ip);
|
|
if (error)
|
|
goto out_trans_cancel;
|
|
|
|
error = xfs_dir_removename(sc->tp, dp, &dl->xname, sc->ip->i_ino,
|
|
resblks);
|
|
if (error) {
|
|
ASSERT(error != -ENOENT);
|
|
goto out_trans_cancel;
|
|
}
|
|
|
|
if (xfs_has_parent(sc->mp)) {
|
|
error = xfs_parent_removename(sc->tp, &dl->ppargs, dp,
|
|
&dl->xname, sc->ip);
|
|
if (error)
|
|
goto out_trans_cancel;
|
|
}
|
|
|
|
/*
|
|
* Notify dirent hooks that we removed the bad link, invalidate the
|
|
* dcache, and commit the repair.
|
|
*/
|
|
xfs_dir_update_hook(dp, sc->ip, -1, &dl->xname);
|
|
error = xrep_dirtree_purge_dentry(dl, dp, &dl->xname);
|
|
if (error)
|
|
goto out_trans_cancel;
|
|
|
|
error = xrep_trans_commit(sc);
|
|
goto out_ilock;
|
|
|
|
out_trans_cancel:
|
|
xchk_trans_cancel(sc);
|
|
out_ilock:
|
|
xfs_iunlock(sc->ip, XFS_ILOCK_EXCL);
|
|
xfs_iunlock(dp, XFS_ILOCK_EXCL);
|
|
out_iolock:
|
|
xfs_iunlock(dp, XFS_IOLOCK_EXCL);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Delete a directory entry that points to this directory. Returns -ESTALE
|
|
* if the scan data are now out of date.
|
|
*/
|
|
STATIC int
|
|
xrep_dirtree_delete_path(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirpath *path)
|
|
{
|
|
struct xchk_dirpath_step step;
|
|
struct xfs_scrub *sc = dl->sc;
|
|
struct xfs_inode *dp;
|
|
int error;
|
|
|
|
/*
|
|
* Load the parent pointer and directory inode for this path, then
|
|
* drop the scan lock, the ILOCK, and the transaction so that
|
|
* _delete_path can reserve the proper transaction. This sets up
|
|
* @dl->xname for the deletion.
|
|
*/
|
|
error = xrep_dirtree_prep_path(dl, path, &step);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xchk_iget(sc, be64_to_cpu(step.pptr_rec.p_ino), &dp);
|
|
if (error)
|
|
return error;
|
|
|
|
mutex_unlock(&dl->lock);
|
|
xchk_trans_cancel(sc);
|
|
xchk_iunlock(sc, XFS_ILOCK_EXCL);
|
|
|
|
/* Delete the directory link and release the parent. */
|
|
error = xrep_dirtree_unlink(dl, dp, path, &step);
|
|
xchk_irele(sc, dp);
|
|
|
|
/*
|
|
* Retake all the resources we had at the beginning even if the repair
|
|
* failed or the scan data are now stale. This keeps things simple for
|
|
* the caller.
|
|
*/
|
|
xchk_trans_alloc_empty(sc);
|
|
xchk_ilock(sc, XFS_ILOCK_EXCL);
|
|
mutex_lock(&dl->lock);
|
|
|
|
if (!error && dl->stale)
|
|
error = -ESTALE;
|
|
return error;
|
|
}
|
|
|
|
/* Add a new path to represent our in-progress adoption. */
|
|
STATIC int
|
|
xrep_dirtree_create_adoption_path(
|
|
struct xchk_dirtree *dl)
|
|
{
|
|
struct xfs_scrub *sc = dl->sc;
|
|
struct xchk_dirpath *path;
|
|
int error;
|
|
|
|
/*
|
|
* We should have capped the number of paths at XFS_MAXLINK-1 in the
|
|
* scanner.
|
|
*/
|
|
if (dl->nr_paths > XFS_MAXLINK) {
|
|
ASSERT(dl->nr_paths <= XFS_MAXLINK);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
/*
|
|
* Create a new xchk_path structure to remember this parent pointer
|
|
* and record the first name step.
|
|
*/
|
|
path = kmalloc(sizeof(struct xchk_dirpath), XCHK_GFP_FLAGS);
|
|
if (!path)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&path->list);
|
|
xino_bitmap_init(&path->seen_inodes);
|
|
path->nr_steps = 0;
|
|
path->outcome = XREP_DIRPATH_ADOPTING;
|
|
|
|
/*
|
|
* Record the new link that we just created in the orphanage. Because
|
|
* adoption is the last repair that we perform, we don't bother filling
|
|
* in the path all the way back to the root.
|
|
*/
|
|
xfs_inode_to_parent_rec(&dl->pptr_rec, sc->orphanage);
|
|
|
|
error = xino_bitmap_set(&path->seen_inodes, sc->orphanage->i_ino);
|
|
if (error)
|
|
goto out_path;
|
|
|
|
trace_xrep_dirtree_create_adoption(sc, sc->ip, dl->nr_paths,
|
|
&dl->xname, &dl->pptr_rec);
|
|
|
|
error = xchk_dirpath_append(dl, sc->ip, path, &dl->xname,
|
|
&dl->pptr_rec);
|
|
if (error)
|
|
goto out_path;
|
|
|
|
path->first_step = xfarray_length(dl->path_steps) - 1;
|
|
path->second_step = XFARRAY_NULLIDX;
|
|
path->path_nr = dl->nr_paths;
|
|
|
|
list_add_tail(&path->list, &dl->path_list);
|
|
dl->nr_paths++;
|
|
return 0;
|
|
|
|
out_path:
|
|
kfree(path);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Prepare to move a file to the orphanage by taking the IOLOCK of the
|
|
* orphanage and the child (scrub target). Caller must hold IOLOCK_EXCL on
|
|
* @sc->ip. Returns 0 if we took both locks, or a negative errno if we
|
|
* couldn't lock the orphanage in time.
|
|
*/
|
|
static inline int
|
|
xrep_dirtree_adopt_iolock(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
int error;
|
|
|
|
ASSERT(sc->ilock_flags & XFS_IOLOCK_EXCL);
|
|
|
|
if (xrep_orphanage_ilock_nowait(sc, XFS_IOLOCK_EXCL))
|
|
return 0;
|
|
|
|
xchk_iunlock(sc, XFS_IOLOCK_EXCL);
|
|
do {
|
|
xrep_orphanage_ilock(sc, XFS_IOLOCK_EXCL);
|
|
if (xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
|
|
break;
|
|
xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
|
|
|
|
if (xchk_should_terminate(sc, &error)) {
|
|
xchk_ilock(sc, XFS_IOLOCK_EXCL);
|
|
return error;
|
|
}
|
|
|
|
delay(1);
|
|
} while (1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Reattach this orphaned directory to the orphanage. Do not call this with
|
|
* any resources held. Returns -ESTALE if the scan data have become out of
|
|
* date.
|
|
*/
|
|
STATIC int
|
|
xrep_dirtree_adopt(
|
|
struct xchk_dirtree *dl)
|
|
{
|
|
struct xfs_scrub *sc = dl->sc;
|
|
int error;
|
|
|
|
/* Take the IOLOCK of the orphanage and the scrub target. */
|
|
error = xrep_dirtree_adopt_iolock(sc);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Set up for an adoption. The directory tree fixer runs after the
|
|
* link counts have been corrected. Therefore, we must bump the
|
|
* child's link count since there will be no further opportunity to fix
|
|
* errors.
|
|
*/
|
|
error = xrep_adoption_trans_alloc(sc, &dl->adoption);
|
|
if (error)
|
|
goto out_iolock;
|
|
dl->adoption.bump_child_nlink = true;
|
|
|
|
/* Figure out what name we're going to use here. */
|
|
error = xrep_adoption_compute_name(&dl->adoption, &dl->xname);
|
|
if (error)
|
|
goto out_trans;
|
|
|
|
/*
|
|
* Now that we have a proposed name for the orphanage entry, create
|
|
* a faux path so that the live update hook will see it.
|
|
*/
|
|
mutex_lock(&dl->lock);
|
|
if (dl->stale) {
|
|
mutex_unlock(&dl->lock);
|
|
error = -ESTALE;
|
|
goto out_trans;
|
|
}
|
|
error = xrep_dirtree_create_adoption_path(dl);
|
|
mutex_unlock(&dl->lock);
|
|
if (error)
|
|
goto out_trans;
|
|
|
|
/* Reparent the directory. */
|
|
error = xrep_adoption_move(&dl->adoption);
|
|
if (error)
|
|
goto out_trans;
|
|
|
|
/*
|
|
* Commit the name and release all inode locks except for the scrub
|
|
* target's IOLOCK.
|
|
*/
|
|
error = xrep_trans_commit(sc);
|
|
goto out_ilock;
|
|
|
|
out_trans:
|
|
xchk_trans_cancel(sc);
|
|
out_ilock:
|
|
xchk_iunlock(sc, XFS_ILOCK_EXCL);
|
|
xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
|
|
out_iolock:
|
|
xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* This newly orphaned directory needs to be adopted by the orphanage.
|
|
* Make this happen.
|
|
*/
|
|
STATIC int
|
|
xrep_dirtree_move_to_orphanage(
|
|
struct xchk_dirtree *dl)
|
|
{
|
|
struct xfs_scrub *sc = dl->sc;
|
|
int error;
|
|
|
|
/*
|
|
* Start by dropping all the resources that we hold so that we can grab
|
|
* all the resources that we need for the adoption.
|
|
*/
|
|
mutex_unlock(&dl->lock);
|
|
xchk_trans_cancel(sc);
|
|
xchk_iunlock(sc, XFS_ILOCK_EXCL);
|
|
|
|
/* Perform the adoption. */
|
|
error = xrep_dirtree_adopt(dl);
|
|
|
|
/*
|
|
* Retake all the resources we had at the beginning even if the repair
|
|
* failed or the scan data are now stale. This keeps things simple for
|
|
* the caller.
|
|
*/
|
|
xchk_trans_alloc_empty(sc);
|
|
xchk_ilock(sc, XFS_ILOCK_EXCL);
|
|
mutex_lock(&dl->lock);
|
|
|
|
if (!error && dl->stale)
|
|
error = -ESTALE;
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Try to fix all the problems. Returns -ESTALE if the scan data have become
|
|
* out of date.
|
|
*/
|
|
STATIC int
|
|
xrep_dirtree_fix_problems(
|
|
struct xchk_dirtree *dl,
|
|
struct xchk_dirtree_outcomes *oc)
|
|
{
|
|
struct xchk_dirpath *path;
|
|
int error;
|
|
|
|
/* Delete all the paths we don't want. */
|
|
xchk_dirtree_for_each_path(dl, path) {
|
|
if (path->outcome != XCHK_DIRPATH_DELETE)
|
|
continue;
|
|
|
|
error = xrep_dirtree_delete_path(dl, path);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
/* Reparent this directory to the orphanage. */
|
|
if (oc->needs_adoption) {
|
|
if (xrep_orphanage_can_adopt(dl->sc))
|
|
return xrep_dirtree_move_to_orphanage(dl);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Fix directory loops involving this directory. */
|
|
int
|
|
xrep_dirtree(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xchk_dirtree *dl = sc->buf;
|
|
struct xchk_dirtree_outcomes oc;
|
|
int error;
|
|
|
|
/*
|
|
* Prepare to fix the directory tree by retaking the scan lock. The
|
|
* order of resource acquisition is still IOLOCK -> transaction ->
|
|
* ILOCK -> scan lock.
|
|
*/
|
|
mutex_lock(&dl->lock);
|
|
do {
|
|
/*
|
|
* Decide what we're going to do, then do it. An -ESTALE
|
|
* return here means the scan results are invalid and we have
|
|
* to walk again.
|
|
*/
|
|
if (!dl->stale) {
|
|
xrep_dirtree_decide_fate(dl, &oc);
|
|
|
|
trace_xrep_dirtree_decided_fate(dl, &oc);
|
|
|
|
error = xrep_dirtree_fix_problems(dl, &oc);
|
|
if (!error || error != -ESTALE)
|
|
break;
|
|
}
|
|
error = xchk_dirtree_find_paths_to_root(dl);
|
|
if (error == -ELNRNG || error == -ENOSR)
|
|
error = -EFSCORRUPTED;
|
|
} while (!error);
|
|
mutex_unlock(&dl->lock);
|
|
|
|
return error;
|
|
}
|