summaryrefslogtreecommitdiff
path: root/fs/xfs/scrub/parent_repair.c
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2020-03-19 10:13:12 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2020-06-01 21:16:37 -0700
commit2490c4ceed3fcfd17afc53c2ddc1edc04034a26c (patch)
treeabc07e1e770226ed99b7e6410534b0b4e6e964fe /fs/xfs/scrub/parent_repair.c
parentdaad2af6e6130efb1f6568772d4f32d67ec95cb0 (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.c205
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);
+}