root/fs/xfs/scrub/quotacheck_repair.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_iwalk.h"
#include "xfs_ialloc.h"
#include "xfs_sb.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 Repair
 * ======================
 *
 * Use the live quota counter information that we collected to replace the
 * counter values in the incore dquots.  A scrub->repair cycle should have left
 * the live data and hooks active, so this is safe so long as we make sure the
 * dquot is locked.
 */

/* Commit new counters to a dquot. */
static int
xqcheck_commit_dquot(
        struct xqcheck          *xqc,
        xfs_dqtype_t            dqtype,
        struct xfs_dquot        *dq)
{
        struct xqcheck_dquot    xcdq;
        struct xfarray          *counts = xqcheck_counters_for(xqc, dqtype);
        int64_t                 delta;
        bool                    dirty = false;
        int                     error = 0;

        error = xchk_trans_alloc(xqc->sc, 0);
        if (error)
                return error;

        mutex_lock(&dq->q_qlock);
        xfs_trans_dqjoin(xqc->sc->tp, dq);

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

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

        /* Adjust counters as needed. */
        delta = (int64_t)xcdq.icount - dq->q_ino.count;
        if (delta) {
                dq->q_ino.reserved += delta;
                dq->q_ino.count += delta;
                dirty = true;
        }

        delta = (int64_t)xcdq.bcount - dq->q_blk.count;
        if (delta) {
                dq->q_blk.reserved += delta;
                dq->q_blk.count += delta;
                dirty = true;
        }

        delta = (int64_t)xcdq.rtbcount - dq->q_rtb.count;
        if (delta) {
                dq->q_rtb.reserved += delta;
                dq->q_rtb.count += delta;
                dirty = true;
        }

        xcdq.flags |= (XQCHECK_DQUOT_REPAIR_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 repair
                 * and must cancel the whole operation.  This should never
                 * happen, but we need to catch it anyway.
                 */
                error = -ECANCELED;
        }
        mutex_unlock(&xqc->lock);
        if (error || !dirty)
                goto out_cancel;

        trace_xrep_quotacheck_dquot(xqc->sc->mp, dq->q_type, dq->q_id);

        /* Commit the dirty dquot to disk. */
        dq->q_flags |= XFS_DQFLAG_DIRTY;
        if (dq->q_id)
                xfs_qm_adjust_dqtimers(dq);
        xfs_trans_log_dquot(xqc->sc->tp, dq);
        return xrep_trans_commit(xqc->sc);

out_unlock:
        mutex_unlock(&xqc->lock);
out_cancel:
        xchk_trans_cancel(xqc->sc);
        return error;
}

/* Commit new quota counters for a particular quota type. */
STATIC int
xqcheck_commit_dqtype(
        struct xqcheck          *xqc,
        unsigned int            dqtype)
{
        struct xchk_dqiter      cursor = { };
        struct xqcheck_dquot    xcdq;
        struct xfs_scrub        *sc = xqc->sc;
        struct xfs_mount        *mp = sc->mp;
        struct xfarray          *counts = xqcheck_counters_for(xqc, dqtype);
        struct xfs_dquot        *dq;
        xfarray_idx_t           cur = XFARRAY_CURSOR_INIT;
        int                     error;

        /*
         * Update the counters of every dquot that the quota file knows about.
         */
        xchk_dqiter_init(&cursor, sc, dqtype);
        while ((error = xchk_dquot_iter(&cursor, &dq)) == 1) {
                error = xqcheck_commit_dquot(xqc, dqtype, dq);
                xfs_qm_dqrele(dq);
                if (error)
                        break;
        }
        if (error)
                return error;

        /*
         * Make a second pass to deal with the dquots that we know about but
         * the quota file previously did not know about.
         */
        mutex_lock(&xqc->lock);
        while ((error = xfarray_iter(counts, &cur, &xcdq)) == 1) {
                xfs_dqid_t      id = cur - 1;

                if (xcdq.flags & XQCHECK_DQUOT_REPAIR_SCANNED)
                        continue;

                mutex_unlock(&xqc->lock);

                /*
                 * Grab the dquot, allowing for dquot block allocation in a
                 * separate transaction.  We committed the scrub transaction
                 * in a previous step, so we will not be creating nested
                 * transactions here.
                 */
                error = xfs_qm_dqget(mp, id, dqtype, true, &dq);
                if (error)
                        return error;

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

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

        return error;
}

/* Figure out quota CHKD flags for the running quota types. */
static inline unsigned int
xqcheck_chkd_flags(
        struct xfs_mount        *mp)
{
        unsigned int            ret = 0;

        if (XFS_IS_UQUOTA_ON(mp))
                ret |= XFS_UQUOTA_CHKD;
        if (XFS_IS_GQUOTA_ON(mp))
                ret |= XFS_GQUOTA_CHKD;
        if (XFS_IS_PQUOTA_ON(mp))
                ret |= XFS_PQUOTA_CHKD;
        return ret;
}

/* Commit the new dquot counters. */
int
xrep_quotacheck(
        struct xfs_scrub        *sc)
{
        struct xqcheck          *xqc = sc->buf;
        unsigned int            qflags = xqcheck_chkd_flags(sc->mp);
        int                     error;

        /*
         * Clear the CHKD flag for the running quota types and commit the scrub
         * transaction so that we can allocate new quota block mappings if we
         * have to.  If we crash after this point, the sb still has the CHKD
         * flags cleared, so mount quotacheck will fix all of this up.
         */
        xrep_update_qflags(sc, qflags, 0);
        error = xrep_trans_commit(sc);
        if (error)
                return error;

        /* Commit the new counters to the dquots. */
        if (xqc->ucounts) {
                error = xqcheck_commit_dqtype(xqc, XFS_DQTYPE_USER);
                if (error)
                        return error;
        }
        if (xqc->gcounts) {
                error = xqcheck_commit_dqtype(xqc, XFS_DQTYPE_GROUP);
                if (error)
                        return error;
        }
        if (xqc->pcounts) {
                error = xqcheck_commit_dqtype(xqc, XFS_DQTYPE_PROJ);
                if (error)
                        return error;
        }

        /* Set the CHKD flags now that we've fixed quota counts. */
        error = xchk_trans_alloc(sc, 0);
        if (error)
                return error;

        xrep_update_qflags(sc, 0, qflags);
        return xrep_trans_commit(sc);
}