root/fs/xfs/scrub/quotacheck.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2020-2024 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#include "xfs_platform.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_inode.h"
#include "xfs_quota.h"
#include "xfs_qm.h"
#include "xfs_icache.h"
#include "xfs_bmap_util.h"
#include "xfs_ialloc.h"
#include "xfs_ag.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/repair.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/iscan.h"
#include "scrub/quota.h"
#include "scrub/quotacheck.h"
#include "scrub/trace.h"

/*
 * Live Quotacheck
 * ===============
 *
 * Quota counters are "summary" metadata, in the sense that they are computed
 * as the summation of the block usage counts for every file on the filesystem.
 * Therefore, we compute the correct icount, bcount, and rtbcount values by
 * creating a shadow quota counter structure and walking every inode.
 */

/* Track the quota deltas for a dquot in a transaction. */
struct xqcheck_dqtrx {
        xfs_dqtype_t            q_type;
        xfs_dqid_t              q_id;

        int64_t                 icount_delta;

        int64_t                 bcount_delta;
        int64_t                 delbcnt_delta;

        int64_t                 rtbcount_delta;
        int64_t                 delrtb_delta;
};

#define XQCHECK_MAX_NR_DQTRXS   (XFS_QM_TRANS_DQTYPES * XFS_QM_TRANS_MAXDQS)

/*
 * Track the quota deltas for all dquots attached to a transaction if the
 * quota deltas are being applied to an inode that we already scanned.
 */
struct xqcheck_dqacct {
        struct rhash_head       hash;
        uintptr_t               tx_id;
        struct xqcheck_dqtrx    dqtrx[XQCHECK_MAX_NR_DQTRXS];
        unsigned int            refcount;
};

/* Free a shadow dquot accounting structure. */
static void
xqcheck_dqacct_free(
        void                    *ptr,
        void                    *arg)
{
        struct xqcheck_dqacct   *dqa = ptr;

        kfree(dqa);
}

/* Set us up to scrub quota counters. */
int
xchk_setup_quotacheck(
        struct xfs_scrub        *sc)
{
        if (!XFS_IS_QUOTA_ON(sc->mp))
                return -ENOENT;

        xchk_fsgates_enable(sc, XCHK_FSGATES_QUOTA);

        sc->buf = kzalloc_obj(struct xqcheck, XCHK_GFP_FLAGS);
        if (!sc->buf)
                return -ENOMEM;

        return xchk_setup_fs(sc);
}

/*
 * Part 1: Collecting dquot resource usage counts.  For each xfs_dquot attached
 * to each inode, we create a shadow dquot, and compute the inode count and add
 * the data/rt block usage from what we see.
 *
 * To avoid false corruption reports in part 2, any failure in this part must
 * set the INCOMPLETE flag even when a negative errno is returned.  This care
 * must be taken with certain errno values (i.e. EFSBADCRC, EFSCORRUPTED,
 * ECANCELED) that are absorbed into a scrub state flag update by
 * xchk_*_process_error.  Scrub and repair share the same incore data
 * structures, so the INCOMPLETE flag is critical to prevent a repair based on
 * insufficient information.
 *
 * Because we are scanning a live filesystem, it's possible that another thread
 * will try to update the quota counters for an inode that we've already
 * scanned.  This will cause our counts to be incorrect.  Therefore, we hook
 * the live transaction code in two places: (1) when the callers update the
 * per-transaction dqtrx structure to log quota counter updates; and (2) when
 * transaction commit actually logs those updates to the incore dquot.  By
 * shadowing transaction updates in this manner, live quotacheck can ensure
 * by locking the dquot and the shadow structure that its own copies are not
 * out of date.  Because the hook code runs in a different process context from
 * the scrub code and the scrub state flags are not accessed atomically,
 * failures in the hook code must abort the iscan and the scrubber must notice
 * the aborted scan and set the incomplete flag.
 *
 * Note that we use srcu notifier hooks to minimize the overhead when live
 * quotacheck is /not/ running.
 */

/* Update an incore dquot counter information from a live update. */
static int
xqcheck_update_incore_counts(
        struct xqcheck          *xqc,
        struct xfarray          *counts,
        xfs_dqid_t              id,
        int64_t                 inodes,
        int64_t                 nblks,
        int64_t                 rtblks)
{
        struct xqcheck_dquot    xcdq;
        int                     error;

        error = xfarray_load_sparse(counts, id, &xcdq);
        if (error)
                return error;

        xcdq.flags |= XQCHECK_DQUOT_WRITTEN;
        xcdq.icount += inodes;
        xcdq.bcount += nblks;
        xcdq.rtbcount += rtblks;

        error = xfarray_store(counts, id, &xcdq);
        if (error == -EFBIG) {
                /*
                 * EFBIG means we tried to store data at too high a byte offset
                 * in the sparse array.  IOWs, we cannot complete the check and
                 * must notify userspace that the check was incomplete.
                 */
                error = -ECANCELED;
        }
        return error;
}

/* Decide if this is the shadow dquot accounting structure for a transaction. */
static int
xqcheck_dqacct_obj_cmpfn(
        struct rhashtable_compare_arg   *arg,
        const void                      *obj)
{
        const uintptr_t                 *tx_idp = arg->key;
        const struct xqcheck_dqacct     *dqa = obj;

        if (dqa->tx_id != *tx_idp)
                return 1;
        return 0;
}

static const struct rhashtable_params xqcheck_dqacct_hash_params = {
        .min_size               = 32,
        .key_len                = sizeof(uintptr_t),
        .key_offset             = offsetof(struct xqcheck_dqacct, tx_id),
        .head_offset            = offsetof(struct xqcheck_dqacct, hash),
        .automatic_shrinking    = true,
        .obj_cmpfn              = xqcheck_dqacct_obj_cmpfn,
};

/* Find a shadow dqtrx slot for the given dquot. */
STATIC struct xqcheck_dqtrx *
xqcheck_get_dqtrx(
        struct xqcheck_dqacct   *dqa,
        xfs_dqtype_t            q_type,
        xfs_dqid_t              q_id)
{
        int                     i;

        for (i = 0; i < XQCHECK_MAX_NR_DQTRXS; i++) {
                if (dqa->dqtrx[i].q_type == 0 ||
                    (dqa->dqtrx[i].q_type == q_type &&
                     dqa->dqtrx[i].q_id == q_id))
                        return &dqa->dqtrx[i];
        }

        return NULL;
}

/*
 * Create and fill out a quota delta tracking structure to shadow the updates
 * going on in the regular quota code.
 */
