Skip to content

Commit a553a10

Browse files
committedMar 10, 2025··
Cleanup temporary files created during IMAP APPEND command.
Since a recent change (likely since implementing MULTIAPPEND), the temporary files weren't removed any more. When changing it, I must have had the wrong mental model about the MessageAdd method, assuming it would remove the temp file. Noticed during tests.
1 parent 0857e81 commit a553a10

File tree

3 files changed

+20
-26
lines changed

3 files changed

+20
-26
lines changed
 

‎imapserver/replace.go

+7-11
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
166166
}
167167

168168
var file *os.File
169-
var newMsgPath string
169+
var newID int64 // Delivered message ID, file removed on error.
170170
var f io.Writer
171171
var commit bool
172172

@@ -186,17 +186,14 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
186186
var err error
187187
file, err = store.CreateMessageTemp(c.log, "imap-replace")
188188
xcheckf(err, "creating temp file for message")
189-
newMsgPath = file.Name()
189+
defer store.CloseRemoveTempFile(c.log, file, "temporary message file")
190190
f = file
191191

192192
defer func() {
193-
if file != nil {
194-
err := file.Close()
195-
c.xsanity(err, "close temporary file for replace")
196-
}
197-
if newMsgPath != "" && !commit {
198-
err := os.Remove(newMsgPath)
199-
c.xsanity(err, "remove temporary file for replace")
193+
if !commit && newID != 0 {
194+
p := c.account.MessagePath(newID)
195+
err := os.Remove(p)
196+
c.xsanity(err, "remove message file for replace after error")
200197
}
201198
}()
202199
}
@@ -289,8 +286,7 @@ func (c *conn) cmdxReplace(isUID bool, tag, cmd string, p *parser) {
289286

290287
err = c.account.MessageAdd(c.log, tx, &mbDst, &nm, file, store.AddOpts{})
291288
xcheckf(err, "delivering message")
292-
// Update path to what is stored in the account. We may still have to clean it up on errors.
293-
newMsgPath = c.account.MessagePath(nm.ID)
289+
newID = nm.ID
294290

295291
changes = append(changes, nm.ChangeAddUID(), mbDst.ChangeCounts())
296292
if nkeywords != len(mbDst.Keywords) {

‎imapserver/server.go

+8-15
Original file line numberDiff line numberDiff line change
@@ -3275,24 +3275,19 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
32753275
time time.Time
32763276

32773277
file *os.File // Message file we are appending. Can be nil if we are writing to a nopWriteCloser due to being over quota.
3278-
path string // Path if an actual file, either a temporary file, or of the message file stored in the account.
32793278

32803279
mw *message.Writer
3281-
m store.Message
3280+
m store.Message // New message. Delivered file for m.ID is removed on error.
32823281
}
32833282

32843283
var appends []*appendMsg
32853284
var commit bool
32863285
defer func() {
32873286
for _, a := range appends {
3288-
if a.file != nil {
3289-
err := a.file.Close()
3290-
c.xsanity(err, "closing APPEND temporary file")
3291-
}
3292-
3293-
if !commit && a.path != "" {
3294-
err := os.Remove(a.path)
3295-
c.xsanity(err, "removing APPEND temporary file")
3287+
if !commit && a.m.ID != 0 {
3288+
p := c.account.MessagePath(a.m.ID)
3289+
err := os.Remove(p)
3290+
c.xsanity(err, "cleaning up temporary append file after error")
32963291
}
32973292
}
32983293
}()
@@ -3385,7 +3380,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
33853380
var err error
33863381
a.file, err = store.CreateMessageTemp(c.log, "imap-append")
33873382
xcheckf(err, "creating temp file for message")
3388-
a.path = a.file.Name()
3383+
defer store.CloseRemoveTempFile(c.log, a.file, "temporary message file")
33893384
f = a.file
33903385

33913386
c.writelinef("+ ")
@@ -3398,7 +3393,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
33983393
var err error
33993394
a.file, err = store.CreateMessageTemp(c.log, "imap-append")
34003395
xcheckf(err, "creating temp file for message")
3401-
a.path = a.file.Name()
3396+
defer store.CloseRemoveTempFile(c.log, a.file, "temporary message file")
34023397
f = a.file
34033398
}
34043399
}
@@ -3483,12 +3478,10 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
34833478
// todo: do a single junk training
34843479
err = c.account.MessageAdd(c.log, tx, &mb, &a.m, a.file, store.AddOpts{SkipDirSync: true})
34853480
xcheckf(err, "delivering message")
3486-
// Update path to what is stored in the account. We may still have to clean it up on errors.
3487-
a.path = c.account.MessagePath(a.m.ID)
34883481

34893482
changes = append(changes, a.m.ChangeAddUID())
34903483

3491-
msgDirs[filepath.Dir(a.path)] = struct{}{}
3484+
msgDirs[filepath.Dir(c.account.MessagePath(a.m.ID))] = struct{}{}
34923485
}
34933486

34943487
changes = append(changes, mb.ChangeCounts())

‎store/account.go

+5
Original file line numberDiff line numberDiff line change
@@ -2043,6 +2043,11 @@ type AddOpts struct {
20432043

20442044
// MessageAdd delivers a mail message to the account.
20452045
//
2046+
// The file is hardlinked or copied, the caller must clean up the original file. If
2047+
// this call succeeds, but the database transaction with the change can't be
2048+
// committed, the caller must clean up the delivered message file identified by
2049+
// m.ID.
2050+
//
20462051
// If the message does not fit in the quota, an error with ErrOverQuota is returned
20472052
// and the mailbox and message are unchanged and the transaction can continue. For
20482053
// other errors, the caller must abort the transaction.

0 commit comments

Comments
 (0)
Please sign in to comment.