| // 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); |
| } |