diff options
-rw-r--r-- | fs/xfs/Makefile | 1 | ||||
-rw-r--r-- | fs/xfs/scrub/dir_repair.c | 40 | ||||
-rw-r--r-- | fs/xfs/scrub/parent.c | 17 | ||||
-rw-r--r-- | fs/xfs/scrub/parent.h | 11 | ||||
-rw-r--r-- | fs/xfs/scrub/parent_repair.c | 388 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/trace.h | 1 |
8 files changed, 427 insertions, 35 deletions
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index 737e157e1122..22a8b8ce4fae 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -182,6 +182,7 @@ xfs-y += $(addprefix scrub/, \ fscounters_repair.o \ ialloc_repair.o \ inode_repair.o \ + parent_repair.o \ refcount_repair.o \ repair.o \ rmap_repair.o \ diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 7c971501043c..e6b66daae289 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -35,6 +35,7 @@ #include "scrub/repair.h" #include "scrub/array.h" #include "scrub/blob.h" +#include "scrub/parent.h" /* * Directory Repair @@ -1061,36 +1062,6 @@ xrep_dir_rebuild_tree( } /* - * Make sure we return with a valid parent inode. - * - * If the directory salvaging step found a single '..' entry, check the alleged - * parent for a dentry pointing to the directory. If this succeeds, we're - * done. Otherwise, scan the entire filesystem for a parent. - */ -STATIC int -xrep_dir_validate_parent( - struct xrep_dir *rd) -{ - struct xfs_scrub *sc = rd->sc; - - /* - * If we're the root directory, we are our own parent. If we're an - * unlinked directory, the parent /won't/ have a link to us. Set the - * parent directory to the root for both cases. - */ - if (rd->sc->ip->i_ino == sc->mp->m_sb.sb_rootino || - VFS_I(rd->sc->ip)->i_nlink == 0) { - rd->parent_ino = sc->mp->m_sb.sb_rootino; - return 0; - } - - if (!xfs_verify_dir_ino(sc->mp, rd->parent_ino)) - return -EFSCORRUPTED; - - return 0; -} - -/* * Repair the directory metadata. * * XXX: Directory entry buffers can be multiple fsblocks in size. The buffer @@ -1166,11 +1137,13 @@ xrep_dir( /* * Validate the parent pointer that we observed while salvaging the - * directory; or scan the filesystem to find one. + * directory; or scan the filesystem to find one. If we can't find + * one, we'll set a bogus parent and let the parent pointer repair + * fix it. */ - error = xrep_dir_validate_parent(&rd); + error = xrep_dir_parent_find(rd.sc, &rd.parent_ino); if (error) - goto out; + return error; /* Now rebuild the directory information. */ return xrep_dir_rebuild_tree(&rd); @@ -1179,6 +1152,5 @@ out_names: xblob_destroy(rd.dir_names); out_arr: xfbma_destroy(rd.dir_entries); -out: return error; } diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c index 032a2dcc0fee..df5633d9eb2e 100644 --- a/fs/xfs/scrub/parent.c +++ b/fs/xfs/scrub/parent.c @@ -16,12 +16,29 @@ #include "xfs_dir2_priv.h" #include "scrub/scrub.h" #include "scrub/common.h" +#include "scrub/parent.h" /* Set us up to scrub parents. */ int xchk_setup_parent( struct xfs_scrub *sc) { + int error; + + /* + * If we're attempting a repair having failed a previous repair due to + * being unable to lock an inode (TRY_HARDER), we need to freeze the + * filesystem to make the repair happen. Note that we don't bother + * with the fs freeze when TRY_HARDER is set but IFLAG_REPAIR isn't, + * because a plain scrub is allowed to return with INCOMPLETE set. + */ + if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) && + (sc->flags & XCHK_TRY_HARDER)) { + error = xchk_fs_freeze(sc); + if (error) + return error; + } + return xchk_setup_inode_contents(sc, 0); } diff --git a/fs/xfs/scrub/parent.h b/fs/xfs/scrub/parent.h new file mode 100644 index 000000000000..a04b87953b82 --- /dev/null +++ b/fs/xfs/scrub/parent.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#ifndef __XFS_SCRUB_PARENT_H__ +#define __XFS_SCRUB_PARENT_H__ + +int xrep_dir_parent_find(struct xfs_scrub *sc, xfs_ino_t *parent_ino); + +#endif /* __XFS_SCRUB_PARENT_H__ */ diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c new file mode 100644 index 000000000000..308a6cad9227 --- /dev/null +++ b/fs/xfs/scrub/parent_repair.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 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_defer.h" +#include "xfs_bit.h" +#include "xfs_log_format.h" +#include "xfs_trans.h" +#include "xfs_sb.h" +#include "xfs_inode.h" +#include "xfs_icache.h" +#include "xfs_da_format.h" +#include "xfs_da_btree.h" +#include "xfs_dir2.h" +#include "xfs_bmap_btree.h" +#include "xfs_dir2_priv.h" +#include "xfs_trans_space.h" +#include "xfs_iwalk.h" +#include "xfs_health.h" +#include "scrub/xfs_scrub.h" +#include "scrub/scrub.h" +#include "scrub/common.h" +#include "scrub/trace.h" +#include "scrub/repair.h" +#include "scrub/parent.h" + +/* + * Scanning Directory Trees for Parent Pointers + * ============================================ + * + * Walk the inode table looking for directories. Scan each directory looking + * for directory entries that point to the target inode. Call a function on + * each match. + */ + +typedef int (*xrep_parents_walk_fn)(struct xfs_inode *dp, struct xfs_name *name, + unsigned int dtype, void *data); + +struct xrep_parents_scan { + /* Context for scanning all dentries in a directory. */ + struct dir_context dc; + void *data; + xrep_parents_walk_fn fn; + + /* Potential parent of the directory we're scanning. */ +// xfs_ino_t *parent_ino; + + /* This is the inode for which we want to find the parent. */ + xfs_ino_t target_ino; + + /* Directory that we're scanning. */ + struct xfs_inode *scan_dir; + + /* Errors encountered during scanning. */ + int scan_error; +}; + +/* + * If this directory entry points to the directory we're rebuilding, then the + * directory we're scanning is the parent. Call our function. + * + * Note that the vfs readdir functions squash the nonzero codes that we return + * here into a "short" directory read, so the actual error codes are tracked + * and returned separately. + */ +STATIC int +xrep_parents_iwalk_dirents( + struct dir_context *dc, + const char *name, + int namelen, + loff_t pos, + u64 ino, + unsigned type) +{ + struct xrep_parents_scan *rps; + + rps = container_of(dc, struct xrep_parents_scan, dc); + + if (ino == rps->target_ino) { + struct xfs_name xname = { .name = name, .len = namelen }; + + rps->scan_error = rps->fn(rps->scan_dir, &xname, type, + rps->data); + if (rps->scan_error) + return 1; + } + + return 0; +} + +/* + * If this is a directory, walk the dirents looking for any that point to the + * target directory. + */ +STATIC int +xrep_parents_iwalk( + struct xfs_mount *mp, + struct xfs_trans *tp, + xfs_ino_t ino, + void *data) +{ + struct xrep_parents_scan *rps = data; + struct xfs_inode *dp; + loff_t oldpos; + size_t bufsize; + unsigned int lock_mode; + int error; + + /* Skip the inode that we're trying to find the parents of. */ + if (ino == rps->target_ino) + return 0; + + /* + * Grab inode and lock it so we can scan it. If the inode is unlinked + * or free or corrupt we'll just ignore it, since callers must be able + * to handle the case that no parent is ever found. + */ + error = xfs_iget(mp, tp, ino, XFS_IGET_UNTRUSTED, 0, &dp); + if (error) + return 0; + + if (!S_ISDIR(VFS_I(dp)->i_mode)) + goto out_rele; + + /* + * Try to get the parent directory's IOLOCK. We still hold the child's + * IOLOCK in exclusive mode, so we must avoid an ABBA deadlock. + */ + error = xchk_ilock_inverted(dp, XFS_IOLOCK_SHARED); + if (error) + goto out_rele; + + /* + * If there are any blocks, read-ahead block 0 as we're almost certain + * to have the next operation be a read there. This is how we + * guarantee that the directory's extent map has been loaded, if there + * is one. + */ + lock_mode = xfs_ilock_data_map_shared(dp); + if (dp->i_df.if_nextents > 0) + error = xfs_dir3_data_readahead(dp, 0, 0); + xfs_iunlock(dp, lock_mode); + if (error) + goto out_unlock; + + /* + * Scan the directory to see if there it contains an entry pointing to + * the directory that we are repairing. + */ + rps->scan_dir = dp; + bufsize = (size_t)min_t(loff_t, XFS_READDIR_BUFSIZE, dp->i_d.di_size); + oldpos = 0; + while (true) { + error = xfs_readdir(tp, dp, &rps->dc, bufsize); + if (error) + break; + if (rps->scan_error) { + error = rps->scan_error; + break; + } + if (oldpos == rps->dc.pos) + break; + oldpos = rps->dc.pos; + } + +out_unlock: + xfs_iunlock(dp, XFS_IOLOCK_SHARED); +out_rele: + xfs_irele(dp); + return error; +} + +/* + * Walk every dirent of every directory in the filesystem to find the entries + * that point to the target inode. + */ +STATIC int +xrep_parents_walk( + struct xfs_scrub *sc, + xfs_ino_t target_ino, + xrep_parents_walk_fn fn, + void *data) +{ + struct xrep_parents_scan rps = { + .dc.actor = xrep_parents_iwalk_dirents, + .data = data, + .fn = fn, + .target_ino = target_ino, + }; + + return xfs_iwalk(sc->mp, sc->tp, 0, 0, xrep_parents_iwalk, 0, + &rps); +} + +/* + * Repairing The Directory Parent Pointer + * ====================================== + * + * Currently, only directories support parent pointers (in the form of '..' + * entries), so we simply scan the filesystem and update the '..' entry. + * + * Note that because the only parent pointer is the dotdot entry, we won't + * touch an unhealthy directory, since the directory repair code is perfectly + * capable of rebuilding a directory with the proper parent inode. + */ + +struct xrep_dir_parent_pick_info { + struct xfs_scrub *sc; + xfs_ino_t found_parent; +}; + +/* + * If this directory entry points to the directory we're rebuilding, then the + * directory we're scanning is the parent. Remember the parent. + */ +STATIC int +xrep_dir_parent_pick( + struct xfs_inode *dp, + struct xfs_name *name, + unsigned int dtype, + void *data) +{ + struct xrep_dir_parent_pick_info *dpi = data; + int error = 0; + + /* Uhoh, more than one parent for a dir? */ + if (dpi->found_parent != NULLFSINO) + return -EFSCORRUPTED; + + if (xchk_should_terminate(dpi->sc, &error)) + return error; + + /* We found a potential parent; remember this. */ + dpi->found_parent = dp->i_ino; + return 0; +} + +/* + * Scan the directory @parent_ino to see if it has exactly one dirent that + * points to the directory that we're examining. + */ +STATIC int +xrep_dir_parent_check( + struct xfs_scrub *sc, + xfs_ino_t parent_ino, + bool *is_parent) +{ + struct xrep_dir_parent_pick_info dpi = { + .sc = sc, + .found_parent = NULLFSINO, + }; + struct xrep_parents_scan rps = { + .dc.actor = xrep_parents_iwalk_dirents, + .data = &dpi, + .fn = xrep_dir_parent_pick, + .target_ino = sc->ip->i_ino, + }; + int error; + + error = xrep_parents_iwalk(sc->mp, sc->tp, parent_ino, &rps); + if (error) + return error; + + *is_parent = dpi.found_parent == parent_ino; + return 0; +} + +/* + * Find the parent of a directory. Callers can pass in a suggested parent as + * the initial value of @parent_ino, or NULLFSINO if they don't know. If a + * parent directory is found, it will be passed back out via @parent_ino. + */ +int +xrep_dir_parent_find( + struct xfs_scrub *sc, + xfs_ino_t *parent_ino) +{ + struct xrep_dir_parent_pick_info dpi = { + .sc = sc, + .found_parent = NULLFSINO, + }; + bool is_parent = false; + int error; + + /* + * If we are the root directory, then we are our own parent. Return + * the root directory. + */ + if (sc->ip == sc->mp->m_rootip) { + *parent_ino = sc->mp->m_sb.sb_rootino; + return 0; + } + + /* + * If we are an unlinked directory, the parent won't have a link to us. + * We might as well return the suggestion, or the root directory if the + * suggestion is NULLFSINO or garbage. There's no point in scanning + * the filesystem. + */ + if (VFS_I(sc->ip)->i_nlink == 0) { + if (!xfs_verify_dir_ino(sc->mp, *parent_ino)) + *parent_ino = sc->mp->m_sb.sb_rootino; + return 0; + } + + /* + * If the caller provided a suggestion, check to see if that's really + * the parent. + */ + if (xfs_verify_dir_ino(sc->mp, *parent_ino)) { + error = xrep_dir_parent_check(sc, *parent_ino, &is_parent); + if (error || is_parent) + return error; + } + + /* Otherwise, scan the entire filesystem to find a parent. */ + error = xrep_parents_walk(sc, sc->ip->i_ino, xrep_dir_parent_pick, + &dpi); + if (error) + return error; + + *parent_ino = dpi.found_parent; + return 0; +} + +/* Replace a directory's parent '..' pointer. */ +STATIC int +xrep_dir_parent_replace( + struct xfs_scrub *sc, + xfs_ino_t parent_ino) +{ + unsigned int spaceres; + int error; + + trace_xrep_dir_parent_replace(sc->ip, parent_ino); + + /* Reserve more space just in case we have to expand the dir. */ + spaceres = XFS_RENAME_SPACE_RES(sc->mp, 2); + error = xfs_trans_reserve_more(sc->tp, spaceres, 0); + if (error) + return error; + + /* Re-take the ILOCK, we're going to need it to modify the dir. */ + sc->ilock_flags |= XFS_ILOCK_EXCL; + xfs_ilock(sc->ip, XFS_ILOCK_EXCL); + + /* Replace the dotdot entry. */ + xfs_trans_ijoin(sc->tp, sc->ip, 0); + return xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot, parent_ino, + spaceres); +} + +int +xrep_parent( + struct xfs_scrub *sc) +{ + xfs_ino_t parent_ino = NULLFSINO; + unsigned int sick, checked; + int error; + + /* + * Avoid sick directories. The parent pointer scrubber dropped the + * ILOCK, but we still hold IOLOCK_EXCL on the directory, so there + * shouldn't be anyone else clearing the directory's sick status. + */ + xfs_inode_measure_sickness(sc->ip, &sick, &checked); + if (sick & XFS_SICK_INO_DIR) + return -EFSCORRUPTED; + + /* + * Try to find the parent of this directory. If we can't find it, + * we'll just bail out for now. + */ + error = xrep_dir_parent_find(sc, &parent_ino); + if (error) + return error; + if (parent_ino == NULLFSINO) + return -EFSCORRUPTED; + + return xrep_dir_parent_replace(sc, parent_ino); +} diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index c68296c9ae31..e93fbb1bafb8 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -118,6 +118,7 @@ int xrep_symlink(struct xfs_scrub *sc); int xrep_fscounters(struct xfs_scrub *sc); int xrep_xattr(struct xfs_scrub *sc); int xrep_dir(struct xfs_scrub *sc); +int xrep_parent(struct xfs_scrub *sc); #ifdef CONFIG_XFS_QUOTA int xrep_quota(struct xfs_scrub *sc); @@ -254,6 +255,7 @@ xrep_rmapbt_setup( #define xrep_rtsummary xrep_notsupported #define xrep_xattr xrep_notsupported #define xrep_dir xrep_notsupported +#define xrep_parent xrep_notsupported #endif /* CONFIG_XFS_ONLINE_REPAIR */ diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index fba47c9158ec..d6f7d8cf20e7 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -329,7 +329,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = { .type = ST_INODE, .setup = xchk_setup_parent, .scrub = xchk_parent, - .repair = xrep_notsupported, + .repair = xrep_parent, }, [XFS_SCRUB_TYPE_RTBITMAP] = { /* realtime bitmap */ .type = ST_FS, diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index bacd9380fd46..0b9eab7b74af 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1516,6 +1516,7 @@ DEFINE_EVENT(xrep_dir_class, name, \ TP_PROTO(struct xfs_inode *dp, xfs_ino_t parent_ino), \ TP_ARGS(dp, parent_ino)) DEFINE_XREP_DIR_CLASS(xrep_dir_reset_fork); +DEFINE_XREP_DIR_CLASS(xrep_dir_parent_replace); #define XFS_DIR3_FTYPE_STR \ { XFS_DIR3_FT_UNKNOWN, "unknown" }, \ |