]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blobdiff - scrub/scrub.c
xfs_scrub: remove moveon from phase 3 functions
[thirdparty/xfsprogs-dev.git] / scrub / scrub.c
index 9e8267524cba5c473797129682c53926c50f6db1..9aac37377084ea25b05b36d7584802f587b782fb 100644 (file)
@@ -1,21 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * Copyright (C) 2018 Oracle.  All Rights Reserved.
- *
  * Author: Darrick J. Wong <darrick.wong@oracle.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it would be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write the Free Software Foundation,
- * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 #include "xfs.h"
 #include <stdint.h>
 #include <sys/types.h>
 #include <sys/statvfs.h>
 #include "list.h"
-#include "path.h"
+#include "libfrog/paths.h"
+#include "libfrog/fsgeom.h"
+#include "libfrog/scrub.h"
 #include "xfs_scrub.h"
 #include "common.h"
 #include "progress.h"
 #include "scrub.h"
 #include "xfs_errortag.h"
+#include "repair.h"
+#include "descr.h"
 
 /* Online scrub and repair wrappers. */
 
-/* Type info and names for the scrub types. */
-enum scrub_type {
-       ST_NONE,        /* disabled */
-       ST_AGHEADER,    /* per-AG header */
-       ST_PERAG,       /* per-AG metadata */
-       ST_FS,          /* per-FS metadata */
-       ST_INODE,       /* per-inode metadata */
-};
-struct scrub_descr {
-       const char      *name;
-       enum scrub_type type;
-};
-
-/* These must correspond to XFS_SCRUB_TYPE_ */
-static const struct scrub_descr scrubbers[XFS_SCRUB_TYPE_NR] = {
-       [XFS_SCRUB_TYPE_PROBE] =
-               {"metadata",                            ST_NONE},
-       [XFS_SCRUB_TYPE_SB] =
-               {"superblock",                          ST_AGHEADER},
-       [XFS_SCRUB_TYPE_AGF] =
-               {"free space header",                   ST_AGHEADER},
-       [XFS_SCRUB_TYPE_AGFL] =
-               {"free list",                           ST_AGHEADER},
-       [XFS_SCRUB_TYPE_AGI] =
-               {"inode header",                        ST_AGHEADER},
-       [XFS_SCRUB_TYPE_BNOBT] =
-               {"freesp by block btree",               ST_PERAG},
-       [XFS_SCRUB_TYPE_CNTBT] =
-               {"freesp by length btree",              ST_PERAG},
-       [XFS_SCRUB_TYPE_INOBT] =
-               {"inode btree",                         ST_PERAG},
-       [XFS_SCRUB_TYPE_FINOBT] =
-               {"free inode btree",                    ST_PERAG},
-       [XFS_SCRUB_TYPE_RMAPBT] =
-               {"reverse mapping btree",               ST_PERAG},
-       [XFS_SCRUB_TYPE_REFCNTBT] =
-               {"reference count btree",               ST_PERAG},
-       [XFS_SCRUB_TYPE_INODE] =
-               {"inode record",                        ST_INODE},
-       [XFS_SCRUB_TYPE_BMBTD] =
-               {"data block map",                      ST_INODE},
-       [XFS_SCRUB_TYPE_BMBTA] =
-               {"attr block map",                      ST_INODE},
-       [XFS_SCRUB_TYPE_BMBTC] =
-               {"CoW block map",                       ST_INODE},
-       [XFS_SCRUB_TYPE_DIR] =
-               {"directory entries",                   ST_INODE},
-       [XFS_SCRUB_TYPE_XATTR] =
-               {"extended attributes",                 ST_INODE},
-       [XFS_SCRUB_TYPE_SYMLINK] =
-               {"symbolic link",                       ST_INODE},
-       [XFS_SCRUB_TYPE_PARENT] =
-               {"parent pointer",                      ST_INODE},
-       [XFS_SCRUB_TYPE_RTBITMAP] =
-               {"realtime bitmap",                     ST_FS},
-       [XFS_SCRUB_TYPE_RTSUM] =
-               {"realtime summary",                    ST_FS},
-       [XFS_SCRUB_TYPE_UQUOTA] =
-               {"user quotas",                         ST_FS},
-       [XFS_SCRUB_TYPE_GQUOTA] =
-               {"group quotas",                        ST_FS},
-       [XFS_SCRUB_TYPE_PQUOTA] =
-               {"project quotas",                      ST_FS},
-};
-
 /* Format a scrub description. */