static int
xqcheck_mod_live_ino_dqtrx(
        struct notifier_block           *nb,
        unsigned long                   action,
        void                            *data)
{
        struct xfs_mod_ino_dqtrx_params *p = data;
        struct xqcheck                  *xqc;
        struct xqcheck_dqacct           *dqa;
        struct xqcheck_dqtrx            *dqtrx;
        int                             error;

        xqc = container_of(nb, struct xqcheck, qhook.mod_hook.nb);

        /* Skip quota reservation fields. */
        switch (action) {
        case XFS_TRANS_DQ_BCOUNT:
        case XFS_TRANS_DQ_DELBCOUNT:
        case XFS_TRANS_DQ_ICOUNT:
        case XFS_TRANS_DQ_RTBCOUNT:
        case XFS_TRANS_DQ_DELRTBCOUNT:
                break;
        default:
                return NOTIFY_DONE;
        }

        /* Ignore dqtrx updates for quota types we don't care about. */
        switch (p->q_type) {
        case XFS_DQTYPE_USER:
                if (!xqc->ucounts)
                        return NOTIFY_DONE;
                break;
        case XFS_DQTYPE_GROUP:
                if (!xqc->gcounts)
                        return NOTIFY_DONE;
                break;
        case XFS_DQTYPE_PROJ:
                if (!xqc->pcounts)
                        return NOTIFY_DONE;
                break;
        default:
                return NOTIFY_DONE;
        }

        /* Skip inodes that haven't been scanned yet. */
        if (!xchk_iscan_want_live_update(&xqc->iscan, p->ino))
                return NOTIFY_DONE;

        /* Make a shadow quota accounting tracker for this transaction. */
        mutex_lock(&xqc->lock);
        dqa = rhashtable_lookup_fast(&xqc->shadow_dquot_acct, &p->tx_id,
                        xqcheck_dqacct_hash_params);
        if (!dqa) {
                dqa = kzalloc_obj(struct xqcheck_dqacct, XCHK_GFP_FLAGS);
                if (!dqa)
                        goto out_abort;

                dqa->tx_id = p->tx_id;
                error = rhashtable_insert_fast(&xqc->shadow_dquot_acct,
                                &dqa->hash, xqcheck_dqacct_hash_params);
                if (error)
                        goto out_abort;
        }

        /* Find the shadow dqtrx (or an empty slot) here. */
        dqtrx = xqcheck_get_dqtrx(dqa, p->q_type, p->q_id);
        if (!dqtrx)
                goto out_abort;
        if (dqtrx->q_type == 0) {
                dqtrx->q_type = p->q_type;
                dqtrx->q_id = p->q_id;
                dqa->refcount++;
        }

        /* Update counter */
        switch (action) {
        case XFS_TRANS_DQ_BCOUNT:
                dqtrx->bcount_delta += p->delta;
                break;
        case XFS_TRANS_DQ_DELBCOUNT:
                dqtrx->delbcnt_delta += p->delta;
                break;
        case XFS_TRANS_DQ_ICOUNT:
                dqtrx->icount_delta += p->delta;
                break;
        case XFS_TRANS_DQ_RTBCOUNT:
                dqtrx->rtbcount_delta += p->delta;
                break;
        case XFS_TRANS_DQ_DELRTBCOUNT:
                dqtrx->delrtb_delta += p->delta;
                break;
        }

        mutex_unlock(&xqc->lock);
        return NOTIFY_DONE;

out_abort:
        xchk_iscan_abort(&xqc->iscan);
        mutex_unlock(&xqc->lock);
        return NOTIFY_DONE;
}

/*
 * Apply the transaction quota deltas to our shadow quota accounting info when
 * the regular quota code are doing the same.
 */
static int
xqcheck_apply_live_dqtrx(
        struct notifier_block           *nb,
        unsigned long                   action,
        void                            *data)
{
        struct xfs_apply_dqtrx_params   *p = data;
        struct xqcheck                  *xqc;
        struct xqcheck_dqacct           *dqa;
        struct xqcheck_dqtrx            *dqtrx;
        struct xfarray                  *counts;
        int                             error;

        xqc = container_of(nb, struct xqcheck, qhook.apply_hook.nb);

        /* Map the dquot type to an incore counter object. */
        switch (p->q_type) {
        case XFS_DQTYPE_USER:
                counts = xqc->ucounts;
                break;
        case XFS_DQTYPE_GROUP:
                counts = xqc->gcounts;
                break;
        case XFS_DQTYPE_PROJ:
                counts = xqc->pcounts;
                break;
        default:
                return NOTIFY_DONE;
        }

        if (xchk_iscan_aborted(&xqc->iscan) || counts == NULL)
                return NOTIFY_DONE;

        /*
         * Find the shadow dqtrx for this transaction and dquot, if any deltas
         * need to be applied here.  If not, we're finished early.
         */
        mutex_lock(&xqc->lock);
        dqa = rhashtable_lookup_fast(&xqc->shadow_dquot_acct, &p->tx_id,
                        xqcheck_dqacct_hash_params);
        if (!dqa)
                goto out_unlock;
        dqtrx = xqcheck_get_dqtrx(dqa, p->q_type, p->q_id);
        if (!dqtrx || dqtrx->q_type == 0)
                goto out_unlock;

        /* Update our shadow dquot if we're committing. */
        if (action == XFS_APPLY_DQTRX_COMMIT) {
                error = xqcheck_update_incore_counts(xqc, counts, p->q_id,
                                dqtrx->icount_delta,
                                dqtrx->bcount_delta + dqtrx->delbcnt_delta,
                                dqtrx->rtbcount_delta + dqtrx->delrtb_delta);
                if (error)
                        goto out_abort;
        }

        /* Free the shadow accounting structure if that was the last user. */
        dqa->refcount--;
        if (dqa->refcount == 0) {
                error = rhashtable_remove_fast(&xqc->shadow_dquot_acct,
                                &dqa->hash, xqcheck_dqacct_hash_params);
                if (error)
                        goto out_abort;
                xqcheck_dqacct_free(dqa, NULL);
        }

        mutex_unlock(&xqc->lock);
        return NOTIFY_DONE;

out_abort:
        xchk_iscan_abort(&xqc->iscan);
out_unlock:
        mutex_unlock(&xqc->lock);
        return NOTIFY_DONE;
}

