From e3d0a3a001445d1cef6559c3610d4a342c0ef5c8 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Wed, 26 Jul 2023 09:24:24 +0200 Subject: [PATCH] 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 " 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). --- import.go | 4 ++++ main.go | 3 ++- verifydata.go | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/import.go b/import.go index a0f4865..ba5f845 100644 --- a/import.go +++ b/import.go @@ -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)) diff --git a/main.go b/main.go index 93a4679..c7e035b 100644 --- a/main.go +++ b/main.go @@ -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 diff --git a/verifydata.go b/verifydata.go index 64e4201..1558fe7 100644 --- a/verifydata.go +++ b/verifydata.go @@ -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 {