-static void
+static int
 format_scrub_descr(
+       struct scrub_ctx                *ctx,
        char                            *buf,
        size_t                          buflen,
-       struct xfs_scrub_metadata       *meta,
-       const struct scrub_descr        *sc)
+       void                            *where)
 {
+       struct xfs_scrub_metadata       *meta = where;
+       const struct xfrog_scrub_descr  *sc = &xfrog_scrubbers[meta->sm_type];
+
        switch (sc->type) {
-       case ST_AGHEADER:
-       case ST_PERAG:
-               snprintf(buf, buflen, _("AG %u %s"), meta->sm_agno,
-                               _(sc->name));
+       case XFROG_SCRUB_TYPE_AGHEADER:
+       case XFROG_SCRUB_TYPE_PERAG:
+               return snprintf(buf, buflen, _("AG %u %s"), meta->sm_agno,
+                               _(sc->descr));
                break;
-       case ST_INODE:
-               snprintf(buf, buflen, _("Inode %"PRIu64" %s"),
-                               (uint64_t)meta->sm_ino, _(sc->name));
+       case XFROG_SCRUB_TYPE_INODE:
+               return scrub_render_ino_descr(ctx, buf, buflen,
+                               meta->sm_ino, meta->sm_gen, "%s",
+                               _(sc->descr));
                break;
-       case ST_FS:
-               snprintf(buf, buflen, _("%s"), _(sc->name));
+       case XFROG_SCRUB_TYPE_FS:
+               return snprintf(buf, buflen, _("%s"), _(sc->descr));
                break;
-       case ST_NONE:
+       case XFROG_SCRUB_TYPE_NONE:
                assert(0);
                break;
        }
+       return -1;
 }
 
 /* Predicates for scrub flag state. */
@@ -168,43 +98,45 @@ static inline bool needs_repair(struct xfs_scrub_metadata *sm)
 static inline void
 xfs_scrub_warn_incomplete_scrub(
        struct scrub_ctx                *ctx,
-       const char                      *descr,
+       struct descr                    *dsc,
        struct xfs_scrub_metadata       *meta)
 {
        if (is_incomplete(meta))
-               str_info(ctx, descr, _("Check incomplete."));
+               str_info(ctx, descr_render(dsc), _("Check incomplete."));
 
        if (is_suspicious(meta)) {
                if (debug)
-                       str_info(ctx, descr, _("Possibly suspect metadata."));
+                       str_info(ctx, descr_render(dsc),
+                                       _("Possibly suspect metadata."));
                else
-                       str_warn(ctx, descr, _("Possibly suspect metadata."));
+                       str_warn(ctx, descr_render(dsc),
+                                       _("Possibly suspect metadata."));
        }
 
        if (xref_failed(meta))
-               str_info(ctx, descr, _("Cross-referencing failed."));
+               str_info(ctx, descr_render(dsc),
+                               _("Cross-referencing failed."));
 }
 
 /* Do a read-only check of some metadata. */
 static enum check_outcome
 xfs_check_metadata(
        struct scrub_ctx                *ctx,
-       int                             fd,
        struct xfs_scrub_metadata       *meta,
        bool                            is_inode)
 {
-       char                            buf[DESCR_BUFSZ];
+       DEFINE_DESCR(dsc, ctx, format_scrub_descr);
        unsigned int                    tries = 0;
        int                             code;
        int                             error;
 
        assert(!debug_tweak_on("XFS_SCRUB_NO_KERNEL"));
        assert(meta->sm_type < XFS_SCRUB_TYPE_NR);
-       format_scrub_descr(buf, DESCR_BUFSZ, meta, &scrubbers[meta->sm_type]);
+       descr_set(&dsc, meta);
 
-       dbg_printf("check %s flags %xh\n", buf, meta->sm_flags);
+       dbg_printf("check %s flags %xh\n", descr_render(&dsc), meta->sm_flags);
 retry:
-       error = ioctl(fd, XFS_IOC_SCRUB_METADATA, meta);
+       error = xfrog_scrub_metadata(&ctx->mnt, meta);
        if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR") && !error)
                meta->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
        if (error) {
@@ -215,13 +147,13 @@ retry:
                        return CHECK_DONE;
                case ESHUTDOWN:
                        /* FS already crashed, give up. */
-                       str_info(ctx, buf,
+                       str_error(ctx, descr_render(&dsc),
 _("Filesystem is shut down, aborting."));
                        return CHECK_ABORT;
                case EIO:
                case ENOMEM:
                        /* Abort on I/O errors or insufficient memory. */
-                       str_errno(ctx, buf);
+                       str_errno(ctx, descr_render(&dsc));
                        return CHECK_ABORT;
                case EDEADLOCK:
                case EBUSY:
@@ -231,12 +163,11 @@ _("Filesystem is shut down, aborting."));
                         * The first two should never escape the kernel,
                         * and the other two should be reported via sm_flags.
                         */
-                       str_info(ctx, buf,
-_("Kernel bug!  errno=%d"), code);
+                       str_liberror(ctx, code, _("Kernel bug"));
                        /* fall through */
                default:
                        /* Operational error. */
-                       str_errno(ctx, buf);
+                       str_errno(ctx, descr_render(&dsc));
                        return CHECK_DONE;
                }
        }
@@ -254,7 +185,7 @@ _("Kernel bug!  errno=%d"), code);
        }
 
        /* Complain about incomplete or suspicious metadata. */
