linux/fs/xfs/scrub/dirtree_repair.c
Darrick J. Wong 3f31406aef xfs: fix corruptions in the directory tree
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>
2024-04-23 16:55:17 -07:00

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;
}