[PATCH 2/3] xfs: move files to orphanage instead of letting nlinks drop to zero

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Darrick J. Wong <djwong@xxxxxxxxxx>

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@xxxxxxxxxx>
---
 fs/xfs/scrub/nlinks.c        |   11 ++
 fs/xfs/scrub/nlinks.h        |    6 +
 fs/xfs/scrub/nlinks_repair.c |  250 +++++++++++++++++++++++++++++++++++++++++-
 fs/xfs/scrub/repair.h        |    2 
 fs/xfs/scrub/trace.c         |    1 
 fs/xfs/scrub/trace.h         |   26 ++++
 6 files changed, 290 insertions(+), 6 deletions(-)


diff --git a/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c
index 54aa3dc4dc89..dca759d27ac4 100644
--- a/fs/xfs/scrub/nlinks.c
+++ b/fs/xfs/scrub/nlinks.c
@@ -24,6 +24,7 @@
 #include "scrub/xfile.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"
@@ -44,9 +45,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 4723b015a1c1..f881e5dbd432 100644
--- a/fs/xfs/scrub/nlinks_repair.c
+++ b/fs/xfs/scrub/nlinks_repair.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/trace.h"
 #include "scrub/tempfile.h"
@@ -37,6 +38,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.
@@ -185,10 +422,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;
@@ -209,7 +446,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 596993b06256..5a7787e3d3a1 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -85,6 +85,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);
 
@@ -196,6 +197,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 ae8a5852e258..116f03c2fe48 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -2538,6 +2538,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) */
 
 




[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux