mbox-sync.c revision c000c8eca8f24b2a0c76393ec4bbf76a505a4983
5a580c3a38ced62d4bcc95b8ac7c4f2935b5d294Timo Sirainen/* Copyright (c) 2004-2010 Dovecot authors, see the included COPYING file */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen Modifying mbox can be slow, so we try to do it all at once minimizing the
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen required disk I/O. We may need to:
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Update message flags in Status, X-Status and X-Keywords headers
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Write missing X-UID and X-IMAPbase headers
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Write missing or broken Content-Length header if there's space
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Expunge specified messages
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen Here's how we do it:
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Start reading the mails from the beginning
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - X-Keywords, X-UID and X-IMAPbase headers may contain padding at the end
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen of them, remember how much each message has and offset to beginning of the
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - If header needs to be rewritten and there's enough space, do it
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - If we didn't have enough space, remember how much was missing
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Continue reading and counting the padding in each message. If available
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen padding is enough to rewrite all the previous messages needing it, do it
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - When we encounter expunged message, treat all of it as padding and
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen rewrite previous messages if needed (and there's enough space).
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen Afterwards keep moving messages backwards to fill the expunged space.
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen Moving is done by rewriting each message's headers, with possibly adding
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen missing Content-Length header and padding. Message bodies are moved
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen without modifications.
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - If we encounter end of file, grow the file and rewrite needed messages
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen - Rewriting is done by moving message body forward, rewriting message's
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen header and doing the same for previous message, until all of them are
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen/* The text below was taken exactly as c-client wrote it to my mailbox,
81d3c215bb1fdbda2cf7ccd9519f6b4fd03c3791Timo Sirainen so it's probably copyrighted by University of Washington. */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen"This text is part of the internal format of your mail folder, and is not\n" \
8ddc45fe2080010715c212ecbb2b12b6734f6d4bTimo Sirainen"a real message. It is created automatically by the mail system software.\n" \
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen"If deleted, important folder data will be lost, and it will be re-created\n" \
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen"with the data reset to initial values.\n"
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainenvoid mbox_sync_set_critical(struct mbox_sync_context *sync_ctx,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen const char *fmt, ...)
13f6c879a84b3edd2fcc8f9832812be1f8c5d3b6Timo Sirainen mail_storage_set_critical(&sync_ctx->mbox->storage->storage,
13f6c879a84b3edd2fcc8f9832812be1f8c5d3b6Timo Sirainen "mbox file %s was modified while we were syncing, "
13f6c879a84b3edd2fcc8f9832812be1f8c5d3b6Timo Sirainen "check your locking settings",
13f6c879a84b3edd2fcc8f9832812be1f8c5d3b6Timo Sirainen mail_storage_set_critical(&sync_ctx->mbox->storage->storage,
13f6c879a84b3edd2fcc8f9832812be1f8c5d3b6Timo Sirainen "Sync failed for mbox file %s: %s",
13f6c879a84b3edd2fcc8f9832812be1f8c5d3b6Timo Sirainenint mbox_sync_seek(struct mbox_sync_context *sync_ctx, uoff_t from_offset)
8ddc45fe2080010715c212ecbb2b12b6734f6d4bTimo Sirainen if (istream_raw_mbox_seek(sync_ctx->input, from_offset) < 0) {
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen "Unexpectedly lost From-line at offset %"PRIuUOFF_T,
adcb46fe2c6d1139dfbd8ab08a8fbe96e53f8fd6Timo Sirainenvoid mbox_sync_file_update_ext_modified(struct mbox_sync_context *sync_ctx)
df452e9628fe8d3356c42dd644b020ea9733c0c1Timo Sirainen /* Do this even if ext_modified is already set. Expunging code relies
df452e9628fe8d3356c42dd644b020ea9733c0c1Timo Sirainen on last_stat being updated. */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mbox_set_syscall_error(sync_ctx->mbox, "fstat()");
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if (st.st_size != sync_ctx->last_stat.st_size ||
81d3c215bb1fdbda2cf7ccd9519f6b4fd03c3791Timo Sirainenvoid mbox_sync_file_updated(struct mbox_sync_context *sync_ctx, bool dirty)
81d3c215bb1fdbda2cf7ccd9519f6b4fd03c3791Timo Sirainen /* just mark the stat as dirty. */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if (fstat(sync_ctx->write_fd, &sync_ctx->last_stat) < 0)
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen mbox_set_syscall_error(sync_ctx->mbox, "fstat()");
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainenmbox_sync_read_next_mail(struct mbox_sync_context *sync_ctx,
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen /* get EOF */
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen (void)istream_raw_mbox_get_header_offset(sync_ctx->input);
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen istream_raw_mbox_get_start_offset(sync_ctx->input);
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen istream_raw_mbox_get_header_offset(sync_ctx->input);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mbox_sync_parse_next_mail(sync_ctx->input, mail_ctx);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen i_assert(sync_ctx->input->v_offset != mail_ctx->mail.from_offset ||
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if (istream_raw_mbox_is_corrupted(sync_ctx->input))
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen istream_raw_mbox_get_body_size(sync_ctx->input,
ecd69c4e8371853667e01b0c16d436ef7f7393e2Timo Sirainen i_assert(mail_ctx->mail.body_size < OFF_T_MAX);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if ((mail_ctx->mail.flags & MAIL_RECENT) != 0 &&
adcb46fe2c6d1139dfbd8ab08a8fbe96e53f8fd6Timo Sirainen /* need to add 'O' flag to Status-header */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainenstatic void mbox_sync_read_index_syncs(struct mbox_sync_context *sync_ctx,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen uint8_t expunged_guid_128[MAIL_GUID_128_SIZE];
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* nothing for this or the future ones */
9ed77dd00248e88d731ec129116db5dddc0dd3b5Timo Sirainen index_sync_changes_read(sync_ctx->sync_changes, uid, sync_expunge_r,
9ed77dd00248e88d731ec129116db5dddc0dd3b5Timo Sirainen /* we can't expunge anything from read-only mboxes */
adcb46fe2c6d1139dfbd8ab08a8fbe96e53f8fd6Timo Sirainenmbox_sync_read_index_rec(struct mbox_sync_context *sync_ctx,
adcb46fe2c6d1139dfbd8ab08a8fbe96e53f8fd6Timo Sirainen uint32_t uid, const struct mail_index_record **rec_r)
9ed77dd00248e88d731ec129116db5dddc0dd3b5Timo Sirainen mail_index_view_get_messages_count(sync_ctx->sync_view);
9ed77dd00248e88d731ec129116db5dddc0dd3b5Timo Sirainen rec = mail_index_lookup(sync_ctx->sync_view, sync_ctx->idx_seq);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* externally expunged message, remove from index */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if (rec == NULL && uid < sync_ctx->idx_next_uid) {
df452e9628fe8d3356c42dd644b020ea9733c0c1Timo Sirainen /* this UID was already in index and it was expunged */
df452e9628fe8d3356c42dd644b020ea9733c0c1Timo Sirainen "Expunged message reappeared to mailbox "
df452e9628fe8d3356c42dd644b020ea9733c0c1Timo Sirainen "(UID %u < %u, seq=%u, idx_msgs=%u)",
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* new UID in the middle of the mailbox - shouldn't happen */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen "UID inserted in the middle of mailbox "
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen "(%u > %u, seq=%u, idx_msgs=%u)",
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen rec->uid, uid, sync_ctx->seq, messages_count);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainenstatic void mbox_sync_find_index_md5(struct mbox_sync_context *sync_ctx,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen unsigned char hdr_md5_sum[],
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_index_view_get_messages_count(sync_ctx->sync_view);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen rec = mail_index_lookup(sync_ctx->sync_view, sync_ctx->idx_seq);
00bf64c70c231f647c12c2fd49925ef73cb07f07Timo Sirainen if (data != NULL && memcmp(data, hdr_md5_sum, 16) == 0)
00bf64c70c231f647c12c2fd49925ef73cb07f07Timo Sirainen /* externally expunged message, remove from index */
00bf64c70c231f647c12c2fd49925ef73cb07f07Timo Sirainen mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq);
00bf64c70c231f647c12c2fd49925ef73cb07f07Timo Sirainenmbox_sync_update_from_offset(struct mbox_sync_context *sync_ctx,
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen /* see if from_offset needs updating */
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen mail_index_lookup_ext(sync_ctx->sync_view, sync_ctx->idx_seq,
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen *((const uint64_t *)data) == mail->from_offset)
2ed248fba21fdd3abcc4bb4d07c2822b9ba3f66fTimo Sirainen mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainenmbox_sync_update_index_keywords(struct mbox_sync_mail_context *mail_ctx)
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen struct mail_index *index = sync_ctx->mbox->box.index;
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen keywords = !array_is_created(&mail_ctx->mail.keywords) ?
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen mail_index_keywords_create_from_indexes(index,
530f80fcbfa8be7378ff0d36ec7ecef41f198e2cTimo Sirainen mail_index_update_keywords(sync_ctx->t, sync_ctx->idx_seq,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainenmbox_sync_update_md5_if_changed(struct mbox_sync_mail_context *mail_ctx)
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_index_lookup_ext(sync_ctx->sync_view, sync_ctx->idx_seq,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen sync_ctx->mbox->md5hdr_ext_idx, &ext_data, NULL);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen memcmp(mail_ctx->hdr_md5_sum, ext_data, 16) != 0) {
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainenstatic void mbox_sync_get_dirty_flags(struct mbox_sync_mail_context *mail_ctx,
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* default to undirtying the message. it gets added back if
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen flags/keywords don't match what is in the index. */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_ctx->mail.flags &= ~MAIL_INDEX_MAIL_FLAG_DIRTY;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* replace flags */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen idx_flags = rec->flags & MAIL_FLAGS_NONRECENT;
b1a2d2042e8c7e99983175eb462b82cc7a8cb70bTimo Sirainen mbox_flags = mail_ctx->mail.flags & MAIL_FLAGS_NONRECENT;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_ctx->mail.flags = (mail_ctx->mail.flags & MAIL_RECENT) |
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* replace keywords */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_index_lookup_keywords(sync_ctx->sync_view, sync_ctx->idx_seq,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if (!index_keyword_array_cmp(&idx_keywords, &mail_ctx->mail.keywords)) {
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen mail_ctx->mail.flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if (!array_is_created(&mail_ctx->mail.keywords)) {
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen array_append_array(&mail_ctx->mail.keywords, &idx_keywords);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainenstatic void mbox_sync_update_flags(struct mbox_sync_mail_context *mail_ctx,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen struct mbox_sync_mail *mail = &mail_ctx->mail;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen ARRAY_TYPE(keyword_indexes) orig_keywords = ARRAY_INIT;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* flags and keywords are dirty. replace the current
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen ones from the flags in index file. */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen flags = orig_flags = mail->flags & MAIL_FLAGS_NONRECENT;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen array_append_array(&orig_keywords, &mail->keywords);
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen /* apply new changes */
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen index_sync_changes_apply(sync_ctx->sync_changes,
adcb46fe2c6d1139dfbd8ab08a8fbe96e53f8fd6Timo Sirainen !index_keyword_array_cmp(&mail->keywords, &orig_keywords)) {
adcb46fe2c6d1139dfbd8ab08a8fbe96e53f8fd6Timo Sirainen mail->flags = flags | (mail->flags & MAIL_RECENT) |
df452e9628fe8d3356c42dd644b020ea9733c0c1Timo Sirainen if (sync_type != 0 && box->v.sync_notify != NULL) {
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainenstatic void mbox_sync_update_index(struct mbox_sync_mail_context *mail_ctx,
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen struct mbox_sync_mail *mail = &mail_ctx->mail;
MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
bool dirty;
const unsigned char *data;
if (from_line_size == 0)
const char *str;
int ret;
if (ret < 0) {
if (ret == 0) {
unsigned int i, count;
for (i = 0; i < count; i++) {
int ret;
if (ret < 0)
if (ret > 0) {
-move_diff);
move_diff = 0;
int ret;
bool deleted;
if (seq == 0) {
seq++;
if (ret < 0) {
if (deleted) {
if (ret == 0) {
old_offset) < 0) {
uid = 0;
int ret;
if (ret < 0) {
size) < 0) {
bool *skipped_mails)
int ret;
if (uid != 0) {
if (ret == 0) {
return ret;
return TRUE;
return FALSE;
bool partial)
int ret;
if (ret <= 0)
return ret;
uid = 0;
if (uid != 0) {
ret = 0;
if (ret == 0) {
uid = 0;
} else if (uid == 0 &&
&expunged);
if (!expunged) {
} T_END;
} T_END;
if (!expunged) {
} else if (partial) {
&partial,
if (ret <= 0)
if (ret < 0)
if (!skipped_mails)
unsigned int uid_validity;
int ret;
if (ret < 0) {
if (ret == 0) {
trailer_size = 0;
trailer_size = 0;
trailer_size) < 0)
if (offset == 0) {
const void *data;
if (ret < 0)
ret = 0;
return ret;
const void *data;
if (data_size == 0) {
bool empty;
bool *empty_r)
unsigned int *lock_id)
if (!changed)
*lock_id = 0;
if (changed) {
if (ret <= 0) {
if (ret < 0)
return ret;
bool expunged;
if (uid == 0) {
goto nothing_to_do;
if (*lock_id == 0) {
goto again;
if (ret < 0)
!readonly) {
return ret;
unsigned int lock_id = 0;
int ret;
if (lock_id != 0) {
if (ret < 0) {
unsigned int read_lock_id = 0;
return ret;
struct mailbox_sync_context *
int ret = 0;