/* Record this inode's quota usage in our shadow quota counter data. */
STATIC int
xqcheck_collect_inode(
        struct xqcheck          *xqc,
        struct xfs_inode        *ip)
{
        struct xfs_trans        *tp = xqc->sc->tp;
        xfs_filblks_t           nblks, rtblks;
        uint                    ilock_flags = 0;
        xfs_dqid_t              id;
        bool                    isreg = S_ISREG(VFS_I(ip)->i_mode);
        int                     error = 0;

        if (xfs_is_metadir_inode(ip) ||
            xfs_is_quota_inode(&tp->t_mountp->m_sb, ip->i_ino)) {
                /*
                 * Quota files are never counted towards quota, so we do not
                 * need to take the lock.  Files do not switch between the
                 * metadata and regular directory trees without a reallocation,
                 * so we do not need to ILOCK them either.
                 */
                xchk_iscan_mark_visited(&xqc->iscan, ip);
                return 0;
        }

        /* Figure out the data / rt device block counts. */
        xfs_ilock(ip, XFS_IOLOCK_SHARED);
        if (isreg)
                xfs_ilock(ip, XFS_MMAPLOCK_SHARED);
        if (XFS_IS_REALTIME_INODE(ip)) {
                /*
                 * Read in the data fork for rt files so that _count_blocks
                 * can count the number of blocks allocated from the rt volume.
                 * Inodes do not track that separately.
                 */
                ilock_flags = xfs_ilock_data_map_shared(ip);
                error = xfs_iread_extents(tp, ip, XFS_DATA_FORK);
                if (error)
                        goto out_abort;
        } else {
                ilock_flags = XFS_ILOCK_SHARED;
                xfs_ilock(ip, XFS_ILOCK_SHARED);
        }
        xfs_inode_count_blocks(tp, ip, &nblks, &rtblks);

        if (xchk_iscan_aborted(&xqc->iscan)) {
                error = -ECANCELED;
                goto out_incomplete;
        }

        /* Update the shadow dquot counters. */
        mutex_lock(&xqc->lock);
        if (xqc->ucounts) {
                id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_USER);
                error = xqcheck_update_incore_counts(xqc, xqc->ucounts, id, 1,
                                nblks, rtblks);
                if (error)
                        goto out_mutex;
        }

        if (xqc->gcounts) {
                id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_GROUP);
                error = xqcheck_update_incore_counts(xqc, xqc->gcounts, id, 1,
                                nblks, rtblks);
                if (error)
                        goto out_mutex;
        }

        if (xqc->pcounts) {
                id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_PROJ);
                error = xqcheck_update_incore_counts(xqc, xqc->pcounts, id, 1,
                                nblks, rtblks);
                if (error)
                        goto out_mutex;
        }
        mutex_unlock(&xqc->lock);

        xchk_iscan_mark_visited(&xqc->iscan, ip);
        goto out_ilock;

out_mutex:
        mutex_unlock(&xqc->lock);
out_abort:
        xchk_iscan_abort(&xqc->iscan);
out_incomplete:
        xchk_set_incomplete(xqc->sc);
out_ilock:
        xfs_iunlock(ip, ilock_flags);
        if (isreg)
                xfs_iunlock(ip, XFS_MMAPLOCK_SHARED);
        xfs_iunlock(ip, XFS_IOLOCK_SHARED);
        return error;
}

/* Walk all the allocated inodes and run a quota scan on them. */
STATIC int
xqcheck_collect_counts(
        struct xqcheck          *xqc)
{
        struct xfs_scrub        *sc = xqc->sc;
        struct xfs_inode        *ip;
        int                     error;