-       xfs_scrub_warn_incomplete_scrub(ctx, buf, meta);
+       xfs_scrub_warn_incomplete_scrub(ctx, &dsc, meta);
 
        /*
         * If we need repairs or there were discrepancies, schedule a
@@ -262,7 +193,7 @@ _("Kernel bug!  errno=%d"), code);
         */
        if (is_corrupt(meta) || xref_disagrees(meta)) {
                if (ctx->mode < SCRUB_MODE_REPAIR) {
-                       str_error(ctx, buf,
+                       str_corrupt(ctx, descr_render(&dsc),
 _("Repairs are required."));
                        return CHECK_DONE;
                }
@@ -278,7 +209,7 @@ _("Repairs are required."));
                if (ctx->mode != SCRUB_MODE_REPAIR) {
                        if (!is_inode) {
                                /* AG or FS metadata, always warn. */
-                               str_info(ctx, buf,
+                               str_info(ctx, descr_render(&dsc),
 _("Optimization is possible."));
                        } else if (!ctx->preen_triggers[meta->sm_type]) {
                                /* File metadata, only warn once per type. */
@@ -310,128 +241,192 @@ xfs_scrub_report_preen_triggers(
                        ctx->preen_triggers[i] = false;
                        pthread_mutex_unlock(&ctx->lock);
                        str_info(ctx, ctx->mntpoint,
-_("Optimizations of %s are possible."), scrubbers[i].name);
+_("Optimizations of %s are possible."), _(xfrog_scrubbers[i].descr));
                } else {
                        pthread_mutex_unlock(&ctx->lock);
                }
        }
 }
 
-/* Scrub metadata, saving corruption reports for later. */
-static bool
-xfs_scrub_metadata(
+/* Save a scrub context for later repairs. */
+static int
+xfs_scrub_save_repair(
        struct scrub_ctx                *ctx,
-       enum scrub_type                 scrub_type,
-       xfs_agnumber_t                  agno)
+       struct action_list              *alist,
+       struct xfs_scrub_metadata       *meta)
 {
-       struct xfs_scrub_metadata       meta = {0};
-       const struct scrub_descr        *sc;
-       enum check_outcome              fix;
-       int                             type;
+       struct action_item              *aitem;
 
-       sc = scrubbers;
-       for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) {
-               if (sc->type != scrub_type)
-                       continue;
+       /* Schedule this item for later repairs. */
+       aitem = malloc(sizeof(struct action_item));
+       if (!aitem) {
+               str_errno(ctx, _("adding item to repair list"));
+               return errno;
+       }
 
-               meta.sm_type = type;
-               meta.sm_flags = 0;
-               meta.sm_agno = agno;
-               background_sleep();
-
-               /* Check the item. */
-               fix = xfs_check_metadata(ctx, ctx->mnt_fd, &meta, false);
-               progress_add(1);
-               switch (fix) {
-               case CHECK_ABORT:
-                       return false;
-               case CHECK_REPAIR:
-                       /* fall through */
-               case CHECK_DONE:
-                       continue;
-               case CHECK_RETRY:
-                       abort();
-                       break;
-               }
+       memset(aitem, 0, sizeof(*aitem));
+       aitem->type = meta->sm_type;
+       aitem->flags = meta->sm_flags;
+       switch (xfrog_scrubbers[meta->sm_type].type) {
+       case XFROG_SCRUB_TYPE_AGHEADER:
+       case XFROG_SCRUB_TYPE_PERAG:
+               aitem->agno = meta->sm_agno;
+               break;
+       case XFROG_SCRUB_TYPE_INODE:
+               aitem->ino = meta->sm_ino;
+               aitem->gen = meta->sm_gen;
+               break;
+       default:
+               break;
        }
 
-       return true;
+       action_list_add(alist, aitem);
+       return 0;
 }
 
 /*
- * Scrub primary superblock.  This will be useful if we ever need to hook
- * a filesystem-wide pre-scrub activity off of the sb 0 scrubber (which
- * currently does nothing).
+ * Scrub a single XFS_SCRUB_TYPE_*, saving corruption reports for later.
+ *
+ * Returns 0 for success.  If errors occur, this function will log them and
+ * return a positive error code.
  */
-bool
-xfs_scrub_primary_super(
-       struct scrub_ctx                *ctx)
+static int
+xfs_scrub_meta_type(
+       struct scrub_ctx                *ctx,
+       unsigned int                    type,
+       xfs_agnumber_t                  agno,
+       struct action_list              *alist)
 {
        struct xfs_scrub_metadata       meta = {
-               .sm_type = XFS_SCRUB_TYPE_SB,
+               .sm_type                = type,
+               .sm_agno                = agno,
        };
        enum check_outcome              fix;
+       int                             ret;
+
+       background_sleep();
 
        /* Check the item. */
-       fix = xfs_check_metadata(ctx, ctx->mnt_fd, &meta, false);
+       fix = xfs_check_metadata(ctx, &meta, false);
+       progress_add(1);
+
        switch (fix) {
        case CHECK_ABORT:
-               return false;
+               return ECANCELED;
        case CHECK_REPAIR:
+               ret = xfs_scrub_save_repair(ctx, alist, &meta);
+               if (ret)
+                       return ret;
                /* fall through */
        case CHECK_DONE:
-               return true;
-       case CHECK_RETRY:
+               return 0;
+       default:
+               /* CHECK_RETRY should never happen. */
                abort();
-               break;
+       }
+}
+
+/*
+ * Scrub all metadata types that are assigned to the given XFROG_SCRUB_TYPE_*,
+ * saving corruption reports for later.  This should not be used for
+ * XFROG_SCRUB_TYPE_INODE or for checking summary metadata.
+ */
+static bool
+xfs_scrub_all_types(
+       struct scrub_ctx                *ctx,
+       enum xfrog_scrub_type           scrub_type,
+       xfs_agnumber_t                  agno,
+       struct action_list              *alist)
+{
+       const struct xfrog_scrub_descr  *sc;
+       unsigned int                    type;
+
+       sc = xfrog_scrubbers;
+       for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) {
+               int                     ret;
+
+               if (sc->type != scrub_type)
+                       continue;
+               if (sc->flags & XFROG_SCRUB_DESCR_SUMMARY)
+                       continue;
+
+               ret = xfs_scrub_meta_type(ctx, type, agno, alist);
+               if (ret)
+                       return ret;
        }
 
-       return true;
+       return 0;
+}
+
+/*
+ * Scrub primary superblock.  This will be useful if we ever need to hook
+ * a filesystem-wide pre-scrub activity off of the sb 0 scrubber (which
+ * currently does nothing).  If errors occur, this function will log them and
+ * return nonzero.
+ */
+int
+xfs_scrub_primary_super(
+       struct scrub_ctx                *ctx,
+       struct action_list              *alist)
+{
+       return xfs_scrub_meta_type(ctx, XFS_SCRUB_TYPE_SB, 0, alist);
 }
 
 /* Scrub each AG's header blocks. */
