Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Darrick J. Wong | 2616 | 96.46% | 36 | 70.59% |
Christoph Hellwig | 48 | 1.77% | 3 | 5.88% |
David Chinner | 15 | 0.55% | 6 | 11.76% |
Michal Marek | 10 | 0.37% | 1 | 1.96% |
Zhi Yong Wu | 6 | 0.22% | 1 | 1.96% |
Russell Cattelan | 5 | 0.18% | 1 | 1.96% |
Chandan Babu R | 5 | 0.18% | 1 | 1.96% |
Eric Sandeen | 5 | 0.18% | 1 | 1.96% |
Xia Kaixu | 2 | 0.07% | 1 | 1.96% |
Total | 2712 | 51 |
// 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; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1