diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2020-03-19 10:13:12 -0700 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2020-06-01 21:16:37 -0700 |
commit | 2490c4ceed3fcfd17afc53c2ddc1edc04034a26c (patch) | |
tree | abc07e1e770226ed99b7e6410534b0b4e6e964fe /fs/xfs/scrub/parent_repair.c | |
parent | daad2af6e6130efb1f6568772d4f32d67ec95cb0 (diff) |
xfs: teach online directory repair to scan for the parent
Enhance the online directory repair code to try to scan for a
directory's parent if it doesn't find it while salvaging the directory
contents.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Diffstat (limited to 'fs/xfs/scrub/parent_repair.c')
-rw-r--r-- | fs/xfs/scrub/parent_repair.c | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c new file mode 100644 index 000000000000..9c8cc7c2c206 --- /dev/null +++ b/fs/xfs/scrub/parent_repair.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <darrick.wong@oracle.com> + */ +#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_dir2_priv.h" +#include "xfs_trans_space.h" +#include "xfs_iwalk.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. + */ + +struct xrep_parents_scan { + /* Context for scanning all dentries in a directory. */ + struct dir_context dc; + void *data; + xrep_parents_iter_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_scan_dentry( + 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; +} + +/* Walk this directory's entries looking for any that point to the target. */ +STATIC int +xrep_parents_scan_inode( + 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 locked; + int retries = 20; + int error; + + if (ino == rps->target_ino) + return 0; + + /* Grab inode and lock it so we can scan it. */ + error = xfs_iget(mp, tp, ino, XFS_IGET_UNTRUSTED, 0, &dp); + if (error) + return error; + + if (!S_ISDIR(VFS_I(dp)->i_mode)) + goto out_rele; + + /* + * Try a few times to take the directory IOLOCK. We have to use + * trylock here to avoid an ABBA deadlock with another thread that + * might have a parent locked and is asleep trying to lock our target. + * The solution for EDEADLOCK is usually to freeze the fs, so try a + * few times to get the inode to avoid that heavyweight solution. + */ + while (!(locked = xfs_ilock_nowait(dp, XFS_IOLOCK_SHARED)) && --retries) + delay(HZ / 10); + if (!locked) { + error = -EDEADLOCK; + 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_d.di_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; +} + +/* Is this an acceptable parent for the inode we're scrubbing? */ +bool +xrep_parent_acceptable( + struct xfs_scrub *sc, + xfs_ino_t ino) +{ + return ino != NULLFSINO && ino != 0 && ino != sc->ip->i_ino && + xfs_verify_dir_ino(sc->mp, ino); +} + +/* + * Scan the directory tree to find the directory entries that point to this + * inode. + */ +int +xrep_scan_for_parents( + struct xfs_scrub *sc, + xfs_ino_t target_ino, + xrep_parents_iter_fn fn, + void *data) +{ + struct xrep_parents_scan rps = { + .dc.actor = xrep_parents_scan_dentry, + .data = data, + .fn = fn, + .target_ino = target_ino, + }; + + return xfs_iwalk(sc->mp, sc->tp, 0, 0, xrep_parents_scan_inode, 0, + &rps); +} |