summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-09-01 11:25:37 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-09-17 18:55:29 -0700
commit803efaa936528ec6a35e4e521cddeb440236c9d0 (patch)
tree25e7f83d5f098589f4d9d0f1fdc7affa67c9e6b8
parente2ae66671e52f06e6dba1c0b4339897759c5e590 (diff)
xfs: teach repair to fix file nlinksscrub-nlinks_2021-09-17
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.c200
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c2
-rw-r--r--fs/xfs/scrub/trace.h21
5 files changed, 225 insertions, 1 deletions
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 17fb0e9e2e9c..4c4b7556e711 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -190,6 +190,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..c4527c1bfc7f
--- /dev/null
+++ b/fs/xfs/scrub/nlinks_repair.c
@@ -0,0 +1,200 @@
+// 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/iscan.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.
+ */
+
+/* Update incore link count information. Caller must hold the xnc lock. */
+static int
+xrep_nlinks_ino_set(
+ struct xchk_nlinks *xnc,
+ xfs_ino_t ino,
+ xfs_nlink_t nlinks)
+{
+ int error;
+
+ trace_xrep_nlinks_ino_set(xnc->sc->mp, ino, nlinks);
+
+ if (!xnc->nlinks)
+ return 0;
+
+ error = xfbma_set(xnc->nlinks, ino, &nlinks);
+ if (error == -EFBIG) {
+ /*
+ * EFBIG means we tried to store data at too high a byte offset
+ * in the sparse array. This should be impossible since we
+ * presumably already stored an nlink count, but we still need
+ * to fail gracefully.
+ */
+ return -ECANCELED;
+ }
+
+ return error;
+}
+
+/* Commit new counters to an inode. */
+static int
+xrep_nlinks_commit_inode(
+ struct xfs_mount *mp,
+ struct xfs_trans *tp_unused,
+ xfs_ino_t ino,
+ void *data)
+{
+ struct xchk_nlinks *xnc = data;
+ struct xfs_inode *ip = NULL;
+ struct xfs_trans *tp;
+ xfs_nlink_t live_nlink;
+ int error;
+
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
+ if (error)
+ return error;
+
+ error = xfs_iget(mp, tp, ino, XFS_IGET_UNTRUSTED, XFS_ILOCK_EXCL, &ip);
+ if (error == -ENOENT || error == -EINVAL) {
+ /* Inode wasn't found, so we'll check for zero nlink. */
+ error = 0;
+ }
+ if (error)
+ goto out_cancel;
+
+ xchk_iscan_lock(&xnc->iscan);
+ 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;
+ }
+
+ /*
+ * We can't have directories with a live nlink count of 1. We grabbed
+ * the directory inode, which means that the ondisk inode has a nonzero
+ * nlink. Set the link counts to two.
+ */
+ if (S_ISDIR(VFS_I(ip)->i_mode) && live_nlink == 1) {
+ live_nlink = 2;
+ error = xrep_nlinks_ino_set(xnc, ip->i_ino, live_nlink);
+ if (error)
+ goto out_unlock;
+ }
+
+ if (live_nlink == VFS_I(ip)->i_nlink)
+ goto out_unlock;
+
+ /* Commit the new link count, if necessary. */
+ xchk_iscan_unlock(&xnc->iscan);
+
+ trace_xrep_nlinks_commit_inode(mp, ino, VFS_I(ip)->i_nlink,
+ live_nlink);
+
+ xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
+ set_nlink(VFS_I(ip), live_nlink);
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ error = xfs_trans_commit(tp);
+ xchk_irele(xnc->sc, ip);
+ return error;
+
+out_unlock:
+ xchk_iscan_unlock(&xnc->iscan);
+ if (ip) {
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ xchk_irele(xnc->sc, ip);
+ }
+out_cancel:
+ xfs_trans_cancel(tp);
+ 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;
+
+ /* Commit the scrub transaction so that we can use multithreaded iwalk. */
+ error = xfs_trans_commit(sc->tp);
+ sc->tp = NULL;
+ if (error)
+ return error;
+
+ /* Update the link count of every inode that the inobt knows about. */
+ error = xfs_iwalk_threaded(sc->mp, 0, XFS_IWALK_METADIR,
+ xrep_nlinks_commit_inode, 0, false, 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.
+ */
+ xchk_iscan_lock(&xnc->iscan);
+ while (!(error = xfbma_iter_get(xnc->nlinks, &nr, &nlink))) {
+ xchk_iscan_unlock(&xnc->iscan);
+
+ if (xchk_should_terminate(xnc->sc, &error))
+ return error;
+
+ error = xrep_nlinks_commit_inode(sc->mp, NULL, nr - 1, xnc);
+ if (error)
+ return error;
+
+ xchk_iscan_lock(&xnc->iscan);
+ }
+ xchk_iscan_unlock(&xnc->iscan);
+
+ /* 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 b4a5043394a2..de0a219f059b 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -121,6 +121,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);
@@ -267,6 +268,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 44890551f73e..d3944a1d0146 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 7892b3e1d140..732a7681d691 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -1912,6 +1912,27 @@ 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);
+
+TRACE_EVENT(xrep_nlinks_ino_set,
+ TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino, xfs_nlink_t nlinks),
+ TP_ARGS(mp, ino, nlinks),
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(xfs_ino_t, ino)
+ __field(xfs_nlink_t, nlinks)
+ ),
+ TP_fast_assign(
+ __entry->dev = mp->m_super->s_dev;
+ __entry->ino = ino;
+ __entry->nlinks = nlinks;
+ ),
+ TP_printk("dev %d:%d ino 0x%llx nlinks %u",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino, __entry->nlinks)
+)
+
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */
#endif /* _TRACE_XFS_SCRUB_TRACE_H */