--- /dev/null
+From 07137e925fa951646325762bda6bd2503dfe64c6 Mon Sep 17 00:00:00 2001
+From: "Darrick J. Wong" <djwong@kernel.org>
+Date: Mon, 2 Dec 2024 10:57:36 -0800
+Subject: xfs: don't lose solo dquot update transactions
+
+From: Darrick J. Wong <djwong@kernel.org>
+
+commit 07137e925fa951646325762bda6bd2503dfe64c6 upstream.
+
+Quota counter updates are tracked via incore objects which hang off the
+xfs_trans object. These changes are then turned into dirty log items in
+xfs_trans_apply_dquot_deltas just prior to commiting the log items to
+the CIL.
+
+However, updating the incore deltas do not cause XFS_TRANS_DIRTY to be
+set on the transaction. In other words, a pure quota counter update
+will be silently discarded if there are no other dirty log items
+attached to the transaction.
+
+This is currently not the case anywhere in the filesystem because quota
+updates always dirty at least one other metadata item, but a subsequent
+bug fix will add dquot log item precommits, so we actually need a dirty
+dquot log item prior to xfs_trans_run_precommits. Also let's not leave
+a logic bomb.
+
+Cc: <stable@vger.kernel.org> # v2.6.35
+Fixes: 0924378a689ccb ("xfs: split out iclog writing from xfs_trans_commit()")
+Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/xfs_quota.h | 5 +++--
+ fs/xfs/xfs_trans.c | 10 +++-------
+ fs/xfs/xfs_trans_dquot.c | 31 ++++++++++++++++++++++++++-----
+ 3 files changed, 32 insertions(+), 14 deletions(-)
+
+--- a/fs/xfs/xfs_quota.h
++++ b/fs/xfs/xfs_quota.h
+@@ -96,7 +96,8 @@ extern void xfs_trans_free_dqinfo(struct
+ extern void xfs_trans_mod_dquot_byino(struct xfs_trans *, struct xfs_inode *,
+ uint, int64_t);
+ extern void xfs_trans_apply_dquot_deltas(struct xfs_trans *);
+-extern void xfs_trans_unreserve_and_mod_dquots(struct xfs_trans *);
++void xfs_trans_unreserve_and_mod_dquots(struct xfs_trans *tp,
++ bool already_locked);
+ int xfs_trans_reserve_quota_nblks(struct xfs_trans *tp, struct xfs_inode *ip,
+ int64_t dblocks, int64_t rblocks, bool force);
+ extern int xfs_trans_reserve_quota_bydquots(struct xfs_trans *,
+@@ -166,7 +167,7 @@ static inline void xfs_trans_mod_dquot_b
+ {
+ }
+ #define xfs_trans_apply_dquot_deltas(tp)
+-#define xfs_trans_unreserve_and_mod_dquots(tp)
++#define xfs_trans_unreserve_and_mod_dquots(tp, a)
+ static inline int xfs_trans_reserve_quota_nblks(struct xfs_trans *tp,
+ struct xfs_inode *ip, int64_t dblocks, int64_t rblocks,
+ bool force)
+--- a/fs/xfs/xfs_trans.c
++++ b/fs/xfs/xfs_trans.c
+@@ -840,6 +840,7 @@ __xfs_trans_commit(
+ */
+ if (tp->t_flags & XFS_TRANS_SB_DIRTY)
+ xfs_trans_apply_sb_deltas(tp);
++ xfs_trans_apply_dquot_deltas(tp);
+
+ error = xfs_trans_run_precommits(tp);
+ if (error)
+@@ -868,11 +869,6 @@ __xfs_trans_commit(
+
+ ASSERT(tp->t_ticket != NULL);
+
+- /*
+- * If we need to update the superblock, then do it now.
+- */
+- xfs_trans_apply_dquot_deltas(tp);
+-
+ xlog_cil_commit(log, tp, &commit_seq, regrant);
+
+ xfs_trans_free(tp);
+@@ -898,7 +894,7 @@ out_unreserve:
+ * the dqinfo portion to be. All that means is that we have some
+ * (non-persistent) quota reservations that need to be unreserved.
+ */
+- xfs_trans_unreserve_and_mod_dquots(tp);
++ xfs_trans_unreserve_and_mod_dquots(tp, true);
+ if (tp->t_ticket) {
+ if (regrant && !xlog_is_shutdown(log))
+ xfs_log_ticket_regrant(log, tp->t_ticket);
+@@ -992,7 +988,7 @@ xfs_trans_cancel(
+ }
+ #endif
+ xfs_trans_unreserve_and_mod_sb(tp);
+- xfs_trans_unreserve_and_mod_dquots(tp);
++ xfs_trans_unreserve_and_mod_dquots(tp, false);
+
+ if (tp->t_ticket) {
+ xfs_log_ticket_ungrant(log, tp->t_ticket);
+--- a/fs/xfs/xfs_trans_dquot.c
++++ b/fs/xfs/xfs_trans_dquot.c
+@@ -602,6 +602,24 @@ xfs_trans_apply_dquot_deltas(
+ ASSERT(dqp->q_blk.reserved >= dqp->q_blk.count);
+ ASSERT(dqp->q_ino.reserved >= dqp->q_ino.count);
+ ASSERT(dqp->q_rtb.reserved >= dqp->q_rtb.count);
++
++ /*
++ * We've applied the count changes and given back
++ * whatever reservation we didn't use. Zero out the
++ * dqtrx fields.
++ */
++ qtrx->qt_blk_res = 0;
++ qtrx->qt_bcount_delta = 0;
++ qtrx->qt_delbcnt_delta = 0;
++
++ qtrx->qt_rtblk_res = 0;
++ qtrx->qt_rtblk_res_used = 0;
++ qtrx->qt_rtbcount_delta = 0;
++ qtrx->qt_delrtb_delta = 0;
++
++ qtrx->qt_ino_res = 0;
++ qtrx->qt_ino_res_used = 0;
++ qtrx->qt_icount_delta = 0;
+ }
+ }
+ }
+@@ -638,7 +656,8 @@ xfs_trans_unreserve_and_mod_dquots_hook(
+ */
+ void
+ xfs_trans_unreserve_and_mod_dquots(
+- struct xfs_trans *tp)
++ struct xfs_trans *tp,
++ bool already_locked)
+ {
+ int i, j;
+ struct xfs_dquot *dqp;
+@@ -667,10 +686,12 @@ xfs_trans_unreserve_and_mod_dquots(
+ * about the number of blocks used field, or deltas.
+ * Also we don't bother to zero the fields.
+ */
+- locked = false;
++ locked = already_locked;
+ if (qtrx->qt_blk_res) {
+- xfs_dqlock(dqp);
+- locked = true;
++ if (!locked) {
++ xfs_dqlock(dqp);
++ locked = true;
++ }
+ dqp->q_blk.reserved -=
+ (xfs_qcnt_t)qtrx->qt_blk_res;
+ }
+@@ -691,7 +712,7 @@ xfs_trans_unreserve_and_mod_dquots(
+ dqp->q_rtb.reserved -=
+ (xfs_qcnt_t)qtrx->qt_rtblk_res;
+ }
+- if (locked)
++ if (locked && !already_locked)
+ xfs_dqunlock(dqp);
+
+ }