summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-10-20 10:45:50 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-10-22 16:41:14 -0700
commitc5355cbaca02979360a5f1227ae3c4971222dc3d (patch)
treef0d8a5a209a5c248168c2d65f86b9bf90cb14663
parentefb0f93ddd0bb3f1f6321d21c374429fedfb3508 (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.c7
-rw-r--r--fs/xfs/scrub/nlinks_repair.c212
-rw-r--r--fs/xfs/scrub/repair.h9
-rw-r--r--fs/xfs/scrub/trace.h23
4 files changed, 244 insertions, 7 deletions
diff --git a/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c
index c833e06c7cc0..77111165e8f5 100644
--- a/fs/xfs/scrub/nlinks.c
+++ b/fs/xfs/scrub/nlinks.c
@@ -53,15 +53,16 @@ int
xchk_setup_nlinks(
struct xfs_scrub *sc)
{
+ unsigned int buf_bytes = sizeof(struct xchk_nlink_ctrs);
int error;
if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) {
- error = xrep_setup_nlinks(sc);
+ error = xrep_setup_nlinks(sc, &buf_bytes);
if (error)
return error;
}
- sc->buf = kmem_zalloc(sizeof(struct xchk_nlink_ctrs),
- KM_NOFS | KM_MAYFAIL);
+
+ sc->buf = kmem_zalloc(buf_bytes, KM_NOFS | KM_MAYFAIL);
if (!sc->buf)
return -ENOMEM;
diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c
index f2b05358a593..03cf3204c7ff 100644
--- a/fs/xfs/scrub/nlinks_repair.c
+++ b/fs/xfs/scrub/nlinks_repair.c
@@ -36,14 +36,219 @@
* inode is locked.
*/
+static inline char *
+xrep_nlinks_namebuf(
+ struct xfs_scrub *sc)
+{
+ return (char *)(((struct xchk_nlink_ctrs *)sc->buf) + 1);
+}
+
+static inline struct xrep_orphanage_req *
+xrep_nlinks_orphanage_req(
+ struct xfs_scrub *sc)
+{
+ return (struct xrep_orphanage_req *)
+ (xrep_nlinks_namebuf(sc) + MAXNAMELEN + 1);
+}
+
/* Set up to repair inode link counts. */
int
xrep_setup_nlinks(
- struct xfs_scrub *sc)
+ struct xfs_scrub *sc,
+ unsigned int *buf_bytes)
{
+ (*buf_bytes) += xrep_orphanage_req_sizeof();
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,
+ const struct xchk_nlink *obs)
+{
+ struct xfs_mount *mp = ip->i_mount;
+
+ if (obs->parent != 0)
+ return false;
+ if (ip == mp->m_rootip || ip == sc->orphanage || ip == mp->m_metadirip)
+ return false;
+ return VFS_I(ip)->i_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 xrep_orphanage_req *orph = xrep_nlinks_orphanage_req(sc);
+ struct xfs_mount *mp = sc->mp;
+ struct xfs_inode *ip = sc->ip;
+ uint64_t total_links;
+ bool orphan = false;
+ int error;
+
+ /* Grab the IOLOCK of the orphanage and the child directory. */
+ error = xrep_orphanage_iolock_two(sc);
+ if (error)
+ return error;
+
+ /*
+ * Allocate a transaction with enough resources that we can update the
+ * link count and move the child to the orphanage, if necessary.
+ */
+ xrep_orphanage_compute_blkres(sc, orph);
+
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_link,
+ orph->orphanage_blkres + orph->child_blkres,
+ 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(orph, xrep_nlinks_namebuf(sc));
+ if (error)
+ goto out_trans;
+
+ error = xrep_orphanage_ilock_resv_quota(orph);
+ if (error)
+ goto out_trans;
+
+ if (xchk_iscan_aborted(&xnc->collect_iscan)) {
+ error = -ECANCELED;
+ goto out_trans;
+ }
+
+ mutex_lock(&xnc->lock);
+ error = xchk_nlinks_get_record(xnc, ip->i_ino, &obs);
+ if (error)
+ goto out_scanlock;
+ total_links = xchk_nlink_total(&obs);
+
+ /* Cannot set more than the maxiumum possible link count. */
+ if (total_links > U32_MAX) {
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ goto out_scanlock;
+ }
+
+ /* Non-directories cannot have directories pointing up to them. */
+ if (!S_ISDIR(VFS_I(ip)->i_mode) && obs.child > 0) {
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ goto out_scanlock;
+ }
+
+ /*
+ * Directories should have at least one "child" (the dot entry)
+ * pointing up to them.
+ */
+ if (S_ISDIR(VFS_I(ip)->i_mode) && obs.child == 0) {
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ goto out_scanlock;
+ }
+
+ if (xrep_nlinks_is_orphaned(sc, ip, &obs)) {
+ obs.parent++;
+ 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. If the inode agrees, we
+ * have nothing further to do. If not, the inode has a nonzero link
+ * count, and we don't have an orphanage to adopt the child. Dropping
+ * a live inode's link count to zero is evil, so leave it alone.
+ */
+ if (total_links == 0) {
+ if (VFS_I(ip)->i_nlink != 0)
+ trace_xrep_nlinks_unfixable_inode(mp, ip, &obs);
+ goto out_scanlock;
+ }
+
+ /* If the inode has the correct link count and isn't orphaned, exit. */
+ if (total_links == VFS_I(ip)->i_nlink && !orphan)
+ goto out_scanlock;
+ mutex_unlock(&xnc->lock);
+
+ /* 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(orph);
+ if (error)
+ goto out_trans;
+ }
+ set_nlink(VFS_I(ip), total_links);
+ xfs_trans_log_inode(sc->tp, ip, XFS_ILOG_CORE);
+
+ 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.
@@ -172,7 +377,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 7a56244a8708..50c0364b17c4 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -80,7 +80,7 @@ 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_rtbitmap(struct xfs_scrub *sc, unsigned int *resblks);
-int xrep_setup_nlinks(struct xfs_scrub *sc);
+int xrep_setup_nlinks(struct xfs_scrub *sc, unsigned int *buf_bytes);
int xrep_xattr_reset_fork(struct xfs_scrub *sc, struct xfs_inode *ip);
@@ -288,7 +288,12 @@ xrep_setup_xattr(
#define xrep_setup_directory xrep_setup_xattr
#define xrep_setup_parent xrep_setup_xattr
-#define xrep_setup_nlinks xrep_setup_xattr
+
+static inline int
+xrep_setup_nlinks(struct xfs_scrub *sc, unsigned int *buf_bytes)
+{
+ return 0;
+}
#define xrep_revalidate_allocbt (NULL)
#define xrep_revalidate_iallocbt (NULL)
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index e0f7cd0de061..9878ee415e8b 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -2078,6 +2078,29 @@ TRACE_EVENT(xrep_rtrefc_found,
DEFINE_SCRUB_NLINK_DIFF_EVENT(xrep_nlinks_update_inode);
DEFINE_SCRUB_NLINK_DIFF_EVENT(xrep_nlinks_unfixable_inode);
+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, parent_nlinks)
+ __field(xfs_nlink_t, child_nlinks)
+ ),
+ TP_fast_assign(
+ __entry->dev = mp->m_super->s_dev;
+ __entry->ino = ino;
+ __entry->parent_nlinks = obs->parent;
+ __entry->child_nlinks = obs->child;
+ ),
+ TP_printk("dev %d:%d ino 0x%llx parent_links %u child_links %u",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino,
+ __entry->parent_nlinks,
+ __entry->child_nlinks)
+);
+
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */
#endif /* _TRACE_XFS_SCRUB_TRACE_H */