#include "xfs_platform.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"
int
xrep_setup_dirtree(
struct xfs_scrub *sc)
{
return xrep_orphanage_try_create(sc);
}
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;
}
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);
}
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);
}
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);
}
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);
}
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);
}
STATIC void
xrep_dirtree_decide_fate(
struct xchk_dirtree *dl,
struct xchk_dirtree_outcomes *oc)
{
xchk_dirtree_evaluate(dl, oc);
if (xchk_dirtree_parentless(dl)) {
xrep_dirtree_delete_all_paths(dl, oc);
return;
}
if (oc->good + oc->suspect == 1) {
xrep_dirtree_find_surviving_path(dl, oc);
return;
}
if (oc->good + oc->suspect == 0) {
if (dl->sc->orphanage)
oc->needs_adoption = true;
return;
}
if (oc->good > 0) {
xrep_dirtree_keep_one_good_path(dl, oc);
return;
}
xrep_dirtree_keep_one_suspect_path(dl, oc);
}
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;
return 0;
}
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;
parent_dentry = d_find_alias(VFS_I(dp));
if (!parent_dentry)
return 0;
if (!d_is_dir(parent_dentry)) {
ASSERT(d_is_dir(parent_dentry));
error = -EFSCORRUPTED;
goto out_dput_parent;
}
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);
if (!d_is_dir(child_dentry)) {
ASSERT(d_is_dir(child_dentry));
error = -EFSCORRUPTED;
goto out_dput_child;
}
d_delete(child_dentry);
out_dput_child:
dput(child_dentry);
out_dput_parent:
dput(parent_dentry);
return error;
}
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;
}
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;
error = xrep_dirtree_unlink_iolock(sc, dp);
if (error)
return error;
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;
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);
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;
error = xfs_droplink(sc->tp, dp);
if (error)
goto out_trans_cancel;
if (parent_ino != NULLFSINO) {
error = xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot,
parent_ino, 0);
if (error)
goto out_trans_cancel;
}
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;
}
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;
}
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;
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);
error = xrep_dirtree_unlink(dl, dp, path, &step);
xchk_irele(sc, dp);
xchk_trans_alloc_empty(sc);
xchk_ilock(sc, XFS_ILOCK_EXCL);
mutex_lock(&dl->lock);
if (!error && dl->stale)
error = -ESTALE;
return error;
}
STATIC int
xrep_dirtree_create_adoption_path(
struct xchk_dirtree *dl)
{
struct xfs_scrub *sc = dl->sc;
struct xchk_dirpath *path;
int error;
if (dl->nr_paths > XFS_MAXLINK) {
ASSERT(dl->nr_paths <= XFS_MAXLINK);
return -EFSCORRUPTED;
}
path = kmalloc_obj(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;
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;
}
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;
}
STATIC int
xrep_dirtree_adopt(
struct xchk_dirtree *dl)
{
struct xfs_scrub *sc = dl->sc;
int error;
error = xrep_dirtree_adopt_iolock(sc);
if (error)
return error;
error = xrep_adoption_trans_alloc(sc, &dl->adoption);
if (error)
goto out_iolock;
dl->adoption.bump_child_nlink = true;
error = xrep_adoption_compute_name(&dl->adoption, &dl->xname);
if (error)
goto out_trans;
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;
error = xrep_adoption_move(&dl->adoption);
if (error)
goto out_trans;
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;
}
STATIC int
xrep_dirtree_move_to_orphanage(
struct xchk_dirtree *dl)
{
struct xfs_scrub *sc = dl->sc;
int error;
mutex_unlock(&dl->lock);
xchk_trans_cancel(sc);
xchk_iunlock(sc, XFS_ILOCK_EXCL);
error = xrep_dirtree_adopt(dl);
xchk_trans_alloc_empty(sc);
xchk_ilock(sc, XFS_ILOCK_EXCL);
mutex_lock(&dl->lock);
if (!error && dl->stale)
error = -ESTALE;
return error;
}
STATIC int
xrep_dirtree_fix_problems(
struct xchk_dirtree *dl,
struct xchk_dirtree_outcomes *oc)
{
struct xchk_dirpath *path;
int error;
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;
}
if (oc->needs_adoption) {
if (xrep_orphanage_can_adopt(dl->sc))
return xrep_dirtree_move_to_orphanage(dl);
return -EFSCORRUPTED;
}
return 0;
}
int
xrep_dirtree(
struct xfs_scrub *sc)
{
struct xchk_dirtree *dl = sc->buf;
struct xchk_dirtree_outcomes oc;
int error;
mutex_lock(&dl->lock);
do {
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;
}