-bool
+int
 xfs_scrub_ag_headers(
        struct scrub_ctx                *ctx,
-       xfs_agnumber_t                  agno)
+       xfs_agnumber_t                  agno,
+       struct action_list              *alist)
 {
-       return xfs_scrub_metadata(ctx, ST_AGHEADER, agno);
+       return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_AGHEADER, agno, alist);
 }
 
 /* Scrub each AG's metadata btrees. */
-bool
+int
 xfs_scrub_ag_metadata(
        struct scrub_ctx                *ctx,
-       xfs_agnumber_t                  agno)
+       xfs_agnumber_t                  agno,
+       struct action_list              *alist)
 {
-       return xfs_scrub_metadata(ctx, ST_PERAG, agno);
+       return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_PERAG, agno, alist);
 }
 
 /* Scrub whole-FS metadata btrees. */
-bool
+int
 xfs_scrub_fs_metadata(
-       struct scrub_ctx                *ctx)
+       struct scrub_ctx                *ctx,
+       struct action_list              *alist)
 {
-       return xfs_scrub_metadata(ctx, ST_FS, 0);
+       return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_FS, 0, alist);
+}
+
+/* Scrub FS summary metadata. */
+int
+xfs_scrub_fs_summary(
+       struct scrub_ctx                *ctx,
+       struct action_list              *alist)
+{
+       return xfs_scrub_meta_type(ctx, XFS_SCRUB_TYPE_FSCOUNTERS, 0, alist);
 }
 
 /* How many items do we have to check? */
 unsigned int
-xfs_scrub_estimate_ag_work(
+scrub_estimate_ag_work(
        struct scrub_ctx                *ctx)
 {
-       const struct scrub_descr        *sc;
+       const struct xfrog_scrub_descr  *sc;
        int                             type;
        unsigned int                    estimate = 0;
 
-       sc = scrubbers;
+       sc = xfrog_scrubbers;
        for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) {
                switch (sc->type) {
-               case ST_AGHEADER:
-               case ST_PERAG:
-                       estimate += ctx->geo.agcount;
+               case XFROG_SCRUB_TYPE_AGHEADER:
+               case XFROG_SCRUB_TYPE_PERAG:
+                       estimate += ctx->mnt.fsgeom.agcount;
                        break;
-               case ST_FS:
+               case XFROG_SCRUB_TYPE_FS:
                        estimate++;
                        break;
                default:
@@ -441,116 +436,123 @@ xfs_scrub_estimate_ag_work(
        return estimate;
 }
 
-/* Scrub inode metadata. */
-static bool
+/*
+ * Scrub inode metadata.  If errors occur, this function will log them and
+ * return nonzero.
+ */
+static int
 __xfs_scrub_file(
        struct scrub_ctx                *ctx,
        uint64_t                        ino,
        uint32_t                        gen,
-       int                             fd,
-       unsigned int                    type)
+       unsigned int                    type,
+       struct action_list              *alist)
 {
        struct xfs_scrub_metadata       meta = {0};
        enum check_outcome              fix;
 
        assert(type < XFS_SCRUB_TYPE_NR);
-       assert(scrubbers[type].type == ST_INODE);
+       assert(xfrog_scrubbers[type].type == XFROG_SCRUB_TYPE_INODE);
 
        meta.sm_type = type;
        meta.sm_ino = ino;
        meta.sm_gen = gen;
 
        /* Scrub the piece of metadata. */
-       fix = xfs_check_metadata(ctx, fd, &meta, true);
+       fix = xfs_check_metadata(ctx, &meta, true);
        if (fix == CHECK_ABORT)
-               return false;
+               return ECANCELED;
        if (fix == CHECK_DONE)
-               return true;
+               return 0;
 
-       return true;
+       return xfs_scrub_save_repair(ctx, alist, &meta);
 }
 
-bool
+int
 xfs_scrub_inode_fields(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_INODE);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_INODE, alist);
 }
 
-bool
+int
 xfs_scrub_data_fork(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_BMBTD);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTD, alist);
 }
 
-bool
+int
 xfs_scrub_attr_fork(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_BMBTA);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTA, alist);
 }
 
-bool
+int
 xfs_scrub_cow_fork(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_BMBTC);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTC, alist);
 }
 
-bool
+int
 xfs_scrub_dir(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_DIR);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_DIR, alist);
 }
 
-bool
+int
 xfs_scrub_attr(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_XATTR);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_XATTR, alist);
 }
 
-bool
+int
 xfs_scrub_symlink(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_SYMLINK);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_SYMLINK, alist);
 }
 
-bool
+int
 xfs_scrub_parent(
        struct scrub_ctx        *ctx,
        uint64_t                ino,
        uint32_t                gen,
-       int                     fd)
+       struct action_list      *alist)
 {
-       return __xfs_scrub_file(ctx, ino, gen, fd, XFS_SCRUB_TYPE_PARENT);
+       return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_PARENT, alist);
 }
 
