#include "xfs_platform.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_trans_resv.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"
STATIC void
xchk_dirtree_buf_cleanup(
void *buf)
{
struct xchk_dirtree *dl = buf;
struct xchk_dirpath *path, *n;
if (dl->scan_ino != NULLFSINO)
xfs_dir_hook_del(dl->sc->mp, &dl->dhook);
xchk_dirtree_for_each_path_safe(dl, path, n) {
list_del_init(&path->list);
xino_bitmap_destroy(&path->seen_inodes);
kfree(path);
}
if (dl->path_names)
xfblob_destroy(dl->path_names);
dl->path_names = NULL;
if (dl->path_steps)
xfarray_destroy(dl->path_steps);
dl->path_steps = NULL;
mutex_destroy(&dl->lock);
}
int
xchk_setup_dirtree(
struct xfs_scrub *sc)
{
struct xchk_dirtree *dl;
int error;
xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);
if (xchk_could_repair(sc)) {
error = xrep_setup_dirtree(sc);
if (error)
return error;
}
dl = kvzalloc_obj(struct xchk_dirtree, XCHK_GFP_FLAGS);
if (!dl)
return -ENOMEM;
dl->sc = sc;
dl->xname.name = dl->namebuf;
dl->hook_xname.name = dl->hook_namebuf;
INIT_LIST_HEAD(&dl->path_list);
dl->root_ino = NULLFSINO;
dl->scan_ino = NULLFSINO;
dl->parent_ino = NULLFSINO;
mutex_init(&dl->lock);
error = xfarray_create("dirtree path steps", 0,
sizeof(struct xchk_dirpath_step), &dl->path_steps);
if (error)
goto out_dl;
error = xfblob_create("dirtree path names", &dl->path_names);
if (error)
goto out_steps;
error = xchk_setup_inode_contents(sc, 0);
if (error)
goto out_names;
sc->buf = dl;
sc->buf_cleanup = xchk_dirtree_buf_cleanup;
return 0;
out_names:
xfblob_destroy(dl->path_names);
out_steps:
xfarray_destroy(dl->path_steps);
out_dl:
mutex_destroy(&dl->lock);
kvfree(dl);
return error;
}
int
xchk_dirpath_append(
struct xchk_dirtree *dl,
struct xfs_inode *ip,
struct xchk_dirpath *path,
const struct xfs_name *name,
const struct xfs_parent_rec *pptr)
{
struct xchk_dirpath_step step = {
.pptr_rec = *pptr,
.name_len = name->len,
};
int error;
if (path->nr_steps >= XFS_MAXLINK)
return -ELNRNG;
error = xfblob_storename(dl->path_names, &step.name_cookie, name);
if (error)
return error;
error = xino_bitmap_set(&path->seen_inodes, ip->i_ino);
if (error)
return error;
error = xfarray_append(dl->path_steps, &step);
if (error)
return error;
path->nr_steps++;
return 0;
}
STATIC int
xchk_dirtree_create_path(
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 xfs_name xname = {
.name = name,
.len = namelen,
};
struct xchk_dirtree *dl = priv;
struct xchk_dirpath *path;
const struct xfs_parent_rec *rec = value;
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;
if (dl->nr_paths >= XFS_MAXLINK)
return -ENOSR;
trace_xchk_dirtree_create_path(sc, ip, dl->nr_paths, &xname, rec);
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 = XCHK_DIRPATH_SCANNING;
error = xchk_dirpath_append(dl, sc->ip, path, &xname, 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 int
xchk_dirpath_revalidate(
struct xchk_dirtree *dl,
struct xchk_dirpath *path)
{
struct xfs_scrub *sc = dl->sc;
int error;
error = xfs_parent_lookup(sc->tp, sc->ip, &dl->xname, &dl->pptr_rec,
&dl->pptr_args);
if (error == -ENOATTR) {
trace_xchk_dirpath_disappeared(dl->sc, sc->ip, path->path_nr,
path->first_step, &dl->xname, &dl->pptr_rec);
dl->stale = true;
return -ESTALE;
}
return error;
}
STATIC int
xchk_dirpath_find_next_step(
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_dirtree *dl = priv;
const struct xfs_parent_rec *rec = value;
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;
if (dl->parents_found > 0)
return -EMLINK;
dl->parents_found++;
memcpy(dl->namebuf, name, namelen);
dl->xname.len = namelen;
dl->pptr_rec = *rec;
return 0;
}
static inline void
xchk_dirpath_set_outcome(
struct xchk_dirtree *dl,
struct xchk_dirpath *path,
enum xchk_dirpath_outcome outcome)
{
trace_xchk_dirpath_set_outcome(dl->sc, path->path_nr, path->nr_steps,
outcome);
path->outcome = outcome;
}
STATIC int
xchk_dirpath_step_up(
struct xchk_dirtree *dl,
struct xchk_dirpath *path,
bool is_metadir)
{
struct xfs_scrub *sc = dl->sc;
struct xfs_inode *dp;
xfs_ino_t parent_ino = be64_to_cpu(dl->pptr_rec.p_ino);
unsigned int lock_mode;
int error;
error = xchk_iget(sc, parent_ino, &dp);
if (error)
return error;
lock_mode = xfs_ilock_attr_map_shared(dp);
mutex_lock(&dl->lock);
if (dl->stale) {
error = -ESTALE;
goto out_scanlock;
}
if (parent_ino == dl->root_ino) {
xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_OK);
error = 0;
goto out_scanlock;
}
if (parent_ino == sc->ip->i_ino) {
xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
error = 0;
goto out_scanlock;
}
if (xino_bitmap_test(&path->seen_inodes, parent_ino)) {
xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_LOOP);
error = 0;
goto out_scanlock;
}
if (VFS_I(dp)->i_generation != be32_to_cpu(dl->pptr_rec.p_gen)) {
trace_xchk_dirpath_badgen(dl->sc, dp, path->path_nr,
path->nr_steps, &dl->xname, &dl->pptr_rec);
error = -EFSCORRUPTED;
goto out_scanlock;
}
if (!S_ISDIR(VFS_I(dp)->i_mode)) {
trace_xchk_dirpath_nondir_parent(dl->sc, dp, path->path_nr,
path->nr_steps, &dl->xname, &dl->pptr_rec);
error = -EFSCORRUPTED;
goto out_scanlock;
}
if (VFS_I(dp)->i_nlink == 0) {
trace_xchk_dirpath_unlinked_parent(dl->sc, dp, path->path_nr,
path->nr_steps, &dl->xname, &dl->pptr_rec);
error = -EFSCORRUPTED;
goto out_scanlock;
}
if (is_metadir != xfs_is_metadir_inode(dp)) {
trace_xchk_dirpath_crosses_tree(dl->sc, dp, path->path_nr,
path->nr_steps, &dl->xname, &dl->pptr_rec);
error = -EFSCORRUPTED;
goto out_scanlock;
}
if (xchk_pptr_looks_zapped(dp)) {
error = -EBUSY;
xchk_set_incomplete(sc);
goto out_scanlock;
}
mutex_unlock(&dl->lock);
dl->parents_found = 0;
error = xchk_xattr_walk(sc, dp, xchk_dirpath_find_next_step, NULL, dl);
mutex_lock(&dl->lock);
if (error == -EFSCORRUPTED || error == -EMLINK ||
(!error && dl->parents_found == 0)) {
xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_CORRUPT);
error = 0;
goto out_scanlock;
}
if (error)
goto out_scanlock;
if (dl->stale) {
error = -ESTALE;
goto out_scanlock;
}
trace_xchk_dirpath_found_next_step(sc, dp, path->path_nr,
path->nr_steps, &dl->xname, &dl->pptr_rec);
error = xchk_dirpath_append(dl, dp, path, &dl->xname, &dl->pptr_rec);
if (error)
goto out_scanlock;
if (path->second_step == XFARRAY_NULLIDX)
path->second_step = xfarray_length(dl->path_steps) - 1;
out_scanlock:
mutex_unlock(&dl->lock);
xfs_iunlock(dp, lock_mode);
xchk_irele(sc, dp);
return error;
}
STATIC int
xchk_dirpath_walk_upwards(
struct xchk_dirtree *dl,
struct xchk_dirpath *path)
{
struct xfs_scrub *sc = dl->sc;
bool is_metadir;
int error;
ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
error = xchk_dirpath_revalidate(dl, path);
if (error)
return error;
trace_xchk_dirpath_walk_upwards(sc, sc->ip, path->path_nr, &dl->xname,
&dl->pptr_rec);
if (be64_to_cpu(dl->pptr_rec.p_ino) == sc->ip->i_ino) {
xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
return 0;
}
is_metadir = xfs_is_metadir_inode(sc->ip);
mutex_unlock(&dl->lock);
xchk_iunlock(sc, XFS_ILOCK_EXCL);
error = xchk_dirpath_step_up(dl, path, is_metadir);
if (error) {
xchk_ilock(sc, XFS_ILOCK_EXCL);
mutex_lock(&dl->lock);
return error;
}
while (!error && path->outcome == XCHK_DIRPATH_SCANNING)
error = xchk_dirpath_step_up(dl, path, is_metadir);
xchk_ilock(sc, XFS_ILOCK_EXCL);
mutex_lock(&dl->lock);
if (error == -EFSCORRUPTED) {
xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_CORRUPT);
error = 0;
}
if (!error && dl->stale)
return -ESTALE;
return error;
}
STATIC int
xchk_dirpath_step_is_stale(
struct xchk_dirtree *dl,
struct xchk_dirpath *path,
unsigned int step_nr,
xfarray_idx_t step_idx,
struct xfs_dir_update_params *p,
xfs_ino_t *cursor)
{
struct xchk_dirpath_step step;
xfs_ino_t child_ino = *cursor;
int error;
error = xfarray_load(dl->path_steps, step_idx, &step);
if (error)
return error;
*cursor = be64_to_cpu(step.pptr_rec.p_ino);
if (p->ip->i_ino != child_ino || p->dp->i_ino != *cursor)
return 0;
if (p->name->len != step.name_len)
return 0;
error = xfblob_loadname(dl->path_names, step.name_cookie,
&dl->hook_xname, step.name_len);
if (error)
return error;
if (memcmp(dl->hook_xname.name, p->name->name, p->name->len) != 0)
return 0;
if (p->ip->i_ino == dl->scan_ino &&
path->outcome == XREP_DIRPATH_ADOPTING) {
xchk_dirpath_set_outcome(dl, path, XREP_DIRPATH_ADOPTED);
return 0;
}
if (p->ip->i_ino == dl->scan_ino &&
path->outcome == XREP_DIRPATH_DELETING) {
xchk_dirpath_set_outcome(dl, path, XREP_DIRPATH_DELETED);
return 0;
}
trace_xchk_dirpath_changed(dl->sc, path->path_nr, step_nr, p->dp,
p->ip, p->name);
return 1;
}
STATIC int
xchk_dirpath_is_stale(
struct xchk_dirtree *dl,
struct xchk_dirpath *path,
struct xfs_dir_update_params *p)
{
xfs_ino_t cursor = dl->scan_ino;
xfarray_idx_t idx = path->first_step;
unsigned int i;
int ret;
if (!xino_bitmap_test(&path->seen_inodes, p->ip->i_ino))
return 0;
ret = xchk_dirpath_step_is_stale(dl, path, 0, idx, p, &cursor);
if (ret != 0)
return ret;
for (i = 1, idx = path->second_step; i < path->nr_steps; i++, idx++) {
ret = xchk_dirpath_step_is_stale(dl, path, i, idx, p, &cursor);
if (ret != 0)
return ret;
}
return 0;
}
STATIC int
xchk_dirtree_live_update(
struct notifier_block *nb,
unsigned long action,
void *data)
{
struct xfs_dir_update_params *p = data;
struct xchk_dirtree *dl;
struct xchk_dirpath *path;
int ret;
dl = container_of(nb, struct xchk_dirtree, dhook.dirent_hook.nb);
trace_xchk_dirtree_live_update(dl->sc, p->dp, action, p->ip, p->delta,
p->name);
mutex_lock(&dl->lock);
if (dl->stale || dl->aborted)
goto out_unlock;
xchk_dirtree_for_each_path(dl, path) {
ret = xchk_dirpath_is_stale(dl, path, p);
if (ret < 0) {
dl->aborted = true;
break;
}
if (ret == 1) {
dl->stale = true;
break;
}
}
out_unlock:
mutex_unlock(&dl->lock);
return NOTIFY_DONE;
}
STATIC void
xchk_dirtree_reset(
void *buf)
{
struct xchk_dirtree *dl = buf;
struct xchk_dirpath *path, *n;
ASSERT(dl->sc->ilock_flags & XFS_ILOCK_EXCL);
xchk_dirtree_for_each_path_safe(dl, path, n) {
list_del_init(&path->list);
xino_bitmap_destroy(&path->seen_inodes);
kfree(path);
}
dl->nr_paths = 0;
xfarray_truncate(dl->path_steps);
xfblob_truncate(dl->path_names);
dl->stale = false;
}
STATIC int
xchk_dirtree_load_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;
}
int
xchk_dirtree_find_paths_to_root(
struct xchk_dirtree *dl)
{
struct xfs_scrub *sc = dl->sc;
struct xchk_dirpath *path;
int error = 0;
do {
if (xchk_should_terminate(sc, &error))
return error;
xchk_dirtree_reset(dl);
if (xchk_pptr_looks_zapped(sc->ip)) {
xchk_set_incomplete(sc);
return -EBUSY;
}
error = xchk_xattr_walk(sc, sc->ip, xchk_dirtree_create_path,
NULL, dl);
if (error)
return error;
xchk_dirtree_for_each_path(dl, path) {
error = xchk_dirtree_load_path(dl, path);
if (error)
return error;
error = xchk_dirpath_walk_upwards(dl, path);
if (error == -EFSCORRUPTED) {
break;
}
if (error == -ESTALE) {
ASSERT(dl->stale);
break;
}
if (error)
return error;
if (dl->aborted)
return 0;
}
} while (dl->stale);
return error;
}
void
xchk_dirtree_evaluate(
struct xchk_dirtree *dl,
struct xchk_dirtree_outcomes *oc)
{
struct xchk_dirpath *path;
ASSERT(!dl->stale);
memset(oc, 0, sizeof(struct xchk_dirtree_outcomes));
xchk_dirtree_for_each_path(dl, path) {
trace_xchk_dirpath_evaluate_path(dl->sc, path->path_nr,
path->nr_steps, path->outcome);
switch (path->outcome) {
case XCHK_DIRPATH_SCANNING:
ASSERT(0);
break;
case XCHK_DIRPATH_DELETE:
oc->bad++;
break;
case XCHK_DIRPATH_CORRUPT:
case XCHK_DIRPATH_LOOP:
oc->suspect++;
break;
case XCHK_DIRPATH_STALE:
ASSERT(0);
break;
case XCHK_DIRPATH_OK:
oc->good++;
break;
case XREP_DIRPATH_DELETING:
case XREP_DIRPATH_DELETED:
case XREP_DIRPATH_ADOPTING:
case XREP_DIRPATH_ADOPTED:
ASSERT(0);
break;
}
}
trace_xchk_dirtree_evaluate(dl, oc);
}
int
xchk_dirtree(
struct xfs_scrub *sc)
{
struct xchk_dirtree_outcomes oc;
struct xchk_dirtree *dl = sc->buf;
int error;
if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
return -ENOENT;
ASSERT(xfs_has_parent(sc->mp));
dl->root_ino = xchk_inode_rootdir_inum(sc->ip);
dl->scan_ino = sc->ip->i_ino;
trace_xchk_dirtree_start(sc->ip, sc->sm, 0);
ASSERT(sc->flags & XCHK_FSGATES_DIRENTS);
xfs_dir_hook_setup(&dl->dhook, xchk_dirtree_live_update);
error = xfs_dir_hook_add(sc->mp, &dl->dhook);
if (error)
goto out;
mutex_lock(&dl->lock);
error = xchk_dirtree_find_paths_to_root(dl);
if (error == -EFSCORRUPTED || error == -ELNRNG || error == -ENOSR) {
xchk_ino_xref_set_corrupt(sc, sc->ip->i_ino);
error = 0;
goto out_scanlock;
}
if (error == -EBUSY) {
error = 0;
goto out_scanlock;
}
if (error)
goto out_scanlock;
if (dl->aborted) {
xchk_set_incomplete(sc);
goto out_scanlock;
}
xchk_dirtree_evaluate(dl, &oc);
if (xchk_dirtree_parentless(dl)) {
if (oc.good || oc.bad || oc.suspect)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
} else {
if (oc.bad || oc.good + oc.suspect != 1)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
if (oc.suspect)
xchk_ino_xref_set_corrupt(sc, sc->ip->i_ino);
}
out_scanlock:
mutex_unlock(&dl->lock);
out:
trace_xchk_dirtree_done(sc->ip, sc->sm, error);
return error;
}
bool
xchk_dirtree_parentless(const struct xchk_dirtree *dl)
{
struct xfs_scrub *sc = dl->sc;
if (xchk_inode_is_dirtree_root(sc->ip))
return true;
if (VFS_I(sc->ip)->i_nlink == 0)
return true;
return false;
}