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