        /*
         * Set up for a potentially lengthy filesystem scan by reducing our
         * transaction resource usage for the duration.  Specifically:
         *
         * Cancel the transaction to release the log grant space while we scan
         * the filesystem.
         *
         * Create a new empty transaction to eliminate the possibility of the
         * inode scan deadlocking on cyclical metadata.
         *
         * We pass the empty transaction to the file scanning function to avoid
         * repeatedly cycling empty transactions.  This can be done without
         * risk of deadlock between sb_internal and the IOLOCK (we take the
         * IOLOCK to quiesce the file before scanning) because empty
         * transactions do not take sb_internal.
         */
        xchk_trans_cancel(sc);
        xchk_trans_alloc_empty(sc);

        while ((error = xchk_iscan_iter(&xqc->iscan, &ip)) == 1) {
                error = xqcheck_collect_inode(xqc, ip);
                xchk_irele(sc, ip);
                if (error)
                        break;

                if (xchk_should_terminate(sc, &error))
                        break;
        }
        xchk_iscan_iter_finish(&xqc->iscan);
        if (error) {
                xchk_set_incomplete(sc);
                /*
                 * If we couldn't grab an inode that was busy with a state
                 * change, change the error code so that we exit to userspace
                 * as quickly as possible.
                 */
                if (error == -EBUSY)
                        return -ECANCELED;
                return error;
        }

        /*
         * Switch out for a real transaction in preparation for building a new
         * tree.
         */
        xchk_trans_cancel(sc);
        return xchk_setup_fs(sc);
}

/*
 * Part 2: Comparing dquot resource counters.  Walk each xfs_dquot, comparing
 * the resource usage counters against our shadow dquots; and then walk each
 * shadow dquot (that wasn't covered in the first part), comparing it against
 * the xfs_dquot.
 */

/*
 * Check the dquot data against what we observed.  Caller must hold the dquot
 * lock.
 */
STATIC int
xqcheck_compare_dquot(
        struct xqcheck          *xqc,
        xfs_dqtype_t            dqtype,
        struct xfs_dquot        *dq)
{
        struct xqcheck_dquot    xcdq;
        struct xfarray          *counts = xqcheck_counters_for(xqc, dqtype);
        int                     error;

        if (xchk_iscan_aborted(&xqc->iscan)) {
                xchk_set_incomplete(xqc->sc);
                return -ECANCELED;
        }

        mutex_lock(&dq->q_qlock);
        mutex_lock(&xqc->lock);
        error = xfarray_load_sparse(counts, dq->q_id, &xcdq);
        if (error)
                goto out_unlock;

        if (xcdq.icount != dq->q_ino.count)
                xchk_qcheck_set_corrupt(xqc->sc, dqtype, dq->q_id);

        if (xcdq.bcount != dq->q_blk.count)
                xchk_qcheck_set_corrupt(xqc->sc, dqtype, dq->q_id);

        if (xcdq.rtbcount != dq->q_rtb.count)
                xchk_qcheck_set_corrupt(xqc->sc, dqtype, dq->q_id);

        xcdq.flags |= (XQCHECK_DQUOT_COMPARE_SCANNED | XQCHECK_DQUOT_WRITTEN);
        error = xfarray_store(counts, dq->q_id, &xcdq);
        if (error == -EFBIG) {
                /*
                 * EFBIG means we tried to store data at too high a byte offset
                 * in the sparse array.  IOWs, we cannot complete the check and
                 * must notify userspace that the check was incomplete.  This
                 * should never happen outside of the collection phase.
                 */
                xchk_set_incomplete(xqc->sc);
                error = -ECANCELED;
        }
out_unlock:
        mutex_unlock(&xqc->lock);
        mutex_unlock(&dq->q_qlock);
        if (error)
                return error;

