xfs: repair the inode core and forks of a metadata inode
authorDarrick J. Wong <djwong@kernel.org>
Fri, 15 Dec 2023 18:03:42 +0000 (10:03 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Fri, 15 Dec 2023 18:03:42 +0000 (10:03 -0800)
Add a helper function to repair the core and forks of a metadata inode,
so that we can get move onto the task of repairing higher level metadata
that lives in an inode.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/scrub/bmap_repair.c
fs/xfs/scrub/repair.c
fs/xfs/scrub/repair.h

index a8d6415b1c38812afccede4862ea60bf1513cec6..a4bb89fdd51066eeded6cabd4a81492ea5325047 100644 (file)
@@ -86,6 +86,9 @@ struct xrep_bmap {
 
        /* What d the REFLINK flag be set when the repair is over? */
        enum reflink_scan_state reflink_scan;
+
+       /* Do we allow unwritten extents? */
+       bool                    allow_unwritten;
 };
 
 /* Is this space extent shared?  Flag the inode if it is. */
@@ -262,6 +265,10 @@ xrep_bmap_walk_rmap(
            !(rec->rm_flags & XFS_RMAP_ATTR_FORK))
                return 0;
 
+       /* Reject unwritten extents if we don't allow those. */
+       if ((rec->rm_flags & XFS_RMAP_UNWRITTEN) && !rb->allow_unwritten)
+               return -EFSCORRUPTED;
+
        fsbno = XFS_AGB_TO_FSB(mp, cur->bc_ag.pag->pag_agno,
                        rec->rm_startblock);
 
@@ -780,10 +787,11 @@ xrep_bmap_init_reflink_scan(
 }
 
 /* Repair an inode fork. */
-STATIC int
+int
 xrep_bmap(
        struct xfs_scrub        *sc,
-       int                     whichfork)
+       int                     whichfork,
+       bool                    allow_unwritten)
 {
        struct xrep_bmap        *rb;
        char                    *descr;
@@ -803,6 +811,7 @@ xrep_bmap(
        rb->sc = sc;
        rb->whichfork = whichfork;
        rb->reflink_scan = xrep_bmap_init_reflink_scan(sc, whichfork);
+       rb->allow_unwritten = allow_unwritten;
 
        /* Set up enough storage to handle the max records for this fork. */
        large_extcount = xfs_has_large_extent_counts(sc->mp);
@@ -846,7 +855,7 @@ int
 xrep_bmap_data(
        struct xfs_scrub        *sc)
 {
-       return xrep_bmap(sc, XFS_DATA_FORK);
+       return xrep_bmap(sc, XFS_DATA_FORK, true);
 }
 
 /* Repair an inode's attr fork. */
@@ -854,5 +863,5 @@ int
 xrep_bmap_attr(
        struct xfs_scrub        *sc)
 {
-       return xrep_bmap(sc, XFS_ATTR_FORK);
+       return xrep_bmap(sc, XFS_ATTR_FORK, false);
 }
index 020d49b0f9b927dda37de54565c4eec1b5bc5668..745d5b8f405a91c0e3f34042cbe542b1f88e4a57 100644 (file)
@@ -29,6 +29,7 @@
 #include "xfs_defer.h"
 #include "xfs_errortag.h"
 #include "xfs_error.h"
+#include "xfs_reflink.h"
 #include "scrub/scrub.h"
 #include "scrub/common.h"
 #include "scrub/trace.h"
@@ -962,3 +963,155 @@ xrep_will_attempt(
 
        return false;
 }
+
+/* Try to fix some part of a metadata inode by calling another scrubber. */
+STATIC int
+xrep_metadata_inode_subtype(
+       struct xfs_scrub        *sc,
+       unsigned int            scrub_type)
+{
+       __u32                   smtype = sc->sm->sm_type;
+       __u32                   smflags = sc->sm->sm_flags;
+       unsigned int            sick_mask = sc->sick_mask;
+       int                     error;
+
+       /*
+        * Let's see if the inode needs repair.  We're going to open-code calls
+        * to the scrub and repair functions so that we can hang on to the
+        * resources that we already acquired instead of using the standard
+        * setup/teardown routines.
+        */
+       sc->sm->sm_flags &= ~XFS_SCRUB_FLAGS_OUT;
+       sc->sm->sm_type = scrub_type;
+
+       switch (scrub_type) {
+       case XFS_SCRUB_TYPE_INODE:
+               error = xchk_inode(sc);
+               break;
+       case XFS_SCRUB_TYPE_BMBTD:
+               error = xchk_bmap_data(sc);
+               break;
+       case XFS_SCRUB_TYPE_BMBTA:
+               error = xchk_bmap_attr(sc);
+               break;
+       default:
+               ASSERT(0);
+               error = -EFSCORRUPTED;
+       }
+       if (error)
+               goto out;
+
+       if (!xrep_will_attempt(sc))
+               goto out;
+
+       /*
+        * Repair some part of the inode.  This will potentially join the inode
+        * to the transaction.
+        */
+       switch (scrub_type) {
+       case XFS_SCRUB_TYPE_INODE:
+               error = xrep_inode(sc);
+               break;
+       case XFS_SCRUB_TYPE_BMBTD:
+               error = xrep_bmap(sc, XFS_DATA_FORK, false);
+               break;
+       case XFS_SCRUB_TYPE_BMBTA:
+               error = xrep_bmap(sc, XFS_ATTR_FORK, false);
+               break;
+       }
+       if (error)
+               goto out;
+
+       /*
+        * Finish all deferred intent items and then roll the transaction so
+        * that the inode will not be joined to the transaction when we exit
+        * the function.
+        */
+       error = xfs_defer_finish(&sc->tp);
+       if (error)
+               goto out;
+       error = xfs_trans_roll(&sc->tp);
+       if (error)
+               goto out;
+
+       /*
+        * Clear the corruption flags and re-check the metadata that we just
+        * repaired.
+        */
+       sc->sm->sm_flags &= ~XFS_SCRUB_FLAGS_OUT;
+
+       switch (scrub_type) {
+       case XFS_SCRUB_TYPE_INODE:
+               error = xchk_inode(sc);
+               break;
+       case XFS_SCRUB_TYPE_BMBTD:
+               error = xchk_bmap_data(sc);
+               break;
+       case XFS_SCRUB_TYPE_BMBTA:
+               error = xchk_bmap_attr(sc);
+               break;
+       }
+       if (error)
+               goto out;
+
+       /* If corruption persists, the repair has failed. */
+       if (xchk_needs_repair(sc->sm)) {
+               error = -EFSCORRUPTED;
+               goto out;
+       }
+out:
+       sc->sick_mask = sick_mask;
+       sc->sm->sm_type = smtype;
+       sc->sm->sm_flags = smflags;
+       return error;
+}
+
+/*
+ * Repair the ondisk forks of a metadata inode.  The caller must ensure that
+ * sc->ip points to the metadata inode and the ILOCK is held on that inode.
+ * The inode must not be joined to the transaction before the call, and will
+ * not be afterwards.
+ */
+int
+xrep_metadata_inode_forks(
+       struct xfs_scrub        *sc)
+{
+       bool                    dirty = false;
+       int                     error;
+
+       /* Repair the inode record and the data fork. */
+       error = xrep_metadata_inode_subtype(sc, XFS_SCRUB_TYPE_INODE);
+       if (error)
+               return error;
+
+       error = xrep_metadata_inode_subtype(sc, XFS_SCRUB_TYPE_BMBTD);
+       if (error)
+               return error;
+
+       /* Make sure the attr fork looks ok before we delete it. */
+       error = xrep_metadata_inode_subtype(sc, XFS_SCRUB_TYPE_BMBTA);
+       if (error)
+               return error;
+
+       /* Clear the reflink flag since metadata never shares. */
+       if (xfs_is_reflink_inode(sc->ip)) {
+               dirty = true;
+               xfs_trans_ijoin(sc->tp, sc->ip, 0);
+               error = xfs_reflink_clear_inode_flag(sc->ip, &sc->tp);
+               if (error)
+                       return error;
+       }
+
+       /*
+        * If we modified the inode, roll the transaction but don't rejoin the
+        * inode to the new transaction because xrep_bmap_data can do that.
+        */
+       if (dirty) {
+               error = xfs_trans_roll(&sc->tp);
+               if (error)
+                       return error;
+               dirty = false;
+       }
+
+       return 0;
+}
index f89c8f08b0379fd17415a6e1209f4600f0705859..f0f9c5194e8d93fb09c43f031206657adcd098ba 100644 (file)
@@ -82,6 +82,8 @@ int xrep_ino_dqattach(struct xfs_scrub *sc);
 int xrep_ino_ensure_extent_count(struct xfs_scrub *sc, int whichfork,
                xfs_extnum_t nextents);
 int xrep_reset_perag_resv(struct xfs_scrub *sc);
+int xrep_bmap(struct xfs_scrub *sc, int whichfork, bool allow_unwritten);
+int xrep_metadata_inode_forks(struct xfs_scrub *sc);
 
 /* Repair setup functions */
 int xrep_setup_ag_allocbt(struct xfs_scrub *sc);