fix bug with cli import command in case the mbox/maildir had keywords, future delivery to the mailbox would fail with duplicate uid's.

accounts with a mailbox with this problem can be fixed by running the "mox
fixuidmeta <account>" command.

we were resetting the mailbox uidnext after delivering messages when we were
setting new keywords on the mailbox at the end of the import. so in a future
delivery attempt to that mailbox, a uid would be chosen that was already
present.

the fix is to fetch the updated mailbox from the database before setting the
new keywords.

http/import.go doesn't have this bug because it was already fetching the
mailbox before updating keywords (because it can import into many mailboxes,
so different code).

the "mox verifydata" command (recommended with backups) also warns about this
issue (but doesn't fix it)

found while working on new functionality (webmail).
This commit is contained in:
Mechiel Lukkien 2023-07-26 09:24:24 +02:00
parent 700118dbd2
commit e3d0a3a001
No known key found for this signature in database
3 changed files with 11 additions and 6 deletions

View file

@ -386,6 +386,10 @@ func importctl(ctx context.Context, ctl *ctl, mbox bool) {
process(m, msgf, origPath)
}
// Load the mailbox again after delivering, its uidnext has been updated.
err = tx.Get(&mb)
ctl.xcheck(err, "fetching mailbox")
// If there are any new keywords, update the mailbox.
var changed bool
mb.Keywords, changed = store.MergeKeywords(mb.Keywords, maps.Keys(mailboxKeywords))

View file

@ -2240,9 +2240,10 @@ open, or is not running.
} else if err != nil {
return fmt.Errorf("finding message with max uid in mailbox: %w", err)
}
olduidnext := mb.UIDNext
mb.UIDNext = m.UID + 1
log.Printf("fixing uidnext to %d (max uid is %d, old uidnext was %d) for mailbox %q (id %d)", mb.UIDNext, m.UID, olduidnext, mb.Name, mb.ID)
if err := tx.Update(&mb); err != nil {
log.Printf("fixing uidnext to %d (max uid is %d) for mailbox id %d", mb.UIDNext, m.UID, mb.ID)
return fmt.Errorf("updating mailbox uidnext: %v", err)
}
return nil

View file

@ -231,20 +231,20 @@ possibly making them potentially no longer readable by the previous version.
checkf(err, dbpath, "missing nextuidvalidity")
}
mailboxUIDNexts := map[int64]store.UID{}
mailboxes := map[int64]store.Mailbox{}
err := bstore.QueryDB[store.Mailbox](ctxbg, db).ForEach(func(mb store.Mailbox) error {
mailboxUIDNexts[mb.ID] = mb.UIDNext
mailboxes[mb.ID] = mb
if mb.UIDValidity >= uidvalidity.Next {
checkf(errors.New(`inconsistent uidvalidity for mailbox/account, see "mox fixuidmeta"`), dbpath, "mailbox id %d has uidvalidity %d >= account nextuidvalidity %d", mb.ID, mb.UIDValidity, uidvalidity.Next)
checkf(errors.New(`inconsistent uidvalidity for mailbox/account, see "mox fixuidmeta"`), dbpath, "mailbox %q (id %d) has uidvalidity %d >= account nextuidvalidity %d", mb.Name, mb.ID, mb.UIDValidity, uidvalidity.Next)
}
return nil
})
checkf(err, dbpath, "reading mailboxes to check uidnext consistency")
err = bstore.QueryDB[store.Message](ctxbg, db).ForEach(func(m store.Message) error {
if uidnext := mailboxUIDNexts[m.MailboxID]; m.UID >= uidnext {
checkf(errors.New(`inconsistent uidnext for message/mailbox, see "mox fixuidmeta"`), dbpath, "message id %d in mailbox id %d has uid %d >= mailbox uidnext %d", m.ID, m.MailboxID, m.UID, uidnext)
if mb := mailboxes[m.MailboxID]; m.UID >= mb.UIDNext {
checkf(errors.New(`inconsistent uidnext for message/mailbox, see "mox fixuidmeta"`), dbpath, "message id %d in mailbox %q (id %d) has uid %d >= mailbox uidnext %d", m.ID, mb.Name, mb.ID, m.UID, mb.UIDNext)
}
if m.Expunged {