quota: Fix maildir quota drop to zero after IMAP MOVE#274
Open
LexxFedoroff wants to merge 1 commit intodovecot:mainfrom
Open
quota: Fix maildir quota drop to zero after IMAP MOVE#274LexxFedoroff wants to merge 1 commit intodovecot:mainfrom
LexxFedoroff wants to merge 1 commit intodovecot:mainfrom
Conversation
When mailbox_move() is called with the maildir quota driver, the source mail expunge is double-counted, causing quota to drop to zero. The destination transaction's quota_try_alloc() calls quota_alloc_with_size(ctx, size, expunged_size), which subtracts expunged_size via quota_used_apply_expunged(), netting bytes_used to 0. At the same time, mail_expunge() on the source mail fires quota_mail_expunge() -> quota_free_bytes() in the source transaction, subtracting the full message size there as well. Result: only the source transaction commits bytes_used=-size and writes "-size -1" to maildirsize; the destination transaction commits bytes_used=0 and writes nothing. Quota reports 0 after the move. The fix adds a `moving` flag to quota_transaction_context that is set from mail_save_context inside quota_check() when ctx->moving is TRUE. quota_try_alloc() then passes expunged_size=0 to quota_alloc_with_size() only for MOVE, leaving COPY/REPLACE behaviour unchanged. expunged_size is still passed unmodified to quota_test_alloc(), so moves at the quota boundary continue to be correctly permitted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When
mailbox_move()is called with themaildirquota driver, the sourcemail's expunge is double-counted, causing reported quota to drop to 0 after
any IMAP MOVE operation.
Symptom
The message physically exists in the destination folder; only the quota tracking
is wrong. The
maildirsizefile accumulates an unbalanced negative delta with nocorresponding positive entry.
Root Cause
Two separate code paths both account for the source mail's expunge:
1. Destination transaction —
quota_save_finish→quota_check→quota_try_alloc:2. Source transaction —
mail_expunge→quota_mail_expunge→quota_free_bytes:Result:
maildirsizereceives only the negative delta; the positive entry forthe destination is never written.
Fix
Add a
movingflag toquota_transaction_context. It is set frommail_save_context.movinginquota_check(). Inquota_try_alloc(), passexpunged_size=0toquota_alloc_with_size()whenctx->movingisTRUE.expunged_sizeremains unchanged in thequota_test_alloc()call, so moves atthe quota boundary are still correctly permitted. The actual accounting for the
source expunge continues to be handled by the source transaction's
quota_mail_expunge()->quota_free_bytes()path.After the fix:
+N +1-N -1Testing
Reproduce with the
maildirquota driver:doveadm quota recalc -u user@example.com doveadm move -u user@example.com INBOX.Drafts mailbox INBOX ALL doveadm quota get -u user@example.com # was 0 before this fixCOPY + EXPUNGE is unaffected (the
movingflag remainsFALSEin that path).Files changed
src/plugins/quota/quota-private.h— addmoving:1bitfield toquota_transaction_contextsrc/plugins/quota/quota-storage.c— propagatectx->movinginto the quotatransaction in
quota_check()src/plugins/quota/quota.c— skip subtractingexpunged_sizefrombytes_usedinquota_try_alloc()when the transaction is a MOVE