summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-03-17 16:31:31 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-03-25 17:08:55 -0700
commit8b1498533cb77f0625f460963cc090d2a7207173 (patch)
tree07e1db2712da2d6f881a31b235eac6cc815bc71d
parent8e45025becb250087a4bb20e82f549a9297ec15f (diff)
xfs: teach repair to fix file nlinksscrub-nlinks_2021-03-25
Fix the nlinks now too. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
-rw-r--r--fs/xfs/Makefile1
-rw-r--r--fs/xfs/scrub/nlinks_repair.c144
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c2
-rw-r--r--fs/xfs/scrub/trace.h3
5 files changed, 151 insertions, 1 deletions
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 80679edab5ae..77289963e340 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -189,6 +189,7 @@ xfs-y += $(addprefix scrub/, \
fscounters_repair.o \
ialloc_repair.o \
inode_repair.o \
+ nlinks_repair.o \
parent_repair.o \
refcount_repair.o \
repair.o \
diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c
new file mode 100644
index 000000000000..81628172059e
--- /dev/null
+++ b/fs/xfs/scrub/nlinks_repair.c
@@ -0,0 +1,144 @@
+// 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_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_inode.h"
+#include "xfs_icache.h"
+#include "xfs_bmap_util.h"
+#include "xfs_iwalk.h"
+#include "xfs_ialloc.h"
+#include "xfs_sb.h"
+#include "scrub/scrub.h"
+#include "scrub/common.h"
+#include "scrub/repair.h"
+#include "scrub/array.h"
+#include "scrub/nlinks.h"
+#include "scrub/trace.h"
+
+/*
+ * Live Inode Link Count Repair
+ * ============================
+ *
+ * Use the live inode link count information that we collected to replace the
+ * nlink values of the incore inodes. A scrub->repair cycle should have left
+ * the live data and hooks active, so this is safe so long as we make sure the
+ * inode is locked.
+ */
+
+/* Commit new counters to an inode. */
+static int
+xrep_nlinks_commit_inode(
+ struct xfs_mount *mp,
+ struct xfs_trans *tp,
+ xfs_ino_t ino,
+ void *data)
+{
+ struct xchk_nlinks *xnc = data;
+ struct xfs_inode *ip = NULL;
+ xfs_nlink_t live_nlink;
+ int error;
+
+ error = xfs_iget(mp, tp, ino, XFS_IGET_UNTRUSTED | XFS_IGET_UNLINKED,
+ XFS_ILOCK_EXCL, &ip);
+ if (error == -ENOENT || error == -EINVAL) {
+ /* Inode wasn't found, so we'll check for zero nlink. */
+ error = 0;
+ }
+ if (error)
+ return error;
+
+ mutex_lock(&xnc->lock);
+ if (xnc->hook_dead) {
+ error = -ECANCELED;
+ goto out_unlock;
+ }
+ error = xchk_nlinks_get_shadow_count(xnc, ino, &live_nlink);
+ if (error)
+ goto out_unlock;
+
+ if (ip == NULL) {
+ /*
+ * We couldn't get the inode, so the link count had better be
+ * zero! This means we cannot fix the filesystem.
+ */
+ if (live_nlink != 0) {
+ trace_xrep_nlinks_unfixable_inode(mp, ino, 0,
+ live_nlink);
+ error = -EFSCORRUPTED;
+ }
+ goto out_unlock;
+ }
+
+ /* Commit the new link count, if necessary. */
+ if (live_nlink != VFS_I(ip)->i_nlink) {
+ trace_xrep_nlinks_commit_inode(mp, ino, VFS_I(ip)->i_nlink,
+ live_nlink);
+
+ xfs_trans_ijoin(tp, ip, 0);
+ set_nlink(VFS_I(ip), live_nlink);
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ error = xrep_roll_trans(xnc->sc);
+ }
+
+out_unlock:
+ if (error)
+ xnc->hook_dead = true;
+ mutex_unlock(&xnc->lock);
+ if (ip) {
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ xchk_irele(xnc->sc, ip);
+ }
+ return error;
+}
+
+/* Commit the new inode link counters. */
+int
+xrep_nlinks(
+ struct xfs_scrub *sc)
+{
+ struct xchk_nlinks *xnc = sc->buf;
+ uint64_t nr = 0;
+ xfs_nlink_t nlink;
+ int error;
+
+ /* Update the link count of every inode that the inobt knows about. */
+ error = xfs_iwalk(sc->mp, sc->tp, 0, XFS_IWALK_METADIR,
+ xrep_nlinks_commit_inode, 0, xnc);
+ if (error)
+ return error;
+
+ /*
+ * Make a second pass to find inodes for which we have positive link
+ * count but don't seem to exist on disk. We cannot resuscitate dead
+ * inodes, but we can at least signal failure.
+ */
+ mutex_lock(&xnc->lock);
+ while (!(error = xfbma_iter_get(xnc->nlinks, &nr, &nlink))) {
+ mutex_unlock(&xnc->lock);
+
+ if (xchk_should_terminate(xnc->sc, &error))
+ return error;
+
+ error = xrep_nlinks_commit_inode(sc->mp, sc->tp, nr - 1, xnc);
+ if (error)
+ return error;
+
+ mutex_lock(&xnc->lock);
+ }
+ mutex_unlock(&xnc->lock);
+
+ /* ENODATA means we hit the end of the array. */
+ if (error == -ENODATA)
+ return 0;
+
+ return error;
+}
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index c8a4daf67fb2..640f7eb0c2d5 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -131,6 +131,7 @@ 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);
+int xrep_nlinks(struct xfs_scrub *sc);
#ifdef CONFIG_XFS_QUOTA
int xrep_quota(struct xfs_scrub *sc);
@@ -277,6 +278,7 @@ xrep_rmapbt_setup(
#define xrep_rtbitmap xrep_notsupported
#define xrep_rtrmapbt xrep_notsupported
#define xrep_rtrefcountbt xrep_notsupported
+#define xrep_nlinks xrep_notsupported
#endif /* CONFIG_XFS_ONLINE_REPAIR */
diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c
index 1d51a6002ede..c17b0ef24a0e 100644
--- a/fs/xfs/scrub/scrub.c
+++ b/fs/xfs/scrub/scrub.c
@@ -409,7 +409,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
.type = ST_FS,
.setup = xchk_setup_nlinks,
.scrub = xchk_nlinks,
- .repair = xrep_notsupported,
+ .repair = xrep_nlinks,
},
};
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index bcfdcb4ccc16..bb9dc116dec3 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -1774,6 +1774,9 @@ TRACE_EVENT(xrep_rtrefc_found,
__entry->refcount)
)
+DEFINE_SCRUB_NLINK_DIFF_EVENT(xrep_nlinks_commit_inode);
+DEFINE_SCRUB_NLINK_DIFF_EVENT(xrep_nlinks_unfixable_inode);
+
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */
#endif /* _TRACE_XFS_SCRUB_TRACE_H */