summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2022-07-14 11:06:58 -0700
committerDarrick J. Wong <djwong@kernel.org>2022-10-14 14:16:56 -0700
commit13e77f6373b2f8748358b3836000e7acb35c6bcf (patch)
tree87195cc192b017acf111f549f8b2c965781c3263
parent90ab54f24ee8dca5b36a911b5dc6e09df297dfaf (diff)
xfs: move files to orphanage instead of letting nlinks drop to zero
If we encounter an inode with a nonzero link count but zero observed links, move it to the orphanage. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
-rw-r--r--fs/xfs/scrub/nlinks.c11
-rw-r--r--fs/xfs/scrub/nlinks.h6
-rw-r--r--fs/xfs/scrub/nlinks_repair.c250
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/trace.c1
-rw-r--r--fs/xfs/scrub/trace.h26
6 files changed, 290 insertions, 6 deletions
diff --git a/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c
index d52fbcd6e26b..485735672077 100644
--- a/fs/xfs/scrub/nlinks.c
+++ b/fs/xfs/scrub/nlinks.c
@@ -23,6 +23,7 @@
#include "scrub/repair.h"
#include "scrub/xfarray.h"
#include "scrub/iscan.h"
+#include "scrub/orphanage.h"
#include "scrub/nlinks.h"
#include "scrub/trace.h"
#include "scrub/readdir.h"
@@ -43,9 +44,17 @@ int
xchk_setup_nlinks(
struct xfs_scrub *sc)
{
+ int error;
+
xchk_fshooks_enable(sc, XCHK_FSHOOKS_NLINKS);
- sc->buf = kzalloc(sizeof(struct xchk_nlink_ctrs), XCHK_GFP_FLAGS);
+ if (xchk_could_repair(sc)) {
+ error = xrep_setup_nlinks(sc);
+ if (error)
+ return error;
+ }
+
+ sc->buf = kvzalloc(sizeof(struct xchk_nlink_ctrs), XCHK_GFP_FLAGS);
if (!sc->buf)
return -ENOMEM;
diff --git a/fs/xfs/scrub/nlinks.h b/fs/xfs/scrub/nlinks.h
index 46baef3c2237..f5108369cc2b 100644
--- a/fs/xfs/scrub/nlinks.h
+++ b/fs/xfs/scrub/nlinks.h
@@ -28,6 +28,12 @@ struct xchk_nlink_ctrs {
* from other writer threads.
*/
struct xfs_nlink_hook hooks;
+
+ /* Orphanage reparinting request. */
+ struct xrep_orphanage_req adoption;
+
+ /* Directory entry name, plus the trailing null. */
+ char namebuf[MAXNAMELEN];
};
/*
diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c
index ac9d9da1ff45..d8ec6b16da66 100644
--- a/fs/xfs/scrub/nlinks_repair.c
+++ b/fs/xfs/scrub/nlinks_repair.c
@@ -22,6 +22,7 @@
#include "scrub/repair.h"
#include "scrub/xfarray.h"
#include "scrub/iscan.h"
+#include "scrub/orphanage.h"
#include "scrub/nlinks.h"
#include "scrub/trace.h"
#include "scrub/tempfile.h"
@@ -36,6 +37,242 @@
* inode is locked.
*/
+/* Set up to repair inode link counts. */
+int
+xrep_setup_nlinks(
+ struct xfs_scrub *sc)
+{
+ return xrep_orphanage_try_create(sc);
+}
+
+/* Update incore link count information. Caller must hold the xnc lock. */
+STATIC int
+xrep_nlinks_set_record(
+ struct xchk_nlink_ctrs *xnc,
+ xfs_ino_t ino,
+ const struct xchk_nlink *nl)
+{
+ int error;
+
+ trace_xrep_nlinks_set_record(xnc->sc->mp, ino, nl);
+
+ error = xfarray_store(xnc->nlinks, ino, nl);
+ 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;
+}
+
+/*
+ * Inodes that aren't the root directory or the orphanage, have a nonzero link
+ * count, and no observed parents should be moved to the orphanage.
+ */
+static inline bool
+xrep_nlinks_is_orphaned(
+ struct xfs_scrub *sc,
+ struct xfs_inode *ip,
+ unsigned int actual_nlink,
+ const struct xchk_nlink *obs)
+{
+ struct xfs_mount *mp = ip->i_mount;
+
+ if (obs->parents != 0)
+ return false;
+ if (ip == mp->m_rootip || ip == sc->orphanage)
+ return false;
+ return actual_nlink != 0;
+}
+
+/*
+ * Correct the link count of the given inode or move it to the orphanage.
+ * Because we have to grab locks and resources in a certain order, it's
+ * possible that this will be a no-op.
+ */
+STATIC int
+xrep_nlinks_repair_and_relink_inode(
+ struct xchk_nlink_ctrs *xnc)
+{
+ struct xchk_nlink obs;
+ struct xfs_scrub *sc = xnc->sc;
+ struct xfs_mount *mp = sc->mp;
+ struct xfs_inode *ip = sc->ip;
+ uint64_t total_links;
+ unsigned int actual_nlink;
+ bool orphan = false;
+ int error;
+
+ /*
+ * Ignore temporary files being used to stage repairs, since we assume
+ * they're correct for non-directories, and the directory repair code
+ * doesn't bump the link counts for the children.
+ */
+ if (xrep_is_tempfile(ip))
+ return 0;
+
+ /* Grab the IOLOCK of the orphanage and the child directory. */
+ error = xrep_orphanage_iolock_two(sc);
+ if (error)
+ return error;
+
+ /*
+ * Allocate a transaction for the adoption. We'll reserve space for
+ * the transaction in the adoption preparation step.
+ */
+ xrep_orphanage_compute_blkres(sc, &xnc->adoption);
+
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_link, 0, 0, 0, &sc->tp);
+ if (error)
+ goto out_iolock;
+
+ /*
+ * Before we take the ILOCKs, compute the name of the potential
+ * orphanage directory entry.
+ */
+ error = xrep_orphanage_compute_name(&xnc->adoption, xnc->namebuf);
+ if (error)
+ goto out_trans;
+
+ error = xrep_orphanage_adoption_prep(&xnc->adoption);
+ if (error)
+ goto out_trans;
+
+ mutex_lock(&xnc->lock);
+
+ if (xchk_iscan_aborted(&xnc->collect_iscan)) {
+ error = -ECANCELED;
+ goto out_scanlock;
+ }
+
+ error = xfarray_load_sparse(xnc->nlinks, ip->i_ino, &obs);
+ if (error)
+ goto out_scanlock;
+
+ total_links = xchk_nlink_total(&obs);
+ actual_nlink = VFS_I(ip)->i_nlink;
+
+ /* Cannot set more than the maxiumum possible link count. */
+ if (total_links > U32_MAX) {
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ error = 0;
+ goto out_scanlock;
+ }
+
+ /*
+ * Linked directories should have at least one "child" (the dot entry)
+ * pointing up to them.
+ */
+ if (S_ISDIR(VFS_I(ip)->i_mode) && actual_nlink > 0 &&
+ obs.children == 0) {
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ error = 0;
+ goto out_scanlock;
+ }
+
+ /* Non-directories cannot have directories pointing up to them. */
+ if (!S_ISDIR(VFS_I(ip)->i_mode) && obs.children > 0) {
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ error = 0;
+ goto out_scanlock;
+ }
+
+ /*
+ * Decide if we're going to move this file to the orphanage, and fix
+ * up the incore link counts if we are.
+ */
+ if (xrep_nlinks_is_orphaned(sc, ip, actual_nlink, &obs)) {
+ obs.parents++;
+ total_links++;
+
+ error = xrep_nlinks_set_record(xnc, ip->i_ino, &obs);
+ if (error)
+ goto out_scanlock;
+
+ orphan = true;
+ }
+
+ /*
+ * We did not find any links to this inode and we're not planning to
+ * move it to the orphanage. If the inode link count is also zero, we
+ * have nothing further to do. Otherwise, the situation is unfixable.
+ */
+ if (total_links == 0) {
+ if (actual_nlink != 0)
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ error = 0;
+ goto out_scanlock;
+ }
+
+ /* If the inode has the correct link count and isn't orphaned, exit. */
+ if (total_links == actual_nlink && !orphan) {
+ error = 0;
+ goto out_scanlock;
+ }
+
+ /* Commit the new link count. */
+ trace_xrep_nlinks_update_inode(mp, ip, &obs);
+
+ /*
+ * If this is an orphan, create the new name in the orphanage, and bump
+ * the link count of the orphanage if we just added a directory. Then
+ * we can set the correct nlink.
+ */
+ if (orphan) {
+ error = xrep_orphanage_adopt(&xnc->adoption);
+ if (error)
+ goto out_scanlock;
+
+ /*
+ * If the child is a directory, we need to bump the incore link
+ * count of the orphanage to account for the new orphan's
+ * child subdirectory entry.
+ */
+ if (S_ISDIR(VFS_I(ip)->i_mode)) {
+ error = xfarray_load_sparse(xnc->nlinks,
+ sc->orphanage->i_ino, &obs);
+ if (error)
+ goto out_scanlock;
+
+ obs.flags |= XCHK_NLINK_WRITTEN;
+ obs.children++;
+
+ error = xrep_nlinks_set_record(xnc,
+ sc->orphanage->i_ino, &obs);
+ if (error)
+ goto out_scanlock;
+ }
+ }
+ set_nlink(VFS_I(ip), total_links);
+ xfs_trans_log_inode(sc->tp, ip, XFS_ILOG_CORE);
+ mutex_unlock(&xnc->lock);
+
+ error = xrep_trans_commit(sc);
+ if (error)
+ goto out_ilock;
+
+ xchk_iunlock(sc, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL);
+ xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL);
+ return 0;
+
+out_scanlock:
+ mutex_unlock(&xnc->lock);
+out_trans:
+ xchk_trans_cancel(sc);
+out_ilock:
+ xchk_iunlock(sc, XFS_ILOCK_EXCL);
+ xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
+out_iolock:
+ xchk_iunlock(sc, XFS_IOLOCK_EXCL);
+ xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
+ return error;
+}
+
/*
* Correct the link count of the given inode. Because we have to grab locks
* and resources in a certain order, it's possible that this will be a no-op.
@@ -184,10 +421,10 @@ xrep_nlinks(
/*
* We need ftype for an accurate count of the number of child
* subdirectory links. Child subdirectories with a back link (dotdot
- * entry) but no forward link are unfixable, so we cannot repair the
- * link count of the parent directory based on the back link count
- * alone. Filesystems without ftype support are rare (old V4) so we
- * just skip out here.
+ * entry) but no forward link are moved to the orphanage, so we cannot
+ * repair the link count of the parent directory based on the back link
+ * count alone. Filesystems without ftype support are rare (old V4) so
+ * we just skip out here.
*/
if (!xfs_has_ftype(sc->mp))
return -EOPNOTSUPP;
@@ -208,7 +445,10 @@ xrep_nlinks(
*/
xchk_trans_cancel(sc);
- error = xrep_nlinks_repair_inode(xnc);
+ if (sc->orphanage && sc->ip != sc->orphanage)
+ error = xrep_nlinks_repair_and_relink_inode(xnc);
+ else
+ error = xrep_nlinks_repair_inode(xnc);
xchk_iscan_mark_visited(&xnc->compare_iscan, sc->ip);
xchk_irele(sc, sc->ip);
sc->ip = NULL;
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index 073b423a247e..6440d176b072 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -82,6 +82,7 @@ int xrep_setup_rtsummary(struct xfs_scrub *sc, unsigned int *resblks,
int xrep_setup_xattr(struct xfs_scrub *sc);
int xrep_setup_directory(struct xfs_scrub *sc);
int xrep_setup_parent(struct xfs_scrub *sc);
+int xrep_setup_nlinks(struct xfs_scrub *sc);
int xrep_xattr_reset_fork(struct xfs_scrub *sc);
@@ -191,6 +192,7 @@ xrep_setup_nothing(
#define xrep_setup_xattr xrep_setup_nothing
#define xrep_setup_directory xrep_setup_nothing
#define xrep_setup_parent xrep_setup_nothing
+#define xrep_setup_nlinks xrep_setup_nothing
#define xrep_setup_inode(sc, imap) ((void)0)
diff --git a/fs/xfs/scrub/trace.c b/fs/xfs/scrub/trace.c
index f8f50c5a02c0..2e36fcc12e40 100644
--- a/fs/xfs/scrub/trace.c
+++ b/fs/xfs/scrub/trace.c
@@ -23,6 +23,7 @@
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/iscan.h"
+#include "scrub/orphanage.h"
#include "scrub/nlinks.h"
#include "scrub/fscounters.h"
#include "scrub/xfbtree.h"
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index 236635f90f3a..8d5067b4e3d9 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -2508,6 +2508,32 @@ DEFINE_XREP_PARENT_SALVAGE_CLASS(xrep_dir_salvaged_parent);
DEFINE_XREP_PARENT_SALVAGE_CLASS(xrep_findparent_dirent);
DEFINE_XREP_PARENT_SALVAGE_CLASS(xrep_findparent_from_dcache);
+TRACE_EVENT(xrep_nlinks_set_record,
+ TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino,
+ const struct xchk_nlink *obs),
+ TP_ARGS(mp, ino, obs),
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(xfs_ino_t, ino)
+ __field(xfs_nlink_t, parents)
+ __field(xfs_nlink_t, backrefs)
+ __field(xfs_nlink_t, children)
+ ),
+ TP_fast_assign(
+ __entry->dev = mp->m_super->s_dev;
+ __entry->ino = ino;
+ __entry->parents = obs->parents;
+ __entry->backrefs = obs->backrefs;
+ __entry->children = obs->children;
+ ),
+ TP_printk("dev %d:%d ino 0x%llx parents %u backrefs %u children %u",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino,
+ __entry->parents,
+ __entry->backrefs,
+ __entry->children)
+);
+
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */