summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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" }, \