summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/maildir/maildir-sync-index.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/maildir/maildir-sync-index.c')
-rw-r--r--src/lib-storage/index/maildir/maildir-sync-index.c810
1 files changed, 810 insertions, 0 deletions
diff --git a/src/lib-storage/index/maildir/maildir-sync-index.c b/src/lib-storage/index/maildir/maildir-sync-index.c
new file mode 100644
index 0000000..da0e40c
--- /dev/null
+++ b/src/lib-storage/index/maildir/maildir-sync-index.c
@@ -0,0 +1,810 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "maildir-storage.h"
+#include "index-sync-changes.h"
+#include "maildir-uidlist.h"
+#include "maildir-keywords.h"
+#include "maildir-filename-flags.h"
+#include "maildir-sync.h"
+#include "mailbox-recent-flags.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+struct maildir_index_sync_context {
+ struct maildir_mailbox *mbox;
+ struct maildir_sync_context *maildir_sync_ctx;
+
+ struct mail_index_view *view;
+ struct mail_index_sync_ctx *sync_ctx;
+ struct maildir_keywords_sync_ctx *keywords_sync_ctx;
+ struct mail_index_transaction *trans;
+
+ struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
+ struct index_sync_changes_context *sync_changes;
+ enum mail_flags flags;
+ ARRAY_TYPE(keyword_indexes) keywords, idx_keywords;
+
+ uint32_t uid;
+ bool update_maildir_hdr_cur;
+
+ time_t start_time;
+ unsigned int flag_change_count, expunge_count, new_msgs_count;
+};
+
+struct maildir_keywords_sync_ctx *
+maildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context *ctx)
+{
+ return ctx->keywords_sync_ctx;
+}
+
+void maildir_sync_set_new_msgs_count(struct maildir_index_sync_context *ctx,
+ unsigned int count)
+{
+ ctx->new_msgs_count = count;
+}
+
+static bool
+maildir_expunge_is_valid_guid(struct maildir_index_sync_context *ctx,
+ uint32_t uid, const char *filename,
+ guid_128_t expunged_guid_128)
+{
+ guid_128_t guid_128;
+ const char *guid;
+
+ if (guid_128_is_empty(expunged_guid_128)) {
+ /* no GUID associated with expunge */
+ return TRUE;
+ }
+
+ T_BEGIN {
+ guid = maildir_uidlist_lookup_ext(ctx->mbox->uidlist, uid,
+ MAILDIR_UIDLIST_REC_EXT_GUID);
+ if (guid == NULL)
+ guid = t_strcut(filename, *MAILDIR_INFO_SEP_S);
+ mail_generate_guid_128_hash(guid, guid_128);
+ } T_END;
+
+ if (memcmp(guid_128, expunged_guid_128, sizeof(guid_128)) == 0)
+ return TRUE;
+
+ mailbox_set_critical(&ctx->mbox->box,
+ "Expunged GUID mismatch for UID %u: %s vs %s",
+ ctx->uid, guid_128_to_string(guid_128),
+ guid_128_to_string(expunged_guid_128));
+ return FALSE;
+}
+
+static int maildir_expunge(struct maildir_mailbox *mbox, const char *path,
+ struct maildir_index_sync_context *ctx)
+{
+ struct mailbox *box = &mbox->box;
+
+ ctx->expunge_count++;
+
+ if (unlink(path) == 0) {
+ mailbox_sync_notify(box, ctx->uid, MAILBOX_SYNC_TYPE_EXPUNGE);
+ return 1;
+ }
+ if (errno == ENOENT)
+ return 0;
+ if (UNLINK_EISDIR(errno))
+ return maildir_lose_unexpected_dir(box->storage, path);
+
+ mailbox_set_critical(&mbox->box, "unlink(%s) failed: %m", path);
+ return -1;
+}
+
+static int maildir_sync_flags(struct maildir_mailbox *mbox, const char *path,
+ struct maildir_index_sync_context *ctx)
+{
+ struct mailbox *box = &mbox->box;
+ struct stat st;
+ const char *dir, *fname, *newfname, *newpath;
+ enum mail_index_sync_type sync_type;
+ uint8_t flags8;
+
+ ctx->flag_change_count++;
+
+ fname = strrchr(path, '/');
+ i_assert(fname != NULL);
+ fname++;
+ dir = t_strdup_until(path, fname);
+
+ i_assert(*fname != '\0');
+
+ /* get the current flags and keywords */
+ maildir_filename_flags_get(ctx->keywords_sync_ctx,
+ fname, &ctx->flags, &ctx->keywords);
+
+ /* apply changes */
+ flags8 = ctx->flags;
+ index_sync_changes_apply(ctx->sync_changes, NULL,
+ &flags8, &ctx->keywords, &sync_type);
+ ctx->flags = flags8;
+
+ /* and try renaming with the new name */
+ newfname = maildir_filename_flags_kw_set(ctx->keywords_sync_ctx, fname,
+ ctx->flags, &ctx->keywords);
+ newpath = t_strconcat(dir, newfname, NULL);
+ if (strcmp(path, newpath) == 0) {
+ /* just make sure that the file still exists. avoid rename()
+ here because it's slow on HFS. */
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT)
+ return 0;
+ mailbox_set_critical(box, "stat(%s) failed: %m", path);
+ return -1;
+ }
+ } else {
+ if (rename(path, newpath) < 0) {
+ if (errno == ENOENT)
+ return 0;
+ if (!ENOSPACE(errno) && errno != EACCES) {
+ mailbox_set_critical(box,
+ "rename(%s, %s) failed: %m",
+ path, newpath);
+ }
+ return -1;
+ }
+ }
+ mailbox_sync_notify(box, ctx->uid, index_sync_type_convert(sync_type));
+ return 1;
+}
+
+static int maildir_handle_uid_insertion(struct maildir_index_sync_context *ctx,
+ enum maildir_uidlist_rec_flag uflags,
+ const char *filename, uint32_t uid)
+{
+ int ret;
+
+ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
+ /* partial syncing */
+ return 0;
+ }
+
+ /* most likely a race condition: we read the maildir, then someone else
+ expunged messages and committed changes to index. so, this message
+ shouldn't actually exist. */
+ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) == 0) {
+ /* mark it racy and check in next sync */
+ ctx->mbox->maildir_hdr.cur_check_time = 0;
+ maildir_sync_set_racing(ctx->maildir_sync_ctx);
+ maildir_uidlist_add_flags(ctx->mbox->uidlist, filename,
+ MAILDIR_UIDLIST_REC_FLAG_RACING);
+ return 0;
+ }
+
+ if (ctx->uidlist_sync_ctx == NULL) {
+ ret = maildir_uidlist_sync_init(ctx->mbox->uidlist,
+ MAILDIR_UIDLIST_SYNC_PARTIAL |
+ MAILDIR_UIDLIST_SYNC_KEEP_STATE,
+ &ctx->uidlist_sync_ctx);
+ if (ret <= 0)
+ return -1;
+ }
+
+ uflags &= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR;
+ maildir_uidlist_sync_remove(ctx->uidlist_sync_ctx, filename);
+ ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
+ filename, uflags);
+ i_assert(ret > 0);
+
+ /* give the new UID to it immediately */
+ maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);
+
+ i_warning("Maildir %s: Expunged message reappeared, giving a new UID "
+ "(old uid=%u, file=%s)%s", mailbox_get_path(&ctx->mbox->box),
+ uid, filename, !str_begins(filename, "msg.") ? "" :
+ " (Your MDA is saving MH files into Maildir?)");
+ return 0;
+}
+
+int maildir_sync_index_begin(struct maildir_mailbox *mbox,
+ struct maildir_sync_context *maildir_sync_ctx,
+ struct maildir_index_sync_context **ctx_r)
+{
+ struct mailbox *_box = &mbox->box;
+ struct maildir_index_sync_context *ctx;
+ struct mail_index_sync_ctx *sync_ctx;
+ struct mail_index_view *view;
+ struct mail_index_transaction *trans;
+ enum mail_index_sync_flags sync_flags;
+
+ sync_flags = index_storage_get_sync_flags(&mbox->box);
+ /* don't drop recent messages if we're saving messages */
+ if (maildir_sync_ctx == NULL)
+ sync_flags &= ENUM_NEGATE(MAIL_INDEX_SYNC_FLAG_DROP_RECENT);
+
+ if (index_storage_expunged_sync_begin(_box, &sync_ctx, &view,
+ &trans, sync_flags) < 0)
+ return -1;
+
+ ctx = i_new(struct maildir_index_sync_context, 1);
+ ctx->mbox = mbox;
+ ctx->maildir_sync_ctx = maildir_sync_ctx;
+ ctx->sync_ctx = sync_ctx;
+ ctx->view = view;
+ ctx->trans = trans;
+ ctx->keywords_sync_ctx =
+ maildir_keywords_sync_init(mbox->keywords, _box->index);
+ ctx->sync_changes =
+ index_sync_changes_init(ctx->sync_ctx, ctx->view, ctx->trans,
+ maildir_is_backend_readonly(mbox));
+ ctx->start_time = time(NULL);
+
+ *ctx_r = ctx;
+ return 0;
+}
+
+static bool
+maildir_index_header_has_changed(const struct maildir_index_header *old_hdr,
+ const struct maildir_index_header *new_hdr)
+{
+#define DIR_DELAYED_REFRESH(hdr, name) \
+ ((hdr)->name ## _check_time <= \
+ (hdr)->name ## _mtime + MAILDIR_SYNC_SECS)
+
+ if (old_hdr->new_mtime != new_hdr->new_mtime ||
+ old_hdr->new_mtime_nsecs != new_hdr->new_mtime_nsecs ||
+ old_hdr->cur_mtime != new_hdr->cur_mtime ||
+ old_hdr->cur_mtime_nsecs != new_hdr->cur_mtime_nsecs ||
+ old_hdr->uidlist_mtime != new_hdr->uidlist_mtime ||
+ old_hdr->uidlist_mtime_nsecs != new_hdr->uidlist_mtime_nsecs ||
+ old_hdr->uidlist_size != new_hdr->uidlist_size)
+ return TRUE;
+
+ return DIR_DELAYED_REFRESH(old_hdr, new) !=
+ DIR_DELAYED_REFRESH(new_hdr, new) ||
+ DIR_DELAYED_REFRESH(old_hdr, cur) !=
+ DIR_DELAYED_REFRESH(new_hdr, cur);
+}
+
+static void
+maildir_sync_index_update_ext_header(struct maildir_index_sync_context *ctx)
+{
+ struct maildir_mailbox *mbox = ctx->mbox;
+ const char *cur_path;
+ const void *data;
+ size_t data_size;
+ struct stat st;
+
+ cur_path = t_strconcat(mailbox_get_path(&mbox->box), "/cur", NULL);
+ if (ctx->update_maildir_hdr_cur && stat(cur_path, &st) == 0) {
+ if ((time_t)mbox->maildir_hdr.cur_check_time < st.st_mtime)
+ mbox->maildir_hdr.cur_check_time = st.st_mtime;
+ mbox->maildir_hdr.cur_mtime = st.st_mtime;
+ mbox->maildir_hdr.cur_mtime_nsecs = ST_MTIME_NSEC(st);
+ }
+
+ mail_index_get_header_ext(mbox->box.view, mbox->maildir_ext_id,
+ &data, &data_size);
+ if (data_size != sizeof(mbox->maildir_hdr) ||
+ maildir_index_header_has_changed(data, &mbox->maildir_hdr)) {
+ mail_index_update_header_ext(ctx->trans, mbox->maildir_ext_id,
+ 0, &mbox->maildir_hdr,
+ sizeof(mbox->maildir_hdr));
+ }
+}
+
+static int maildir_sync_index_finish(struct maildir_index_sync_context *ctx,
+ bool success)
+{
+ struct maildir_mailbox *mbox = ctx->mbox;
+ unsigned int time_diff;
+ int ret = success ? 0 : -1;
+
+ time_diff = time(NULL) - ctx->start_time;
+ if (time_diff >= MAILDIR_SYNC_TIME_WARN_SECS) {
+ i_warning("Maildir %s: Synchronization took %u seconds "
+ "(%u new msgs, %u flag change attempts, "
+ "%u expunge attempts)",
+ mailbox_get_path(&ctx->mbox->box), time_diff,
+ ctx->new_msgs_count, ctx->flag_change_count,
+ ctx->expunge_count);
+ mail_index_sync_no_warning(ctx->sync_ctx);
+ }
+
+ if (ret < 0)
+ mail_index_sync_rollback(&ctx->sync_ctx);
+ else {
+ maildir_sync_index_update_ext_header(ctx);
+
+ /* Set syncing_commit=TRUE so that if any sync callbacks try
+ to access mails which got lost (eg. expunge callback trying
+ to open the file which was just unlinked) we don't try to
+ start a second index sync and crash. */
+ mbox->syncing_commit = TRUE;
+ if (mail_index_sync_commit(&ctx->sync_ctx) < 0) {
+ mailbox_set_index_error(&mbox->box);
+ ret = -1;
+ }
+ mbox->syncing_commit = FALSE;
+ }
+
+ index_storage_expunging_deinit(&mbox->box);
+ maildir_keywords_sync_deinit(&ctx->keywords_sync_ctx);
+ index_sync_changes_deinit(&ctx->sync_changes);
+ i_free(ctx);
+ return ret;
+}
+
+int maildir_sync_index_commit(struct maildir_index_sync_context **_ctx)
+{
+ struct maildir_index_sync_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+ return maildir_sync_index_finish(ctx, TRUE);
+}
+
+void maildir_sync_index_rollback(struct maildir_index_sync_context **_ctx)
+{
+ struct maildir_index_sync_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+ (void)maildir_sync_index_finish(ctx, FALSE);
+}
+
+static int uint_cmp(const unsigned int *i1, const unsigned int *i2)
+{
+ if (*i1 < *i2)
+ return -1;
+ else if (*i1 > *i2)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+maildir_sync_mail_keywords(struct maildir_index_sync_context *ctx, uint32_t seq)
+{
+ struct mailbox *box = &ctx->mbox->box;
+ struct mail_keywords *kw;
+ unsigned int i, j, old_count, new_count;
+ const unsigned int *old_indexes, *new_indexes;
+ bool have_indexonly_keywords;
+ int diff;
+
+ mail_index_lookup_keywords(ctx->view, seq, &ctx->idx_keywords);
+ if (index_keyword_array_cmp(&ctx->keywords, &ctx->idx_keywords)) {
+ /* no changes - we should get here usually */
+ return;
+ }
+
+ /* sort the keywords */
+ array_sort(&ctx->idx_keywords, uint_cmp);
+ array_sort(&ctx->keywords, uint_cmp);
+
+ /* drop keywords that are in index-only. we don't want to touch them. */
+ old_indexes = array_get(&ctx->idx_keywords, &old_count);
+ have_indexonly_keywords = FALSE;
+ for (i = old_count; i > 0; i--) {
+ if (maildir_keywords_idx_char(ctx->keywords_sync_ctx,
+ old_indexes[i-1]) == '\0') {
+ have_indexonly_keywords = TRUE;
+ array_delete(&ctx->idx_keywords, i-1, 1);
+ }
+ }
+
+ if (!have_indexonly_keywords) {
+ /* no index-only keywords found, so something changed.
+ just replace them all. */
+ kw = mail_index_keywords_create_from_indexes(box->index,
+ &ctx->keywords);
+ mail_index_update_keywords(ctx->trans, seq, MODIFY_REPLACE, kw);
+ mail_index_keywords_unref(&kw);
+ return;
+ }
+
+ /* check again if non-index-only keywords changed */
+ if (index_keyword_array_cmp(&ctx->keywords, &ctx->idx_keywords))
+ return;
+
+ /* we can't reset all the keywords or we'd drop indexonly keywords too.
+ so first remove the unwanted keywords and then add back the wanted
+ ones. we can get these lists easily by removing common elements
+ from old and new keywords. */
+ new_indexes = array_get(&ctx->keywords, &new_count);
+ for (i = j = 0; i < old_count && j < new_count; ) {
+ diff = (int)old_indexes[i] - (int)new_indexes[j];
+ if (diff == 0) {
+ array_delete(&ctx->keywords, j, 1);
+ array_delete(&ctx->idx_keywords, i, 1);
+ old_indexes = array_get(&ctx->idx_keywords, &old_count);
+ new_indexes = array_get(&ctx->keywords, &new_count);
+ } else if (diff < 0) {
+ i++;
+ } else {
+ j++;
+ }
+ }
+
+ if (array_count(&ctx->idx_keywords) > 0) {
+ kw = mail_index_keywords_create_from_indexes(box->index,
+ &ctx->idx_keywords);
+ mail_index_update_keywords(ctx->trans, seq, MODIFY_REMOVE, kw);
+ mail_index_keywords_unref(&kw);
+ }
+
+ if (array_count(&ctx->keywords) > 0) {
+ kw = mail_index_keywords_create_from_indexes(box->index,
+ &ctx->keywords);
+ mail_index_update_keywords(ctx->trans, seq, MODIFY_ADD, kw);
+ mail_index_keywords_unref(&kw);
+ }
+}
+
+int maildir_sync_index(struct maildir_index_sync_context *ctx,
+ bool partial)
+{
+ struct maildir_mailbox *mbox = ctx->mbox;
+ struct mail_index_view *view = ctx->view;
+ struct mail_index_view *view2;
+ struct maildir_uidlist_iter_ctx *iter;
+ struct mail_index_transaction *trans = ctx->trans;
+ const struct mail_index_header *hdr;
+ struct mail_index_header empty_hdr;
+ const struct mail_index_record *rec;
+ uint32_t seq, seq2, uid, prev_uid;
+ enum maildir_uidlist_rec_flag uflags;
+ const char *filename;
+ uint32_t uid_validity, next_uid, hdr_next_uid, first_recent_uid;
+ uint32_t first_uid;
+ unsigned int changes = 0;
+ int ret = 0;
+ time_t time_before_sync;
+ guid_128_t expunged_guid_128;
+ enum mail_flags private_flags_mask;
+ bool expunged, full_rescan = FALSE;
+
+ i_assert(!mbox->syncing_commit);
+
+ first_uid = 1;
+ hdr = mail_index_get_header(view);
+ uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
+ if (uid_validity != hdr->uid_validity &&
+ uid_validity != 0 && hdr->uid_validity != 0) {
+ /* uidvalidity changed and index isn't being synced for the
+ first time, reset the index so we can add all messages as
+ new */
+ i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
+ mailbox_get_path(&ctx->mbox->box),
+ hdr->uid_validity, uid_validity);
+ mail_index_reset(trans);
+ mailbox_recent_flags_reset(&mbox->box);
+
+ first_uid = hdr->messages_count + 1;
+ i_zero(&empty_hdr);
+ empty_hdr.next_uid = 1;
+ hdr = &empty_hdr;
+ }
+ hdr_next_uid = hdr->next_uid;
+
+ ctx->mbox->box.tmp_sync_view = view;
+ private_flags_mask = mailbox_get_private_flags_mask(&mbox->box);
+ time_before_sync = time(NULL);
+ mbox->syncing_commit = TRUE;
+ seq = prev_uid = 0; first_recent_uid = I_MAX(hdr->first_recent_uid, 1);
+ i_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS);
+ i_array_init(&ctx->idx_keywords, MAILDIR_MAX_KEYWORDS);
+ iter = maildir_uidlist_iter_init(mbox->uidlist);
+ while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
+ maildir_filename_flags_get(ctx->keywords_sync_ctx, filename,
+ &ctx->flags, &ctx->keywords);
+
+ i_assert(uid > prev_uid);
+ prev_uid = uid;
+
+ /* the private flags are kept only in indexes. don't use them
+ at all even for newly seen mails */
+ ctx->flags &= ENUM_NEGATE(private_flags_mask);
+
+ again:
+ seq++;
+ ctx->uid = uid;
+
+ if (seq > hdr->messages_count) {
+ if (uid < hdr_next_uid) {
+ if (maildir_handle_uid_insertion(ctx, uflags,
+ filename,
+ uid) < 0)
+ ret = -1;
+ seq--;
+ continue;
+ }
+
+ /* Trust uidlist recent flags only for newly added
+ messages. When saving/copying messages with flags
+ they're stored to cur/ and uidlist treats them
+ as non-recent. */
+ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) == 0) {
+ if (uid >= first_recent_uid)
+ first_recent_uid = uid + 1;
+ }
+
+ hdr_next_uid = uid + 1;
+ mail_index_append(trans, uid, &seq);
+ mail_index_update_flags(trans, seq, MODIFY_REPLACE,
+ ctx->flags);
+ if (array_count(&ctx->keywords) > 0) {
+ struct mail_keywords *kw;
+
+ kw = mail_index_keywords_create_from_indexes(
+ mbox->box.index, &ctx->keywords);
+ mail_index_update_keywords(trans, seq,
+ MODIFY_REPLACE, kw);
+ mail_index_keywords_unref(&kw);
+ }
+ continue;
+ }
+
+ rec = mail_index_lookup(view, seq);
+ if (uid > rec->uid) {
+ /* already expunged (no point in showing guid in the
+ expunge record anymore) */
+ mail_index_expunge(ctx->trans, seq);
+ goto again;
+ }
+
+ if (uid < rec->uid) {
+ if (maildir_handle_uid_insertion(ctx, uflags,
+ filename, uid) < 0)
+ ret = -1;
+ seq--;
+ continue;
+ }
+
+ index_sync_changes_read(ctx->sync_changes, ctx->uid, &expunged,
+ expunged_guid_128);
+ if (expunged) {
+ if (!maildir_expunge_is_valid_guid(ctx, ctx->uid,
+ filename,
+ expunged_guid_128))
+ continue;
+ if (maildir_file_do(mbox, ctx->uid,
+ maildir_expunge, ctx) >= 0) {
+ /* successful expunge */
+ mail_index_expunge(ctx->trans, seq);
+ }
+ if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
+ maildir_sync_notify(ctx->maildir_sync_ctx);
+ continue;
+ }
+
+ /* the private flags are stored only in indexes, keep them */
+ ctx->flags |= rec->flags & private_flags_mask;
+
+ if (index_sync_changes_have(ctx->sync_changes)) {
+ /* apply flag changes to maildir */
+ if (maildir_file_do(mbox, ctx->uid,
+ maildir_sync_flags, ctx) < 0)
+ ctx->flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
+ if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
+ maildir_sync_notify(ctx->maildir_sync_ctx);
+ }
+
+ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
+ /* partial syncing */
+ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) {
+ /* we last saw this mail in new/, but it's
+ not there anymore. possibly expunged,
+ make sure. */
+ full_rescan = TRUE;
+ }
+ continue;
+ }
+
+ if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
+ /* we haven't been able to update maildir with this
+ record's flag changes. don't sync them. */
+ continue;
+ }
+
+ if (ctx->flags != (rec->flags & MAIL_FLAGS_NONRECENT)) {
+ mail_index_update_flags(trans, seq, MODIFY_REPLACE,
+ ctx->flags);
+ }
+
+ maildir_sync_mail_keywords(ctx, seq);
+ }
+ maildir_uidlist_iter_deinit(&iter);
+
+ if (!partial) {
+ /* expunge the rest */
+ for (seq++; seq <= hdr->messages_count; seq++)
+ mail_index_expunge(ctx->trans, seq);
+ }
+
+ /* add \Recent flags. use updated view so it contains newly
+ appended messages. */
+ view2 = mail_index_transaction_open_updated_view(trans);
+ if (mail_index_lookup_seq_range(view2, first_recent_uid, (uint32_t)-1,
+ &seq, &seq2) && seq2 >= first_uid) {
+ if (seq < first_uid) {
+ /* UIDVALIDITY changed, skip over the old messages */
+ seq = first_uid;
+ }
+ mailbox_recent_flags_set_seqs(&mbox->box, view2, seq, seq2);
+ }
+ mail_index_view_close(&view2);
+
+ if (ctx->uidlist_sync_ctx != NULL) {
+ if (maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx,
+ TRUE) < 0)
+ ret = -1;
+ }
+
+ mailbox_sync_notify(&mbox->box, 0, 0);
+ ctx->mbox->box.tmp_sync_view = NULL;
+
+ /* check cur/ mtime later. if we came here from saving messages they
+ could still be moved to cur/ directory. */
+ ctx->update_maildir_hdr_cur = TRUE;
+ mbox->maildir_hdr.cur_check_time = time_before_sync;
+
+ if (uid_validity == 0) {
+ uid_validity = hdr->uid_validity != 0 ? hdr->uid_validity :
+ maildir_get_uidvalidity_next(mbox->box.list);
+ maildir_uidlist_set_uid_validity(mbox->uidlist, uid_validity);
+ }
+ maildir_uidlist_set_next_uid(mbox->uidlist, hdr_next_uid, FALSE);
+
+ if (uid_validity != hdr->uid_validity) {
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, uid_validity),
+ &uid_validity, sizeof(uid_validity), TRUE);
+ }
+
+ next_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
+ if (hdr_next_uid < next_uid) {
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, next_uid),
+ &next_uid, sizeof(next_uid), FALSE);
+ }
+
+ i_assert(hdr->first_recent_uid <= first_recent_uid);
+ if (hdr->first_recent_uid < first_recent_uid) {
+ mail_index_update_header(ctx->trans,
+ offsetof(struct mail_index_header, first_recent_uid),
+ &first_recent_uid, sizeof(first_recent_uid), FALSE);
+ }
+ array_free(&ctx->keywords);
+ array_free(&ctx->idx_keywords);
+ mbox->syncing_commit = FALSE;
+ return ret < 0 ? -1 : (full_rescan ? 0 : 1);
+}
+
+static unsigned int
+maildir_list_get_ext_id(struct maildir_mailbox *mbox,
+ struct mail_index_view *view)
+{
+ if (mbox->maildir_list_index_ext_id == (uint32_t)-1) {
+ mbox->maildir_list_index_ext_id =
+ mail_index_ext_register(mail_index_view_get_index(view),
+ "maildir", 0,
+ sizeof(struct maildir_list_index_record),
+ sizeof(uint32_t));
+ }
+ return mbox->maildir_list_index_ext_id;
+}
+
+int maildir_list_index_has_changed(struct mailbox *box,
+ struct mail_index_view *list_view,
+ uint32_t seq, bool quick,
+ const char **reason_r)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+ const struct maildir_list_index_record *rec;
+ const void *data;
+ const char *root_dir, *new_dir, *cur_dir;
+ struct stat st;
+ uint32_t ext_id;
+ bool expunged;
+ int ret;
+
+ ret = index_storage_list_index_has_changed(box, list_view, seq,
+ quick, reason_r);
+ if (ret != 0 || box->storage->set->mailbox_list_index_very_dirty_syncs)
+ return ret;
+ if (mbox->storage->set->maildir_very_dirty_syncs) {
+ /* we don't track cur/new directories with dirty syncs */
+ return 0;
+ }
+
+ ext_id = maildir_list_get_ext_id(mbox, list_view);
+ mail_index_lookup_ext(list_view, seq, ext_id, &data, &expunged);
+ rec = data;
+
+ if (rec == NULL) {
+ *reason_r = "Maildir record is missing";
+ return 1;
+ } else if (expunged) {
+ *reason_r = "Maildir record is expunged";
+ return 1;
+ } else if (rec->new_mtime == 0) {
+ /* not synced */
+ *reason_r = "Maildir record new_mtime=0";
+ return 1;
+ } else if (rec->cur_mtime == 0) {
+ /* dirty-synced */
+ *reason_r = "Maildir record cur_mtime=0";
+ return 1;
+ }
+
+ ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &root_dir);
+ if (ret < 0)
+ return ret;
+ i_assert(ret > 0);
+
+ /* check if new/ changed */
+ new_dir = t_strconcat(root_dir, "/new", NULL);
+ if (stat(new_dir, &st) < 0) {
+ mailbox_set_critical(box, "stat(%s) failed: %m", new_dir);
+ return -1;
+ }
+ if ((time_t)rec->new_mtime != st.st_mtime) {
+ *reason_r = t_strdup_printf(
+ "Maildir new_mtime changed %u != %"PRIdTIME_T,
+ rec->new_mtime, st.st_mtime);
+ return 1;
+ }
+
+ /* check if cur/ changed */
+ cur_dir = t_strconcat(root_dir, "/cur", NULL);
+ if (stat(cur_dir, &st) < 0) {
+ mailbox_set_critical(box, "stat(%s) failed: %m", cur_dir);
+ return -1;
+ }
+ if ((time_t)rec->cur_mtime != st.st_mtime) {
+ *reason_r = t_strdup_printf(
+ "Maildir cur_mtime changed %u != %"PRIdTIME_T,
+ rec->cur_mtime, st.st_mtime);
+ return 1;
+ }
+ return 0;
+}
+
+void maildir_list_index_update_sync(struct mailbox *box,
+ struct mail_index_transaction *trans,
+ uint32_t seq)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+ struct mail_index_view *list_view;
+ const struct maildir_index_header *mhdr = &mbox->maildir_hdr;
+ const struct maildir_list_index_record *old_rec;
+ struct maildir_list_index_record new_rec;
+ const void *data;
+ uint32_t ext_id;
+ bool expunged;
+
+ index_storage_list_index_update_sync(box, trans, seq);
+ if (mbox->storage->set->maildir_very_dirty_syncs) {
+ /* we don't track cur/new directories with dirty syncs */
+ return;
+ }
+
+ /* get the current record */
+ list_view = mail_index_transaction_get_view(trans);
+ ext_id = maildir_list_get_ext_id(mbox, list_view);
+ mail_index_lookup_ext(list_view, seq, ext_id, &data, &expunged);
+ if (expunged)
+ return;
+ old_rec = data;
+
+ i_zero(&new_rec);
+ if (mhdr->new_check_time <= mhdr->new_mtime + MAILDIR_SYNC_SECS ||
+ mhdr->cur_check_time <= mhdr->cur_mtime + MAILDIR_SYNC_SECS) {
+ /* dirty, we need a refresh next time */
+ } else {
+ new_rec.new_mtime = mhdr->new_mtime;
+ new_rec.cur_mtime = mhdr->cur_mtime;
+ }
+
+ if (old_rec == NULL ||
+ memcmp(old_rec, &new_rec, sizeof(*old_rec)) != 0)
+ mail_index_update_ext(trans, seq, ext_id, &new_rec, NULL);
+}