From da483b8a9c2c3e7b23655699680feb57a194000c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:57:37 -0700 Subject: [PATCH] xfs: fix missing xfs_qm_adjust_dqlimits call in quotacheck repair LOLLM noticed that everywhere else in the kernel, a call to xfs_qm_adjust_dqlimits precedes every call to xfs_qm_adjust_dqtimers. In particular, mount-time quotacheck does this, but online quotacheck does not. Looking at xfs_qm_adjust_dqlimits, that function is in charge of conveying default limits to a dquot if that dquot's limits have been zeroed. That's quite possible in a repair, so we actually need to do that. However, there's a pre-existing pattern in the kernel -- for non-root dquots, first we call xfs_qm_adjust_dqlimits to set the dquot's limits to the defaults if they are zero, and then xfs_qm_adjust_dqtimers to start grace periods if the dquot's usage is above the softlimit. The grace period decision cannot be made correctly if we forget to import the default limits. Therefore, let's combine both into a single xfs_qm_adjust_dqenforcement helper that takes care of both pieces, which fixes quotacheck and makes it hard to repeat this mistake. Cc: stable@vger.kernel.org # v6.9 Fixes: 96ed2ae4a9b06b ("xfs: repair dquots based on live quotacheck results") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 5 +---- fs/xfs/scrub/quotacheck_repair.c | 3 +-- fs/xfs/xfs_dquot.c | 20 +++++++++++++++----- fs/xfs/xfs_dquot.h | 2 +- fs/xfs/xfs_qm.c | 6 +----- fs/xfs/xfs_trans_dquot.c | 6 +----- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 59302e8afc7e..89f7ea4f92ef 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -248,10 +248,7 @@ xrep_quota_item( dq->q_flags |= XFS_DQFLAG_DIRTY; xfs_trans_dqjoin(sc->tp, dq); - if (dq->q_id) { - xfs_qm_adjust_dqlimits(dq); - xfs_qm_adjust_dqtimers(dq); - } + xfs_qm_adjust_dqenforcement(dq); xfs_trans_log_dquot(sc->tp, dq); return xfs_trans_roll(&sc->tp); diff --git a/fs/xfs/scrub/quotacheck_repair.c b/fs/xfs/scrub/quotacheck_repair.c index dbb522e1513b..48ee08df302a 100644 --- a/fs/xfs/scrub/quotacheck_repair.c +++ b/fs/xfs/scrub/quotacheck_repair.c @@ -110,8 +110,7 @@ xqcheck_commit_dquot( /* Commit the dirty dquot to disk. */ dq->q_flags |= XFS_DQFLAG_DIRTY; - if (dq->q_id) - xfs_qm_adjust_dqtimers(dq); + xfs_qm_adjust_dqenforcement(dq); xfs_trans_log_dquot(xqc->sc->tp, dq); return xrep_trans_commit(xqc->sc); diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index e696ee36c2e8..6d22ead562da 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -115,18 +115,15 @@ xfs_qm_dqdestroy( * We overwrite the dquot limits only if they are zero and this * is not the root dquot. */ -void +static void xfs_qm_adjust_dqlimits( struct xfs_dquot *dq) { struct xfs_mount *mp = dq->q_mount; struct xfs_quotainfo *q = mp->m_quotainfo; - struct xfs_def_quota *defq; + struct xfs_def_quota *defq = xfs_get_defquota(q, xfs_dquot_type(dq)); int prealloc = 0; - ASSERT(dq->q_id); - defq = xfs_get_defquota(q, xfs_dquot_type(dq)); - if (!dq->q_blk.softlimit) { dq->q_blk.softlimit = defq->blk.soft; prealloc = 1; @@ -223,6 +220,19 @@ xfs_qm_adjust_dqtimers( xfs_qm_adjust_res_timer(dq->q_mount, &dq->q_rtb, &defq->rtb); } +/* Adjust enforcement limits and timers after a change in usage. */ +void +xfs_qm_adjust_dqenforcement( + struct xfs_dquot *dq) +{ + if (dq->q_id == 0) + return; + + xfs_qm_adjust_dqlimits(dq); + xfs_qm_adjust_dqtimers(dq); + dq->q_flags |= XFS_DQFLAG_DIRTY; +} + /* * initialize a buffer full of dquots and log the whole thing */ diff --git a/fs/xfs/xfs_dquot.h b/fs/xfs/xfs_dquot.h index bbb824adca82..28c09704acc2 100644 --- a/fs/xfs/xfs_dquot.h +++ b/fs/xfs/xfs_dquot.h @@ -205,7 +205,7 @@ void xfs_qm_dqdestroy(struct xfs_dquot *dqp); int xfs_qm_dqflush(struct xfs_dquot *dqp, struct xfs_buf *bp); void xfs_qm_dqunpin_wait(struct xfs_dquot *dqp); void xfs_qm_adjust_dqtimers(struct xfs_dquot *d); -void xfs_qm_adjust_dqlimits(struct xfs_dquot *d); +void xfs_qm_adjust_dqenforcement(struct xfs_dquot *d); xfs_dqid_t xfs_qm_id_for_quotatype(struct xfs_inode *ip, xfs_dqtype_t type); int xfs_qm_dqget(struct xfs_mount *mp, xfs_dqid_t id, diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 54d00d543b51..008fed8624be 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1294,11 +1294,7 @@ xfs_qm_quotacheck_dqadjust( * * There are no timers for the default values set in the root dquot. */ - if (dqp->q_id) { - xfs_qm_adjust_dqlimits(dqp); - xfs_qm_adjust_dqtimers(dqp); - } - + xfs_qm_adjust_dqenforcement(dqp); dqp->q_flags |= XFS_DQFLAG_DIRTY; out_unlock: mutex_unlock(&dqp->q_qlock); diff --git a/fs/xfs/xfs_trans_dquot.c b/fs/xfs/xfs_trans_dquot.c index 1606c614f205..93ec876792cc 100644 --- a/fs/xfs/xfs_trans_dquot.c +++ b/fs/xfs/xfs_trans_dquot.c @@ -566,11 +566,7 @@ xfs_trans_apply_dquot_deltas( * Get any default limits in use. * Start/reset the timer(s) if needed. */ - if (dqp->q_id) { - xfs_qm_adjust_dqlimits(dqp); - xfs_qm_adjust_dqtimers(dqp); - } - + xfs_qm_adjust_dqenforcement(dqp); dqp->q_flags |= XFS_DQFLAG_DIRTY; /* * add this to the list of items to get logged