        if (xqc->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
                return -ECANCELED;

        return 0;
}

/*
 * Walk all the observed dquots, and make sure there's a matching incore
 * dquot and that its counts match ours.
 */
STATIC int
xqcheck_walk_observations(
        struct xqcheck          *xqc,
        xfs_dqtype_t            dqtype)
{
        struct xqcheck_dquot    xcdq;
        struct xfs_dquot        *dq;
        struct xfarray          *counts = xqcheck_counters_for(xqc, dqtype);
        xfarray_idx_t           cur = XFARRAY_CURSOR_INIT;
        int                     error;

        mutex_lock(&xqc->lock);
        while ((error = xfarray_iter(counts, &cur, &xcdq)) == 1) {
                xfs_dqid_t      id = cur - 1;

                if (xcdq.flags & XQCHECK_DQUOT_COMPARE_SCANNED)
                        continue;

                mutex_unlock(&xqc->lock);

                error = xfs_qm_dqget(xqc->sc->mp, id, dqtype, false, &dq);
                if (error == -ENOENT) {
                        xchk_qcheck_set_corrupt(xqc->sc, dqtype, id);
                        return 0;
                }
                if (error)
                        return error;

                error = xqcheck_compare_dquot(xqc, dqtype, dq);
                xfs_qm_dqrele(dq);
                if (error)
                        return error;

                if (xchk_should_terminate(xqc->sc, &error))
                        return error;

                mutex_lock(&xqc->lock);
        }
        mutex_unlock(&xqc->lock);

        return error;
}

/* Compare the quota counters we observed against the live dquots. */
STATIC int
xqcheck_compare_dqtype(
        struct xqcheck          *xqc,
        xfs_dqtype_t            dqtype)
{
        struct xchk_dqiter      cursor = { };
        struct xfs_scrub        *sc = xqc->sc;
        struct xfs_dquot        *dq;
        int                     error;

        if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
                return 0;

        /* If the quota CHKD flag is cleared, we need to repair this quota. */
        if (!(xfs_quota_chkd_flag(dqtype) & sc->mp->m_qflags)) {
                xchk_qcheck_set_corrupt(xqc->sc, dqtype, 0);
                return 0;
        }

        /* Compare what we observed against the actual dquots. */
        xchk_dqiter_init(&cursor, sc, dqtype);
        while ((error = xchk_dquot_iter(&cursor, &dq)) == 1) {
                error = xqcheck_compare_dquot(xqc, dqtype, dq);
                xfs_qm_dqrele(dq);
                if (error)
                        break;
        }
        if (error)
                return error;

        /* Walk all the observed dquots and compare to the incore ones. */
        return xqcheck_walk_observations(xqc, dqtype);
}

/* Tear down everything associated with a quotacheck. */
static void
xqcheck_teardown_scan(
        void                    *priv)
{
        struct xqcheck          *xqc = priv;
        struct xfs_quotainfo    *qi = xqc->sc->mp->m_quotainfo;

        /* Discourage any hook functions that might be running. */
        xchk_iscan_abort(&xqc->iscan);

        /*
         * As noted above, the apply hook is responsible for cleaning up the
         * shadow dquot accounting data when a transaction completes.  The mod
         * hook must be removed before the apply hook so that we don't
         * mistakenly leave an active shadow account for the mod hook to get
         * its hands on.  No hooks should be running after these functions
         * return.
         */
        xfs_dqtrx_hook_del(qi, &xqc->qhook);

        if (xqc->shadow_dquot_acct.key_len) {
                rhashtable_free_and_destroy(&xqc->shadow_dquot_acct,
                                xqcheck_dqacct_free, NULL);
                xqc->shadow_dquot_acct.key_len = 0;
        }

        if (xqc->pcounts) {
                xfarray_destroy(xqc->pcounts);
                xqc->pcounts = NULL;
        }

        if (xqc->gcounts) {
                xfarray_destroy(xqc->gcounts);
                xqc->gcounts = NULL;
        }

        if (xqc->ucounts) {
                xfarray_destroy(xqc->ucounts);
                xqc->ucounts = NULL;
        }

        xchk_iscan_teardown(&xqc->iscan);
        mutex_destroy(&xqc->lock);
        xqc->sc = NULL;
}

/*
 * Scan all inodes in the entire filesystem to generate quota counter data.
 * If the scan is successful, the quota data will be left alive for a repair.
 * If any error occurs, we'll tear everything down.
 */
STATIC int
xqcheck_setup_scan(
        struct xfs_scrub        *sc,
        struct xqcheck          *xqc)
{
        struct xfs_quotainfo    *qi = sc->mp->m_quotainfo;
        unsigned long long      max_dquots = XFS_DQ_ID_MAX + 1ULL;
        int                     error;