-/* Test the availability of a kernel scrub command. */
+/*
+ * Test the availability of a kernel scrub command.  If errors occur (or the
+ * scrub ioctl is rejected) the errors will be logged and this function will
+ * return false.
+ */
 static bool
 __xfs_scrub_test(
        struct scrub_ctx                *ctx,
@@ -558,20 +560,24 @@ __xfs_scrub_test(
        bool                            repair)
 {
        struct xfs_scrub_metadata       meta = {0};
+       struct xfs_error_injection      inject;
        static bool                     injected;
        int                             error;
 
        if (debug_tweak_on("XFS_SCRUB_NO_KERNEL"))
                return false;
        if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR") && !injected) {
-               str_info(ctx, "XFS_SCRUB_FORCE_REPAIR", "Not supported.");
-               return false;
+               inject.fd = ctx->mnt.fd;
+               inject.errtag = XFS_ERRTAG_FORCE_SCRUB_REPAIR;
+               error = ioctl(ctx->mnt.fd, XFS_IOC_ERROR_INJECTION, &inject);
+               if (error == 0)
+                       injected = true;
        }
 
        meta.sm_type = type;
        if (repair)
                meta.sm_flags |= XFS_SCRUB_IFLAG_REPAIR;
-       error = ioctl(ctx->mnt_fd, XFS_IOC_SCRUB_METADATA, &meta);
+       error = xfrog_scrub_metadata(&ctx->mnt, &meta);
        if (!error)
                return true;
        switch (errno) {
@@ -588,7 +594,7 @@ _("Filesystem is mounted norecovery; cannot proceed."));
                if (debug || verbose)
                        str_info(ctx, ctx->mntpoint,
 _("Kernel %s %s facility not detected."),
-                                       _(scrubbers[type].name),
+                                       _(xfrog_scrubbers[type].descr),
                                        repair ? _("repair") : _("scrub"));
                return false;
        case ENOENT:
@@ -663,88 +669,89 @@ enum check_outcome
 xfs_repair_metadata(
        struct scrub_ctx                *ctx,
        int                             fd,
-       struct repair_item              *ri,
+       struct action_item              *aitem,
        unsigned int                    repair_flags)
 {
-       char                            buf[DESCR_BUFSZ];
        struct xfs_scrub_metadata       meta = { 0 };
        struct xfs_scrub_metadata       oldm;
+       DEFINE_DESCR(dsc, ctx, format_scrub_descr);
        int                             error;
 
-       assert(ri->type < XFS_SCRUB_TYPE_NR);
+       assert(aitem->type < XFS_SCRUB_TYPE_NR);
        assert(!debug_tweak_on("XFS_SCRUB_NO_KERNEL"));
-       meta.sm_type = ri->type;
-       meta.sm_flags = ri->flags | XFS_SCRUB_IFLAG_REPAIR;
-       switch (scrubbers[ri->type].type) {
-       case ST_AGHEADER:
-       case ST_PERAG:
-               meta.sm_agno = ri->agno;
+       meta.sm_type = aitem->type;
+       meta.sm_flags = aitem->flags | XFS_SCRUB_IFLAG_REPAIR;
+       switch (xfrog_scrubbers[aitem->type].type) {
+       case XFROG_SCRUB_TYPE_AGHEADER:
+       case XFROG_SCRUB_TYPE_PERAG:
+               meta.sm_agno = aitem->agno;
                break;
-       case ST_INODE:
-               meta.sm_ino = ri->ino;
-               meta.sm_gen = ri->gen;
+       case XFROG_SCRUB_TYPE_INODE:
+               meta.sm_ino = aitem->ino;
+               meta.sm_gen = aitem->gen;
                break;
        default:
                break;
        }
 
-       /*
-        * If this is a preen operation but we're only repairing
-        * critical items, defer the preening until later.
-        */
-       if (!needs_repair(&meta) && (repair_flags & XRM_REPAIR_ONLY))
+       if (!is_corrupt(&meta) && (repair_flags & XRM_REPAIR_ONLY))
                return CHECK_RETRY;
 
        memcpy(&oldm, &meta, sizeof(oldm));
-       format_scrub_descr(buf, DESCR_BUFSZ, &meta, &scrubbers[meta.sm_type]);
+       descr_set(&dsc, &oldm);
 
        if (needs_repair(&meta))
-               str_info(ctx, buf, _("Attempting repair."));
+               str_info(ctx, descr_render(&dsc), _("Attempting repair."));
        else if (debug || verbose)
-               str_info(ctx, buf, _("Attempting optimization."));
+               str_info(ctx, descr_render(&dsc),
+                               _("Attempting optimization."));
 
-       error = ioctl(fd, XFS_IOC_SCRUB_METADATA, &meta);
-       /*
-        * If the caller doesn't want us to complain, tell the caller to
-        * requeue the repair for later and don't say a thing.
-        */
-       if (!(repair_flags & XRM_NOFIX_COMPLAIN) &&
-           (error || needs_repair(&meta)))
-               return CHECK_RETRY;
+       error = xfrog_scrub_metadata(&ctx->mnt, &meta);
        if (error) {
                switch (errno) {
                case EDEADLOCK:
                case EBUSY:
                        /* Filesystem is busy, try again later. */
                        if (debug || verbose)
-                               str_info(ctx, buf,
+                               str_info(ctx, descr_render(&dsc),
 _("Filesystem is busy, deferring repair."));
                        return CHECK_RETRY;
                case ESHUTDOWN:
                        /* Filesystem is already shut down, abort. */
-                       str_info(ctx, buf,
+                       str_error(ctx, descr_render(&dsc),
 _("Filesystem is shut down, aborting."));
                        return CHECK_ABORT;
                case ENOTTY:
                case EOPNOTSUPP:
                        /*
-                        * If we forced repairs, don't complain if kernel
-                        * doesn't know how to fix.
+                        * If we're in no-complain mode, requeue the check for
+                        * later.  It's possible that an error in another
+                        * component caused us to flag an error in this
+                        * component.  Even if the kernel didn't think it
+                        * could fix this, it's at least worth trying the scan
+                        * again to see if another repair fixed it.
+                        */
+                       if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED))
+                               return CHECK_RETRY;
+                       /*
+                        * If we forced repairs or this is a preen, don't
+                        * error out if the kernel doesn't know how to fix.
                         */
-                       if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR"))
+                       if (is_unoptimized(&oldm) ||
+                           debug_tweak_on("XFS_SCRUB_FORCE_REPAIR"))
                                return CHECK_DONE;
                        /* fall through */
                case EINVAL:
                        /* Kernel doesn't know how to repair this? */
-                       str_error(ctx, buf,
+                       str_corrupt(ctx, descr_render(&dsc),
 _("Don't know how to fix; offline repair required."));
                        return CHECK_DONE;
                case EROFS:
                        /* Read-only filesystem, can't fix. */
                        if (verbose || debug || needs_repair(&oldm))
-                               str_info(ctx, buf,
+                               str_error(ctx, descr_render(&dsc),
 _("Read-only filesystem; cannot make changes."));
-                       return CHECK_DONE;
+                       return CHECK_ABORT;
                case ENOENT:
                        /* Metadata not present, just skip it. */
                        return CHECK_DONE;
@@ -755,24 +762,38 @@ _("Read-only filesystem; cannot make changes."));
                                return CHECK_DONE;
                        /* fall through */
                default:
-                       /* Operational error. */
-                       str_errno(ctx, buf);
+                       /*
+                        * Operational error.  If the caller doesn't want us
+                        * to complain about repair failures, tell the caller
+                        * to requeue the repair for later and don't say a
+                        * thing.  Otherwise, print error and bail out.
+                        */
+                       if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED))
+                               return CHECK_RETRY;
+                       str_errno(ctx, descr_render(&dsc));
                        return CHECK_DONE;
                }
        }
-       if (repair_flags & XRM_NOFIX_COMPLAIN)
-               xfs_scrub_warn_incomplete_scrub(ctx, buf, &meta);
+       if (repair_flags & XRM_COMPLAIN_IF_UNFIXED)
+               xfs_scrub_warn_incomplete_scrub(ctx, &dsc, &meta);
        if (needs_repair(&meta)) {
-               /* Still broken, try again or fix offline. */
-               if (repair_flags & XRM_NOFIX_COMPLAIN)
-                       str_error(ctx, buf,
+               /*
+                * Still broken; if we've been told not to complain then we
+                * just requeue this and try again later.  Otherwise we
+                * log the error loudly and don't try again.
+                */
+               if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED))
+                       return CHECK_RETRY;
+               str_corrupt(ctx, descr_render(&dsc),
 _("Repair unsuccessful; offline repair required."));
        } else {
                /* Clean operation, no corruption detected. */
                if (needs_repair(&oldm))
-                       record_repair(ctx, buf, _("Repairs successful."));
+                       record_repair(ctx, descr_render(&dsc),
+                                       _("Repairs successful."));
                else
-                       record_preen(ctx, buf, _("Optimization successful."));
+                       record_preen(ctx, descr_render(&dsc),
+                                       _("Optimization successful."));
        }
        return CHECK_DONE;
 }