| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // 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.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;
- /* Unlock the dquot just long enough to allocate a transaction. */
- xfs_dqunlock(dq);
- error = xchk_trans_alloc(xqc->sc, 0);
- xfs_dqlock(dq);
- if (error)
- return error;
- 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);
- /*
- * Transaction commit unlocks the dquot, so we must re-lock it so that
- * the caller can put the reference (which apparently requires a locked
- * dquot).
- */
- error = xrep_trans_commit(xqc->sc);
- xfs_dqlock(dq);
- return error;
- out_unlock:
- mutex_unlock(&xqc->lock);
- out_cancel:
- xchk_trans_cancel(xqc->sc);
- /* Re-lock the dquot so the caller can put the reference. */
- xfs_dqlock(dq);
- 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_dqput(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_dqput(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);
- }
|