summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2020-03-19 15:09:51 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2020-06-01 21:16:38 -0700
commit881b95040516e184f692308384a7dbdc7056d462 (patch)
tree811387aa06ade9d52038f732ec752ce214378687
parent2490c4ceed3fcfd17afc53c2ddc1edc04034a26c (diff)
xfs: online repair of parent pointersrepair-hard-problems_2020-06-01
Teach the online repair code to fix directory '..' entries (aka parent pointers). Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
-rw-r--r--fs/xfs/scrub/parent.c16
-rw-r--r--fs/xfs/scrub/parent_repair.c95
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c2
-rw-r--r--fs/xfs/scrub/trace.h1
5 files changed, 115 insertions, 1 deletions
diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c
index c42ac90b6c99..5d8cc599fef7 100644
--- a/fs/xfs/scrub/parent.c
+++ b/fs/xfs/scrub/parent.c
@@ -24,6 +24,22 @@ xchk_setup_parent(
struct xfs_scrub *sc,
struct xfs_inode *ip)
{
+ 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, ip, 0);
}
diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c
index 9c8cc7c2c206..3d3993ba920d 100644
--- a/fs/xfs/scrub/parent_repair.c
+++ b/fs/xfs/scrub/parent_repair.c
@@ -19,9 +19,11 @@
#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"
@@ -203,3 +205,96 @@ xrep_scan_for_parents(
return xfs_iwalk(sc->mp, sc->tp, 0, 0, xrep_parents_scan_inode, 0,
&rps);
}
+
+/*
+ * Repairing Parent Pointers
+ * =========================
+ *
+ * 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_parent {
+ struct xfs_scrub *sc;
+
+ /* Potential parent pointer. */
+ xfs_ino_t parent_ino;
+};
+
+/*
+ * 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_parent_absorb(
+ struct xfs_inode *dp,
+ struct xfs_name *name,
+ unsigned int dtype,
+ void *data)
+{
+ struct xrep_parent *rp = data;
+ int error = 0;
+
+ /* Uhoh, more than one parent for a dir? */
+ if (rp->parent_ino != NULLFSINO)
+ return -EFSCORRUPTED;
+
+ if (xchk_should_terminate(rp->sc, &error))
+ return error;
+
+ /* We found a potential parent; remember this. */
+ rp->parent_ino = dp->i_ino;
+ return 0;
+}
+
+int
+xrep_parent(
+ struct xfs_scrub *sc)
+{
+ struct xrep_parent rp = {
+ .sc = sc,
+ .parent_ino = NULLFSINO,
+ };
+ unsigned int sick, checked;
+ unsigned int spaceres;
+ 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;
+
+ /* Scan the entire directory tree for the directory's parent. */
+ error = xrep_scan_for_parents(sc, sc->ip->i_ino, xrep_parent_absorb,
+ &rp);
+ if (error)
+ return error;
+
+ /* If we still don't have a parent, bail out. */
+ if (!xrep_parent_acceptable(sc, rp.parent_ino))
+ return 0;
+
+ trace_xrep_parent_dir(sc->ip, rp.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, rp.parent_ino,
+ spaceres);
+}
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index 481a80d6667b..b135c655ccbd 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -90,6 +90,7 @@ int xrep_bmap_data(struct xfs_scrub *sc);
int xrep_bmap_attr(struct xfs_scrub *sc);
int xrep_symlink(struct xfs_scrub *sc);
int xrep_dir(struct xfs_scrub *sc);
+int xrep_parent(struct xfs_scrub *sc);
int xrep_xattr(struct xfs_scrub *sc);
int xrep_fscounters(struct xfs_scrub *sc);
#ifdef CONFIG_XFS_QUOTA
@@ -217,6 +218,7 @@ xrep_rmapbt_setup(
#define xrep_bmap_attr xrep_notsupported
#define xrep_symlink xrep_notsupported
#define xrep_dir xrep_notsupported
+#define xrep_parent xrep_notsupported
#define xrep_xattr xrep_notsupported
#define xrep_fscounters xrep_notsupported
#define xrep_quota xrep_notsupported
diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c
index b71e193195f9..657db43394a9 100644
--- a/fs/xfs/scrub/scrub.c
+++ b/fs/xfs/scrub/scrub.c
@@ -318,7 +318,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 8d78f66ddfb9..b53fc32b61da 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -1223,6 +1223,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_parent_dir);
#define XFS_DIR3_FTYPE_STR \
{ XFS_DIR3_FT_UNKNOWN, "unknown" }, \