maildir-sync-index.c revision 53cc097d3f8cd789f3c8fa0dfdd02bcda776230e
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen/* Copyright (c) 2007-2014 Dovecot authors, see the included COPYING file */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen struct maildir_sync_context *maildir_sync_ctx;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen struct maildir_keywords_sync_ctx *keywords_sync_ctx;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen struct index_sync_changes_context *sync_changes;
4d4d6d4745682790c20d759ba93dbea46b812c5dTimo Sirainen ARRAY_TYPE(keyword_indexes) keywords, idx_keywords;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen unsigned int flag_change_count, expunge_count, new_msgs_count;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenmaildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context *ctx)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenvoid maildir_sync_set_new_msgs_count(struct maildir_index_sync_context *ctx,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen unsigned int count)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenmaildir_expunge_is_valid_guid(struct maildir_index_sync_context *ctx,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* no GUID associated with expunge */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen guid = maildir_uidlist_lookup_ext(ctx->mbox->uidlist, uid,
78fa3c578c14ee8a612f86cf73b6181c7f16463fTimo Sirainen if (memcmp(guid_128, expunged_guid_128, sizeof(guid_128)) == 0)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen mail_storage_set_critical(&ctx->mbox->storage->storage,
78fa3c578c14ee8a612f86cf73b6181c7f16463fTimo Sirainen "Mailbox %s: Expunged GUID mismatch for UID %u: %s vs %s",
78fa3c578c14ee8a612f86cf73b6181c7f16463fTimo Sirainenstatic int maildir_expunge(struct maildir_mailbox *mbox, const char *path,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen return maildir_lose_unexpected_dir(box->storage, path);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenstatic int maildir_sync_flags(struct maildir_mailbox *mbox, const char *path,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* get the current flags and keywords */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen maildir_filename_flags_get(ctx->keywords_sync_ctx,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* apply changes */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen index_sync_changes_apply(ctx->sync_changes, NULL,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* and try renaming with the new name */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen newfname = maildir_filename_flags_kw_set(ctx->keywords_sync_ctx, fname,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* just make sure that the file still exists. avoid rename()
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen here because it's slow on HFS. */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen "rename(%s, %s) failed: %m",
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenstatic int maildir_handle_uid_insertion(struct maildir_index_sync_context *ctx,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* partial syncing */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* most likely a race condition: we read the maildir, then someone else
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen expunged messages and committed changes to index. so, this message
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen shouldn't actually exist. */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) == 0) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* mark it racy and check in next sync */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen maildir_sync_set_racing(ctx->maildir_sync_ctx);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen maildir_uidlist_add_flags(ctx->mbox->uidlist, filename,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen ret = maildir_uidlist_sync_init(ctx->mbox->uidlist,
17fe695b985e9d6e9dc39c05b24e6b3c3b7e1ba1Timo Sirainen maildir_uidlist_sync_remove(ctx->uidlist_sync_ctx, filename);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* give the new UID to it immediately */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_warning("Maildir %s: Expunged message reappeared, giving a new UID "
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen "(old uid=%u, file=%s)%s", mailbox_get_path(&ctx->mbox->box),
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen uid, filename, strncmp(filename, "msg.", 4) != 0 ? "" :
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen " (Your MDA is saving MH files into Maildir?)");
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainenint maildir_sync_index_begin(struct maildir_mailbox *mbox,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen struct maildir_sync_context *maildir_sync_ctx,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen sync_flags = index_storage_get_sync_flags(&mbox->box);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* don't drop recent messages if we're saving messages */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen sync_flags &= ~MAIL_INDEX_SYNC_FLAG_DROP_RECENT;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (mail_index_sync_begin(_box->index, &sync_ctx, &view,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ctx = i_new(struct maildir_index_sync_context, 1);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen maildir_keywords_sync_init(mbox->keywords, _box->index);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen index_sync_changes_init(ctx->sync_ctx, ctx->view, ctx->trans,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainenmaildir_index_header_has_changed(const struct maildir_index_header *old_hdr,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (old_hdr->new_mtime != new_hdr->new_mtime ||
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen old_hdr->new_mtime_nsecs != new_hdr->new_mtime_nsecs ||
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen old_hdr->cur_mtime_nsecs != new_hdr->cur_mtime_nsecs ||
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen old_hdr->uidlist_mtime != new_hdr->uidlist_mtime ||
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen old_hdr->uidlist_mtime_nsecs != new_hdr->uidlist_mtime_nsecs ||
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen old_hdr->uidlist_size != new_hdr->uidlist_size)
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainenmaildir_sync_index_update_ext_header(struct maildir_index_sync_context *ctx)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen cur_path = t_strconcat(mailbox_get_path(&mbox->box), "/cur", NULL);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if (ctx->update_maildir_hdr_cur && stat(cur_path, &st) == 0) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if ((time_t)mbox->maildir_hdr.cur_check_time < st.st_mtime)
9240d99920783c56405dda74a1f6c7ff1ebed8e6Timo Sirainen mbox->maildir_hdr.cur_check_time = st.st_mtime;
9240d99920783c56405dda74a1f6c7ff1ebed8e6Timo Sirainen mbox->maildir_hdr.cur_mtime_nsecs = ST_MTIME_NSEC(st);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen mail_index_get_header_ext(mbox->box.view, mbox->maildir_ext_id,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen maildir_index_header_has_changed(data, &mbox->maildir_hdr)) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen mail_index_update_header_ext(ctx->trans, mbox->maildir_ext_id,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenstatic int maildir_sync_index_finish(struct maildir_index_sync_context *ctx,
9240d99920783c56405dda74a1f6c7ff1ebed8e6Timo Sirainen if (time_diff >= MAILDIR_SYNC_TIME_WARN_SECS) {
9240d99920783c56405dda74a1f6c7ff1ebed8e6Timo Sirainen i_warning("Maildir %s: Synchronization took %u seconds "
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen "(%u new msgs, %u flag change attempts, "
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen "%u expunge attempts)",
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* Set syncing_commit=TRUE so that if any sync callbacks try
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen to access mails which got lost (eg. expunge callback trying
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen to open the file which was just unlinked) we don't try to
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen start a second index sync and crash. */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (mail_index_sync_commit(&ctx->sync_ctx) < 0) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen maildir_keywords_sync_deinit(&ctx->keywords_sync_ctx);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen index_sync_changes_deinit(&ctx->sync_changes);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainenint maildir_sync_index_commit(struct maildir_index_sync_context **_ctx)
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen struct maildir_index_sync_context *ctx = *_ctx;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainenvoid maildir_sync_index_rollback(struct maildir_index_sync_context **_ctx)
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen struct maildir_index_sync_context *ctx = *_ctx;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenstatic int uint_cmp(const unsigned int *i1, const unsigned int *i2)
b2d562f9c7fd13f9a16e9b3bcee904630b80b1feTimo Sirainenmaildir_sync_mail_keywords(struct maildir_index_sync_context *ctx, uint32_t seq)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen const unsigned int *old_indexes, *new_indexes;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen mail_index_lookup_keywords(ctx->view, seq, &ctx->idx_keywords);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if (index_keyword_array_cmp(&ctx->keywords, &ctx->idx_keywords)) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen /* no changes - we should get here usually */
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen /* sort the keywords */
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen /* drop keywords that are in index-only. we don't want to touch them. */
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen old_indexes = array_get(&ctx->idx_keywords, &old_count);
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen for (i = old_count; i > 0; i--) {
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen if (maildir_keywords_idx_char(ctx->keywords_sync_ctx,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* no index-only keywords found, so something changed.
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen just replace them all. */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen kw = mail_index_keywords_create_from_indexes(box->index,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen mail_index_update_keywords(ctx->trans, seq, MODIFY_REPLACE, kw);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* check again if non-index-only keywords changed */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (index_keyword_array_cmp(&ctx->keywords, &ctx->idx_keywords))
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* we can't reset all the keywords or we'd drop indexonly keywords too.
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen so first remove the unwanted keywords and then add back the wanted
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ones. we can get these lists easily by removing common elements
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen from old and new keywords. */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen new_indexes = array_get(&ctx->keywords, &new_count);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen for (i = j = 0; i < old_count && j < new_count; ) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen diff = (int)old_indexes[i] - (int)new_indexes[j];
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen old_indexes = array_get(&ctx->idx_keywords, &old_count);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen new_indexes = array_get(&ctx->keywords, &new_count);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen } else if (diff < 0) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen kw = mail_index_keywords_create_from_indexes(box->index,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen mail_index_update_keywords(ctx->trans, seq, MODIFY_REMOVE, kw);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen kw = mail_index_keywords_create_from_indexes(box->index,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen mail_index_update_keywords(ctx->trans, seq, MODIFY_ADD, kw);
d39a04db2f4d0599cb9b5f03a9aa10a3c234453cTimo Sirainenint maildir_sync_index(struct maildir_index_sync_context *ctx,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen struct mail_index_transaction *trans = ctx->trans;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen uint32_t uid_validity, next_uid, hdr_next_uid, first_recent_uid;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen unsigned int changes = 0;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen uid_validity != 0 && hdr->uid_validity != 0) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* uidvalidity changed and index isn't being synced for the
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen first time, reset the index so we can add all messages as
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen private_flags_mask = mailbox_get_private_flags_mask(&mbox->box);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen seq = prev_uid = 0; first_recent_uid = I_MAX(hdr->first_recent_uid, 1);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen i_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen i_array_init(&ctx->idx_keywords, MAILDIR_MAX_KEYWORDS);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen iter = maildir_uidlist_iter_init(mbox->uidlist);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen maildir_filename_flags_get(ctx->keywords_sync_ctx, filename,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* the private flags are kept only in indexes. don't use them
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen at all even for newly seen mails */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* Trust uidlist recent flags only for newly added
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen messages. When saving/copying messages with flags
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen they're stored to cur/ and uidlist treats them
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen as non-recent. */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) == 0) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen mail_index_update_flags(trans, seq, MODIFY_REPLACE,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* already expunged (no point in showing guid in the
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen expunge record anymore) */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen index_sync_changes_read(ctx->sync_changes, ctx->uid, &expunged,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (!maildir_expunge_is_valid_guid(ctx, ctx->uid,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* successful expunge */
if (!partial) {
TRUE) < 0)
if (uid_validity == 0) {
sizeof(struct maildir_list_index_record),
sizeof(uint32_t));
const void *data;
bool expunged;
int ret;
return ret;
&root_dir);
if (ret < 0)
return ret;
const void *data;
bool expunged;
if (expunged)