        ASSERT(xqc->sc == NULL);
        xqc->sc = sc;

        mutex_init(&xqc->lock);

        /* Retry iget every tenth of a second for up to 30 seconds. */
        xchk_iscan_start(sc, 30000, 100, &xqc->iscan);

        error = -ENOMEM;
        if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_USER)) {
                error = xfarray_create("user dquot records", max_dquots,
                                sizeof(struct xqcheck_dquot), &xqc->ucounts);
                if (error)
                        goto out_teardown;
        }

        if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_GROUP)) {
                error = xfarray_create("group dquot records", max_dquots,
                                sizeof(struct xqcheck_dquot), &xqc->gcounts);
                if (error)
                        goto out_teardown;
        }

        if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_PROJ)) {
                error = xfarray_create("project dquot records", max_dquots,
                                sizeof(struct xqcheck_dquot), &xqc->pcounts);
                if (error)
                        goto out_teardown;
        }

        /*
         * Set up hash table to map transactions to our internal shadow dqtrx
         * structures.
         */
        error = rhashtable_init(&xqc->shadow_dquot_acct,
                        &xqcheck_dqacct_hash_params);
        if (error)
                goto out_teardown;

        /*
         * Hook into the quota code.  The hook only triggers for inodes that
         * were already scanned, and the scanner thread takes each inode's
         * ILOCK, which means that any in-progress inode updates will finish
         * before we can scan the inode.
         *
         * The apply hook (which removes the shadow dquot accounting struct)
         * must be installed before the mod hook so that we never fail to catch
         * the end of a quota update sequence and leave stale shadow data.
         */
        ASSERT(sc->flags & XCHK_FSGATES_QUOTA);
        xfs_dqtrx_hook_setup(&xqc->qhook, xqcheck_mod_live_ino_dqtrx,
                        xqcheck_apply_live_dqtrx);

        error = xfs_dqtrx_hook_add(qi, &xqc->qhook);
        if (error)
                goto out_teardown;

        /* Use deferred cleanup to pass the quota count data to repair. */
        sc->buf_cleanup = xqcheck_teardown_scan;
        return 0;

out_teardown:
        xqcheck_teardown_scan(xqc);
        return error;
}

/* Scrub all counters for a given quota type. */
int
xchk_quotacheck(
        struct xfs_scrub        *sc)
{
        struct xqcheck          *xqc = sc->buf;
        int                     error = 0;

        /* Check quota counters on the live filesystem. */
        error = xqcheck_setup_scan(sc, xqc);
        if (error)
                return error;

        /* Walk all inodes, picking up quota information. */
        error = xqcheck_collect_counts(xqc);
        if (!xchk_xref_process_error(sc, 0, 0, &error))
                return error;

        /* Fail fast if we're not playing with a full dataset. */
        if (xchk_iscan_aborted(&xqc->iscan))
                xchk_set_incomplete(sc);
        if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE)
                return 0;

        /* Compare quota counters. */
        if (xqc->ucounts) {
                error = xqcheck_compare_dqtype(xqc, XFS_DQTYPE_USER);
                if (!xchk_xref_process_error(sc, 0, 0, &error))
                        return error;
        }
        if (xqc->gcounts) {
                error = xqcheck_compare_dqtype(xqc, XFS_DQTYPE_GROUP);
                if (!xchk_xref_process_error(sc, 0, 0, &error))
                        return error;
        }
        if (xqc->pcounts) {
                error = xqcheck_compare_dqtype(xqc, XFS_DQTYPE_PROJ);
                if (!xchk_xref_process_error(sc, 0, 0, &error))
                        return error;
        }

        /* Check one last time for an incomplete dataset. */
        if (xchk_iscan_aborted(&xqc->iscan))
                xchk_set_incomplete(sc);

        return 0;
}