| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985 |
- // 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_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"
- /*
- * Directory Tree Structure Validation
- * ===================================
- *
- * Validating the tree qualities of the directory tree structure can be
- * difficult. If the tree is frozen, running a depth (or breadth) first search
- * and marking a bitmap suffices to determine if there is a cycle. XORing the
- * mark bitmap with the inode bitmap afterwards tells us if there are
- * disconnected cycles. If the tree is not frozen, directory updates can move
- * subtrees across the scanner wavefront, which complicates the design greatly.
- *
- * Directory parent pointers change that by enabling an incremental approach to
- * validation of the tree structure. Instead of using one thread to scan the
- * entire filesystem, we instead can have multiple threads walking individual
- * subdirectories upwards to the root. In a perfect world, the IOLOCK would
- * suffice to stabilize two directories in a parent -> child relationship.
- * Unfortunately, the VFS does not take the IOLOCK when moving a child
- * subdirectory, so we instead synchronize on ILOCK and use dirent update hooks
- * to detect a race. If a race occurs in a path, we restart the scan.
- *
- * If the walk terminates without reaching the root, we know the path is
- * disconnected and ought to be attached to the lost and found. If on the walk
- * we find the same subdir that we're scanning, we know this is a cycle and
- * should delete an incoming edge. If we find multiple paths to the root, we
- * know to delete an incoming edge.
- *
- * There are two big hitches with this approach: first, all file link counts
- * must be correct to prevent other writers from doing the wrong thing with the
- * directory tree structure. Second, because we're walking upwards in a tree
- * of arbitrary depth, we cannot hold all the ILOCKs. Instead, we will use a
- * directory update hook to invalidate the scan results if one of the paths
- * we've scanned has changed.
- */
- /* Clean up the dirtree checking resources. */
- 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);
- }
- xfblob_destroy(dl->path_names);
- xfarray_destroy(dl->path_steps);
- mutex_destroy(&dl->lock);
- }
- /* Set us up to look for directory loops. */
- int
- xchk_setup_dirtree(
- struct xfs_scrub *sc)
- {
- struct xchk_dirtree *dl;
- char *descr;
- 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(sizeof(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);
- descr = xchk_xfile_ino_descr(sc, "dirtree path steps");
- error = xfarray_create(descr, 0, sizeof(struct xchk_dirpath_step),
- &dl->path_steps);
- kfree(descr);
- if (error)
- goto out_dl;
- descr = xchk_xfile_ino_descr(sc, "dirtree path names");
- error = xfblob_create(descr, &dl->path_names);
- kfree(descr);
- 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;
- }
- /*
- * Add the parent pointer described by @dl->pptr to the given path as a new
- * step. Returns -ELNRNG if the path is too deep.
- */
- 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, /* struct copy */
- .name_len = name->len,
- };
- int error;
- /*
- * If this path is more than 2 billion steps long, this directory tree
- * is too far gone to fix.
- */
- 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;
- }
- /*
- * Create an xchk_path for each parent pointer of the directory that we're
- * scanning. For each path created, we will eventually try to walk towards the
- * root with the goal of deleting all parents except for one that leads to the
- * root.
- *
- * Returns -EFSCORRUPTED to signal that the inode being scanned has a corrupt
- * parent pointer and hence there's no point in continuing; or -ENOSR if there
- * are too many parent pointers for this directory.
- */
- 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 there are more than 2 billion actual parent pointers for this
- * subdirectory, this fs is too far gone to fix.
- */
- if (dl->nr_paths >= XFS_MAXLINK)
- return -ENOSR;
- trace_xchk_dirtree_create_path(sc, ip, dl->nr_paths, &xname, rec);
- /*
- * 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 = 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;
- }
- /*
- * Validate that the first step of this path still has a corresponding
- * parent pointer in @sc->ip. We probably dropped @sc->ip's ILOCK while
- * walking towards the roots, which is why this is necessary.
- *
- * This function has a side effect of loading the first parent pointer of this
- * path into the parent pointer scratch pad. This prepares us to walk up the
- * directory tree towards the root. Returns -ESTALE if the scan data is now
- * out of date.
- */
- STATIC int
- xchk_dirpath_revalidate(
- struct xchk_dirtree *dl,
- struct xchk_dirpath *path)
- {
- struct xfs_scrub *sc = dl->sc;
- int error;
- /*
- * Look up the parent pointer that corresponds to the start of this
- * path. If the parent pointer has disappeared on us, dump all the
- * scan results and try again.
- */
- 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;
- }
- /*
- * Walk the parent pointers of a directory at the end of a path and record
- * the parent that we find in @dl->xname/pptr_rec.
- */
- 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 we've already set @dl->pptr_rec, then this directory has multiple
- * parents. Signal this back to the caller via -EMLINK.
- */
- if (dl->parents_found > 0)
- return -EMLINK;
- dl->parents_found++;
- memcpy(dl->namebuf, name, namelen);
- dl->xname.len = namelen;
- dl->pptr_rec = *rec; /* struct copy */
- return 0;
- }
- /* Set and log the outcome of a path walk. */
- 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;
- }
- /*
- * Scan the directory at the end of this path for its parent directory link.
- * If we find one, extend the path. Returns -ESTALE if the scan data out of
- * date. Returns -EFSCORRUPTED if the parent pointer is bad; or -ELNRNG if
- * the path got too deep.
- */
- STATIC int
- xchk_dirpath_step_up(
- struct xchk_dirtree *dl,
- struct xchk_dirpath *path)
- {
- 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;
- /* Grab and lock the parent directory. */
- 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;
- }
- /* We've reached the root directory; the path is ok. */
- if (parent_ino == dl->root_ino) {
- xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_OK);
- error = 0;
- goto out_scanlock;
- }
- /*
- * The inode being scanned is its own distant ancestor! Get rid of
- * this path.
- */
- if (parent_ino == sc->ip->i_ino) {
- xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
- error = 0;
- goto out_scanlock;
- }
- /*
- * We've seen this inode before during the path walk. There's a loop
- * above us in the directory tree. This probably means that we cannot
- * continue, but let's keep walking paths to get a full picture.
- */
- if (xino_bitmap_test(&path->seen_inodes, parent_ino)) {
- xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_LOOP);
- error = 0;
- goto out_scanlock;
- }
- /* The handle encoded in the parent pointer must match. */
- 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;
- }
- /* Parent pointer must point up to a directory. */
- 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;
- }
- /* Parent cannot be an unlinked directory. */
- 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 the extended attributes look as though they has been zapped by
- * the inode record repair code, we cannot scan for parent pointers.
- */
- if (xchk_pptr_looks_zapped(dp)) {
- error = -EBUSY;
- xchk_set_incomplete(sc);
- goto out_scanlock;
- }
- /*
- * Walk the parent pointers of @dp to find the parent of this directory
- * to find the next step in our walk. If we find that @dp has exactly
- * one parent, the parent pointer information will be stored in
- * @dl->pptr_rec. This prepares us for the next step of the walk.
- */
- 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)) {
- /*
- * Further up the directory tree from @sc->ip, we found a
- * corrupt parent pointer, multiple parent pointers while
- * finding this directory's parent, or zero parents despite
- * having a nonzero link count. Keep looking for other paths.
- */
- 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);
- /* Append to the path steps */
- 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;
- }
- /*
- * Walk the directory tree upwards towards what is hopefully the root
- * directory, recording path steps as we go. The current path components are
- * stored in dl->pptr_rec and dl->xname.
- *
- * Returns -ESTALE if the scan data are out of date. Returns -EFSCORRUPTED
- * only if the direct parent pointer of @sc->ip associated with this path is
- * corrupt.
- */
- STATIC int
- xchk_dirpath_walk_upwards(
- struct xchk_dirtree *dl,
- struct xchk_dirpath *path)
- {
- struct xfs_scrub *sc = dl->sc;
- int error;
- ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
- /* Reload the start of this path and make sure it's still there. */
- 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);
- /*
- * The inode being scanned is its own direct ancestor!
- * Get rid of this path.
- */
- if (be64_to_cpu(dl->pptr_rec.p_ino) == sc->ip->i_ino) {
- xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_DELETE);
- return 0;
- }
- /*
- * Drop ILOCK_EXCL on the inode being scanned. We still hold
- * IOLOCK_EXCL on it, so it cannot move around or be renamed.
- *
- * Beyond this point we're walking up the directory tree, which means
- * that we can acquire and drop the ILOCK on an alias of sc->ip. The
- * ILOCK state is no longer tracked in the scrub context. Hence we
- * must drop @sc->ip's ILOCK during the walk.
- */
- mutex_unlock(&dl->lock);
- xchk_iunlock(sc, XFS_ILOCK_EXCL);
- /*
- * Take the first step in the walk towards the root by checking the
- * start of this path, which is a direct parent pointer of @sc->ip.
- * If we see any kind of error here (including corruptions), the parent
- * pointer of @sc->ip is corrupt. Stop the whole scan.
- */
- error = xchk_dirpath_step_up(dl, path);
- if (error) {
- xchk_ilock(sc, XFS_ILOCK_EXCL);
- mutex_lock(&dl->lock);
- return error;
- }
- /*
- * Take steps upward from the second step in this path towards the
- * root. If we hit corruption errors here, there's a problem
- * *somewhere* in the path, but we don't need to stop scanning.
- */
- while (!error && path->outcome == XCHK_DIRPATH_SCANNING)
- error = xchk_dirpath_step_up(dl, path);
- /* Retake the locks we had, mark paths, etc. */
- 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;
- }
- /*
- * Decide if this path step has been touched by this live update. Returns
- * 1 for yes, 0 for no, or a negative errno.
- */
- 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 the parent and child being updated are not the ones mentioned in
- * this path step, the scan data is still ok.
- */
- if (p->ip->i_ino != child_ino || p->dp->i_ino != *cursor)
- return 0;
- /*
- * If the dirent name lengths or byte sequences are different, the scan
- * data is still ok.
- */
- 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 the update comes from the repair code itself, walk the state
- * machine forward.
- */
- 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;
- }
- /* Exact match, scan data is out of date. */
- trace_xchk_dirpath_changed(dl->sc, path->path_nr, step_nr, p->dp,
- p->ip, p->name);
- return 1;
- }
- /*
- * Decide if this path has been touched by this live update. Returns 1 for
- * yes, 0 for no, or a negative errno.
- */
- 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;
- /*
- * The child being updated has not been seen by this path at all; this
- * path cannot be stale.
- */
- 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;
- }
- /*
- * Decide if a directory update from the regular filesystem touches any of the
- * paths we've scanned, and invalidate the scan data if true.
- */
- 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;
- }
- /* Delete all the collected path information. */
- 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;
- }
- /*
- * Load the name/pptr from the first step in this path into @dl->pptr_rec and
- * @dl->xname.
- */
- 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; /* struct copy */
- return 0;
- }
- /*
- * For each parent pointer of this subdir, trace a path upwards towards the
- * root directory and record what we find. Returns 0 for success;
- * -EFSCORRUPTED if walking the parent pointers of @sc->ip failed, -ELNRNG if a
- * path was too deep; -ENOSR if there were too many parent pointers; or
- * a negative errno.
- */
- 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 the extended attributes look as though they has been
- * zapped by the inode record repair code, we cannot scan for
- * parent pointers.
- */
- if (xchk_pptr_looks_zapped(sc->ip)) {
- xchk_set_incomplete(sc);
- return -EBUSY;
- }
- /*
- * Create path walk contexts for each parent of the directory
- * that is being scanned. Directories are supposed to have
- * only one parent, but this is how we detect multiple parents.
- */
- error = xchk_xattr_walk(sc, sc->ip, xchk_dirtree_create_path,
- NULL, dl);
- if (error)
- return error;
- xchk_dirtree_for_each_path(dl, path) {
- /* Load path components into dl->pptr/xname */
- error = xchk_dirtree_load_path(dl, path);
- if (error)
- return error;
- /*
- * Try to walk up each path to the root. This enables
- * us to find directory loops in ancestors, and the
- * like.
- */
- error = xchk_dirpath_walk_upwards(dl, path);
- if (error == -EFSCORRUPTED) {
- /*
- * A parent pointer of @sc->ip is bad, don't
- * bother continuing.
- */
- break;
- }
- if (error == -ESTALE) {
- /* This had better be an invalidation. */
- ASSERT(dl->stale);
- break;
- }
- if (error)
- return error;
- if (dl->aborted)
- return 0;
- }
- } while (dl->stale);
- return error;
- }
- /*
- * Figure out what to do with the paths we tried to find. Do not call this
- * if the scan results are stale.
- */
- void
- xchk_dirtree_evaluate(
- struct xchk_dirtree *dl,
- struct xchk_dirtree_outcomes *oc)
- {
- struct xchk_dirpath *path;
- ASSERT(!dl->stale);
- /* Scan the paths we have to decide what to do. */
- 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:
- /* shouldn't get here */
- ASSERT(0);
- break;
- case XCHK_DIRPATH_DELETE:
- /* This one is already going away. */
- oc->bad++;
- break;
- case XCHK_DIRPATH_CORRUPT:
- case XCHK_DIRPATH_LOOP:
- /* Couldn't find the end of this path. */
- oc->suspect++;
- break;
- case XCHK_DIRPATH_STALE:
- /* shouldn't get here either */
- ASSERT(0);
- break;
- case XCHK_DIRPATH_OK:
- /* This path got all the way to the root. */
- oc->good++;
- break;
- case XREP_DIRPATH_DELETING:
- case XREP_DIRPATH_DELETED:
- case XREP_DIRPATH_ADOPTING:
- case XREP_DIRPATH_ADOPTED:
- /* These should not be in progress! */
- ASSERT(0);
- break;
- }
- }
- trace_xchk_dirtree_evaluate(dl, oc);
- }
- /* Look for directory loops. */
- int
- xchk_dirtree(
- struct xfs_scrub *sc)
- {
- struct xchk_dirtree_outcomes oc;
- struct xchk_dirtree *dl = sc->buf;
- int error;
- /*
- * Nondirectories do not point downwards to other files, so they cannot
- * cause a cycle in the directory tree.
- */
- if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
- return -ENOENT;
- ASSERT(xfs_has_parent(sc->mp));
- /*
- * Find the root of the directory tree. Remember which directory to
- * scan, because the hook doesn't detach until after sc->ip gets
- * released during teardown.
- */
- dl->root_ino = sc->mp->m_rootip->i_ino;
- dl->scan_ino = sc->ip->i_ino;
- trace_xchk_dirtree_start(sc->ip, sc->sm, 0);
- /*
- * Hook into the directory entry code so that we can capture updates to
- * paths that we have already scanned. The scanner thread takes each
- * directory's ILOCK, which means that any in-progress directory update
- * will finish before we can scan the directory.
- */
- 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);
- /* Trace each parent pointer's path to the root. */
- error = xchk_dirtree_find_paths_to_root(dl);
- if (error == -EFSCORRUPTED || error == -ELNRNG || error == -ENOSR) {
- /*
- * Don't bother walking the paths if the xattr structure or the
- * parent pointers are corrupt; this scan cannot be completed
- * without full information.
- */
- xchk_ino_xref_set_corrupt(sc, sc->ip->i_ino);
- error = 0;
- goto out_scanlock;
- }
- if (error == -EBUSY) {
- /*
- * We couldn't scan some directory's parent pointers because
- * the attr fork looked like it had been zapped. The
- * scan was marked incomplete, so no further error code
- * is necessary.
- */
- error = 0;
- goto out_scanlock;
- }
- if (error)
- goto out_scanlock;
- if (dl->aborted) {
- xchk_set_incomplete(sc);
- goto out_scanlock;
- }
- /* Assess what we found in our path evaluation. */
- 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;
- }
|