summaryrefslogtreecommitdiff
path: root/fs/xfs
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-01-05 17:45:18 -0800
committerDarrick J. Wong <djwong@kernel.org>2021-03-25 17:08:30 -0700
commit6fe04e9be9541e905a6dfd553a91bda4d7885d8f (patch)
tree3093eaee56f389e5a8b22dc7b8f14f73383e5aeb /fs/xfs
parent1cab245f01e970a95bd46338583596b9631288e0 (diff)
xfs: online repair of parent pointers
Teach the online repair code to fix directory '..' entries (aka directory parent pointers). Since this requires us to know how to scan every dirent in every directory on the filesystem, we can reuse the parent scanner components to validate (or find!) the correct parent entry when rebuilding directories too. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Diffstat (limited to 'fs/xfs')
-rw-r--r--fs/xfs/Makefile1
-rw-r--r--fs/xfs/scrub/dir_repair.c40
-rw-r--r--fs/xfs/scrub/parent.c17
-rw-r--r--fs/xfs/scrub/parent.h11
-rw-r--r--fs/xfs/scrub/parent_repair.c388
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c2
-rw-r--r--fs/xfs/scrub/trace.h1
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" }, \