From: Timo Sirainen Date: Mon, 14 Jun 2004 19:23:25 +0000 (+0300) Subject: major syncing code cleanups. the code finally looks almost readable. logic X-Git-Tag: 1.1.alpha1~3982 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dda2c506c8fc8ac2f88272de4523ded42baa0aa0;p=thirdparty%2Fdovecot%2Fcore.git major syncing code cleanups. the code finally looks almost readable. logic should have stayed same. --HG-- branch : HEAD --- diff --git a/src/lib-storage/index/mbox/mbox-sync-private.h b/src/lib-storage/index/mbox/mbox-sync-private.h index 4e3d991f1e..47b45ea49f 100644 --- a/src/lib-storage/index/mbox/mbox-sync-private.h +++ b/src/lib-storage/index/mbox/mbox-sync-private.h @@ -60,12 +60,24 @@ struct mbox_sync_mail_context { struct mbox_sync_context { struct index_mailbox *ibox; struct istream *input, *file_input; + unsigned int lock_id; int fd; + struct mail_index_sync_ctx *index_sync_ctx; + struct mail_index_view *sync_view; + struct mail_index_transaction *t; + const struct mail_index_header *hdr; + string_t *header, *from_line; uint32_t base_uid_validity, base_uid_last; + + /* state: */ + buffer_t *mails, *syncs; + struct mail_index_sync_rec sync_rec; + uint32_t prev_msg_uid, next_uid, first_uid; - off_t expunged_space; + uint32_t seq, idx_seq, need_space_seq; + off_t expunged_space, space_diff; }; int mbox_sync(struct index_mailbox *ibox, int last_commit); @@ -77,7 +89,7 @@ void mbox_sync_update_header(struct mbox_sync_mail_context *ctx, void mbox_sync_update_header_from(struct mbox_sync_mail_context *ctx, const struct mbox_sync_mail *mail); int mbox_sync_try_rewrite(struct mbox_sync_mail_context *ctx, off_t move_diff); -int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, buffer_t *mails_buf, +int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, uint32_t first_seq, uint32_t last_seq, off_t extra_space); int mbox_move(struct mbox_sync_context *sync_ctx, diff --git a/src/lib-storage/index/mbox/mbox-sync-rewrite.c b/src/lib-storage/index/mbox/mbox-sync-rewrite.c index 8e936b8f4b..c37c101e31 100644 --- a/src/lib-storage/index/mbox/mbox-sync-rewrite.c +++ b/src/lib-storage/index/mbox/mbox-sync-rewrite.c @@ -37,6 +37,12 @@ int mbox_move(struct mbox_sync_context *sync_ctx, ret = ret == (off_t)size ? 0 : -1; } + if (ret < 0) { + errno = output->stream_errno; + mbox_set_syscall_error(sync_ctx->ibox, + "o_stream_send_istream()"); + } + o_stream_unref(output); return (int)ret; } @@ -189,7 +195,7 @@ int mbox_sync_try_rewrite(struct mbox_sync_mail_context *ctx, off_t move_diff) new_hdr_size - ctx->header_first_change, ctx->hdr_offset + move_diff + ctx->header_first_change) < 0) { - // FIXME: error handling + mbox_set_syscall_error(ctx->sync_ctx->ibox, "pwrite_full()"); return -1; } istream_raw_mbox_flush(ctx->sync_ctx->input); @@ -249,17 +255,15 @@ static int mbox_sync_read_and_move(struct mbox_sync_context *sync_ctx, headers. */ offset = sync_ctx->file_input->v_offset; if (mbox_move(sync_ctx, offset + mails[idx+1].space, offset, - *end_offset - offset - mails[idx+1].space) < 0) { - // FIXME: error handling + *end_offset - offset - mails[idx+1].space) < 0) return -1; - } mails[idx+1].from_offset += mails[idx+1].space; *end_offset = offset + mails[idx+1].space - str_len(mail_ctx.header); if (pwrite_full(sync_ctx->fd, str_data(mail_ctx.header), str_len(mail_ctx.header), *end_offset) < 0) { - // FIXME: error handling + mbox_set_syscall_error(sync_ctx->ibox, "pwrite_full()"); return -1; } @@ -300,7 +304,7 @@ static int mbox_sync_fill_leftover(struct mbox_sync_context *sync_ctx, if (pwrite_full(sync_ctx->fd, str_data(mail_ctx.header), str_len(mail_ctx.header), start_offset) < 0) { - // FIXME: error handling + mbox_set_syscall_error(sync_ctx->ibox, "pwrite_full()"); return -1; } @@ -308,7 +312,7 @@ static int mbox_sync_fill_leftover(struct mbox_sync_context *sync_ctx, return 0; } -int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, buffer_t *mails_buf, +int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, uint32_t first_seq, uint32_t last_seq, off_t extra_space) { struct mbox_sync_mail *mails; @@ -320,7 +324,7 @@ int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, buffer_t *mails_buf, i_assert(first_seq != last_seq); i_assert(sync_ctx->ibox->mbox_lock_type == F_WRLCK); - mails = buffer_get_modifyable_data(mails_buf, &size); + mails = buffer_get_modifyable_data(sync_ctx->mails, &size); i_assert(size / sizeof(*mails) == last_seq - first_seq + 1); /* if there's expunges in mails[], we would get more correct balancing @@ -368,7 +372,6 @@ int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, buffer_t *mails_buf, dest_offset = offset + mails[idx+1].space; if (mbox_move(sync_ctx, dest_offset, offset, end_offset - dest_offset) < 0) { - // FIXME: error handling ret = -1; break; } @@ -390,10 +393,8 @@ int mbox_sync_rewrite(struct mbox_sync_context *sync_ctx, buffer_t *mails_buf, we need to move From-line to start_offset */ offset = mails[1].offset; if (mbox_move(sync_ctx, start_offset, end_offset, - offset - end_offset) < 0) { - // FIXME: error handling + offset - end_offset) < 0) ret = -1; - } mails[1].from_offset -= end_offset - start_offset; idx++; diff --git a/src/lib-storage/index/mbox/mbox-sync-update.c b/src/lib-storage/index/mbox/mbox-sync-update.c index 5faba2c984..72b5efa18c 100644 --- a/src/lib-storage/index/mbox/mbox-sync-update.c +++ b/src/lib-storage/index/mbox/mbox-sync-update.c @@ -1,4 +1,5 @@ #include "lib.h" +#include "ioloop.h" #include "buffer.h" #include "str.h" #include "message-parser.h" @@ -89,6 +90,13 @@ static void mbox_sync_add_missing_headers(struct mbox_sync_mail_context *ctx) if (ctx->mail.uid == ctx->sync_ctx->first_uid && ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] == (size_t)-1) { + if (ctx->sync_ctx->base_uid_validity == 0) { + ctx->sync_ctx->base_uid_validity = + ctx->sync_ctx->hdr->uid_validity == 0 ? + (uint32_t)ioloop_time : + ctx->sync_ctx->hdr->uid_validity; + } + ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] = str_len(ctx->header); str_printfa(ctx->header, "X-IMAPbase: %u %010u", ctx->sync_ctx->base_uid_validity, @@ -197,6 +205,9 @@ void mbox_sync_update_header(struct mbox_sync_mail_context *ctx, memcpy(old_keywords, ctx->mail.keywords, sizeof(old_keywords)); for (i = 0; i < size; i++) { + if (sync[i].type != MAIL_INDEX_SYNC_TYPE_FLAGS) + continue; + mail_index_sync_flags_apply(&sync[i], &ctx->mail.flags, ctx->mail.keywords); } diff --git a/src/lib-storage/index/mbox/mbox-sync.c b/src/lib-storage/index/mbox/mbox-sync.c index cda172fb0b..e3115de0ea 100644 --- a/src/lib-storage/index/mbox/mbox-sync.c +++ b/src/lib-storage/index/mbox/mbox-sync.c @@ -70,8 +70,10 @@ static int mbox_sync_grow_file(struct mbox_sync_context *sync_ctx, /* put the extra space between last message's header and body */ file_size = i_stream_get_size(sync_ctx->file_input) + grow_size; - if (file_set_size(sync_ctx->fd, file_size) < 0) + if (file_set_size(sync_ctx->fd, file_size) < 0) { + mbox_set_syscall_error(sync_ctx->ibox, "file_set_size()"); return -1; + } src_offset = mail_ctx->body_offset; mail_ctx->body_offset += grow_size; @@ -102,13 +104,18 @@ static void mbox_sync_buffer_delete_old(buffer_t *syncs_buf, uint32_t uid) buffer_set_used_size(syncs_buf, dest * sizeof(*sync)); } -static void -mbox_sync_next_mail(struct mbox_sync_context *sync_ctx, - struct mbox_sync_mail_context *mail_ctx, uint32_t seq) +static int +mbox_sync_read_next_mail(struct mbox_sync_context *sync_ctx, + struct mbox_sync_mail_context *mail_ctx) { + /* set input->eof */ + (void)istream_raw_mbox_get_header_offset(sync_ctx->input); + if (sync_ctx->input->eof) + return 0; + memset(mail_ctx, 0, sizeof(*mail_ctx)); mail_ctx->sync_ctx = sync_ctx; - mail_ctx->seq = seq; + mail_ctx->seq = ++sync_ctx->seq; mail_ctx->header = sync_ctx->header; mail_ctx->from_offset = @@ -116,7 +123,7 @@ mbox_sync_next_mail(struct mbox_sync_context *sync_ctx, mail_ctx->mail.offset = istream_raw_mbox_get_header_offset(sync_ctx->input); - if (seq > 1 && sync_ctx->first_uid == mail_ctx->mail.uid) { + if (mail_ctx->seq > 1 && sync_ctx->first_uid == mail_ctx->mail.uid) { /* First message was expunged and this is the next one. Skip \n header */ mail_ctx->from_offset++; @@ -136,6 +143,42 @@ mbox_sync_next_mail(struct mbox_sync_context *sync_ctx, mail_ctx->need_rewrite = TRUE; // FIXME: save it somewhere } + return 1; +} + +static int mbox_sync_read_index_syncs(struct mbox_sync_context *sync_ctx, + uint32_t uid, int *sync_expunge_r) +{ + struct mail_index_sync_rec *sync_rec = &sync_ctx->sync_rec; + int ret; + + *sync_expunge_r = FALSE; + + if (sync_ctx->ibox->readonly) + return 0; + + mbox_sync_buffer_delete_old(sync_ctx->syncs, uid); + while (uid >= sync_rec->uid1) { + if (sync_rec->uid1 != 0) { + i_assert(uid <= sync_rec->uid2); + buffer_append(sync_ctx->syncs, sync_rec, + sizeof(*sync_rec)); + + if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_EXPUNGE) + *sync_expunge_r = TRUE; + } + + ret = mail_index_sync_next(sync_ctx->index_sync_ctx, sync_rec); + if (ret < 0) + return -1; + + if (ret == 0) { + memset(sync_rec, 0, sizeof(*sync_rec)); + break; + } + } + + return 0; } static void mbox_sync_apply_index_syncs(buffer_t *syncs_buf, uint8_t *flags, @@ -151,6 +194,115 @@ static void mbox_sync_apply_index_syncs(buffer_t *syncs_buf, uint8_t *flags, mail_index_sync_flags_apply(&sync[i], flags, keywords); } +static int +mbox_sync_read_index_rec(struct mbox_sync_context *sync_ctx, + uint32_t uid, const struct mail_index_record **rec_r) +{ + const struct mail_index_record *rec = NULL; + uint32_t messages_count; + + messages_count = mail_index_view_get_message_count(sync_ctx->sync_view); + while (sync_ctx->idx_seq < messages_count) { + if (mail_index_lookup(sync_ctx->sync_view, + ++sync_ctx->idx_seq, &rec) < 0) { + mail_storage_set_index_error(sync_ctx->ibox); + return -1; + } + + if (uid <= rec->uid) + break; + + /* externally expunged message, remove from index */ + mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq); + rec = NULL; + } + + if (rec != NULL && rec->uid != uid) { + /* new UID in the middle of the mailbox - shouldn't happen */ + mail_storage_set_critical(sync_ctx->ibox->box.storage, + "mbox sync: UID inserted in the middle of mailbox " + "(%u > %u)", rec->uid, uid); + mail_index_mark_corrupted(sync_ctx->ibox->index); + return -1; + } + + *rec_r = rec; + return 0; +} + +static int +mbox_sync_update_from_offset(struct mbox_sync_context *sync_ctx, + struct mbox_sync_mail *mail, + int nocheck) +{ + const void *data; + uint64_t offset; + + if (!nocheck) { + /* see if from_offset needs updating */ + if (mail_index_lookup_extra(sync_ctx->sync_view, + sync_ctx->idx_seq, + sync_ctx->ibox->mbox_extra_idx, + &data) < 0) { + mail_storage_set_index_error(sync_ctx->ibox); + return -1; + } + + offset = *((const uint64_t *)data); + if (offset == mail->from_offset) + return 0; + } else { + offset = mail->from_offset; + } + + mail_index_update_extra_rec(sync_ctx->t, sync_ctx->idx_seq, + sync_ctx->ibox->mbox_extra_idx, &offset); + return 0; +} + +static int mbox_sync_update_index(struct mbox_sync_context *sync_ctx, + struct mbox_sync_mail *mail, + const struct mail_index_record *rec) +{ + keywords_mask_t idx_keywords; + uint8_t idx_flags, mbox_flags; + + if (rec == NULL) { + /* new message */ + mail_index_append(sync_ctx->t, mail->uid, &sync_ctx->idx_seq); + mbox_flags = mail->flags & (MAIL_FLAGS_MASK^MAIL_RECENT); + mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq, + MODIFY_REPLACE, mbox_flags, + mail->keywords); + } else { + /* see if flags changed */ + idx_flags = rec->flags; + memcpy(idx_keywords, rec->keywords, INDEX_KEYWORDS_BYTE_COUNT); + mbox_sync_apply_index_syncs(sync_ctx->syncs, + &idx_flags, idx_keywords); + + mbox_flags = (rec->flags & ~MAIL_FLAGS_MASK) | + (mail->flags & (MAIL_FLAGS_MASK^MAIL_RECENT)); + + if (idx_flags != mbox_flags || + memcmp(idx_keywords, mail->keywords, + INDEX_KEYWORDS_BYTE_COUNT) != 0) { + mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq, + MODIFY_REPLACE, mbox_flags, + mail->keywords); + } + } + + /* update from_offsets, but not if we're going to rewrite this message. + rewriting would just move it anyway. */ + if (sync_ctx->need_space_seq == 0) { + int nocheck = rec == NULL || sync_ctx->expunged_space > 0; + if (mbox_sync_update_from_offset(sync_ctx, mail, nocheck) < 0) + return -1; + } + return 0; +} + static int mbox_read_from_line(struct mbox_sync_mail_context *ctx) { struct istream *input = ctx->sync_ctx->file_input; @@ -185,12 +337,9 @@ mbox_write_from_line(struct mbox_sync_mail_context *ctx, off_t move_diff) { string_t *str = ctx->sync_ctx->from_line; - if (move_diff == 0) - return 0; - if (pwrite_full(ctx->sync_ctx->fd, str_data(str), str_len(str), ctx->from_offset + move_diff) < 0) { - // FIXME: error handling + mbox_set_syscall_error(ctx->sync_ctx->ibox, "pwrite_full()"); return -1; } @@ -218,451 +367,381 @@ update_from_offsets(struct index_mailbox *ibox, } } -static int mbox_sync_do(struct index_mailbox *ibox, - struct mail_index_sync_ctx *index_sync_ctx, - struct mail_index_view *sync_view, - buffer_t *syncs, struct mail_index_sync_rec *sync_rec, - int index_synced) +static int mbox_sync_handle_expunge(struct mbox_sync_mail_context *mail_ctx) { - /* a horrible function. needs some serious cleanups. */ - struct mbox_sync_context sync_ctx; - struct mbox_sync_mail_context mail_ctx; - struct mail_index_transaction *t; - const struct mail_index_header *hdr; - const struct mail_index_record *rec; - struct istream *input; - uint32_t seq, need_space_seq, idx_seq, messages_count; - uint8_t new_flags; - off_t space_diff, move_diff; - uoff_t offset, extra_space, trailer_size; - buffer_t *mails; - struct stat st; - int sync_expunge, update_from_offset, ret = 0; + if (mail_ctx->sync_ctx->ibox->mbox_lock_type == F_RDLCK) + return -2; + + mail_ctx->mail.offset = mail_ctx->from_offset; + mail_ctx->mail.space = + mail_ctx->body_offset - mail_ctx->from_offset + + mail_ctx->mail.body_size; + mail_ctx->mail.body_size = 0; + + if (mail_ctx->sync_ctx->seq == 1) { + /* expunging first message, fix space to contain next + message's \n header too since it will be removed. */ + mail_ctx->mail.space++; + } - if (mail_index_get_header(sync_view, &hdr) < 0) - return -1; + mail_ctx->sync_ctx->expunged_space += mail_ctx->mail.space; + return 0; +} - t = mail_index_transaction_begin(sync_view, FALSE); +static int mbox_sync_handle_header(struct mbox_sync_mail_context *mail_ctx) +{ + struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx; + off_t move_diff; + int ret; - memset(&sync_ctx, 0, sizeof(sync_ctx)); - sync_ctx.ibox = ibox; - sync_ctx.file_input = ibox->mbox_file_stream; - sync_ctx.input = ibox->mbox_stream; - sync_ctx.fd = ibox->mbox_fd; - sync_ctx.from_line = str_new(default_pool, 256); - sync_ctx.header = str_new(default_pool, 4096); - sync_ctx.next_uid = 1; + if (sync_ctx->first_uid == 0) + sync_ctx->first_uid = mail_ctx->mail.uid; - input = sync_ctx.input; - istream_raw_mbox_seek(input, 0); + if (sync_ctx->ibox->readonly) + return 0; - mails = buffer_create_dynamic(default_pool, 4096, (size_t)-1); + if (sync_ctx->expunged_space > 0 && sync_ctx->need_space_seq == 0) { + /* move the header backwards to fill expunged space */ + if (sync_ctx->ibox->mbox_lock_type == F_RDLCK) + return -2; - messages_count = mail_index_view_get_message_count(sync_view); + move_diff = -sync_ctx->expunged_space; - space_diff = 0; need_space_seq = 0; idx_seq = 0; rec = NULL; - for (seq = 0;;) { - /* set input->eof */ - (void)istream_raw_mbox_get_header_offset(input); - if (input->eof) - break; - seq++; + /* read the From-line before rewriting overwrites it */ + if (mbox_read_from_line(mail_ctx) < 0) + return -1; - mbox_sync_next_mail(&sync_ctx, &mail_ctx, seq); + mbox_sync_update_header(mail_ctx, sync_ctx->syncs); + if ((ret = mbox_sync_try_rewrite(mail_ctx, move_diff)) < 0) + return -1; - /* get all sync records related to this message */ - ret = 1; sync_expunge = FALSE; - mbox_sync_buffer_delete_old(syncs, mail_ctx.mail.uid); - while (mail_ctx.mail.uid >= sync_rec->uid1 && ret > 0) { - if (sync_rec->uid1 != 0) { - i_assert(mail_ctx.mail.uid <= sync_rec->uid2); - buffer_append(syncs, sync_rec, - sizeof(*sync_rec)); - - if (sync_rec->type == - MAIL_INDEX_SYNC_TYPE_EXPUNGE) - sync_expunge = TRUE; - } - ret = mail_index_sync_next(index_sync_ctx, sync_rec); - if (ret == 0) - memset(sync_rec, 0, sizeof(*sync_rec)); + if (ret > 0) { + /* rewrite successful, write From-line to + new location */ + mail_ctx->mail.from_offset += move_diff; + mail_ctx->mail.offset += move_diff; + if (mbox_write_from_line(mail_ctx, move_diff) < 0) + return -1; } - if (ret < 0) - break; + } else if (mail_ctx->need_rewrite || + buffer_get_used_size(sync_ctx->syncs) != 0) { + if (sync_ctx->ibox->mbox_lock_type == F_RDLCK) + return -2; - if (seq == 1 && sync_ctx.base_uid_validity == 0) { - sync_ctx.base_uid_validity = - hdr->uid_validity == 0 ? (uint32_t)ioloop_time : - hdr->uid_validity; - } + mbox_sync_update_header(mail_ctx, sync_ctx->syncs); + if ((ret = mbox_sync_try_rewrite(mail_ctx, move_diff)) < 0) + return -1; + } else { + /* nothing to do */ + return 0; + } - if (!sync_expunge && sync_ctx.first_uid == 0) - sync_ctx.first_uid = mail_ctx.mail.uid; - - if ((mail_ctx.need_rewrite || sync_ctx.expunged_space > 0 || - buffer_get_used_size(syncs) != 0) && !ibox->readonly) { - if (ibox->mbox_lock_type == F_RDLCK) { - ret = -2; - break; - } - - if (sync_expunge) { - ret = 1; - mail_ctx.mail.offset = mail_ctx.from_offset; - mail_ctx.mail.space = - mail_ctx.body_offset - - mail_ctx.from_offset + - mail_ctx.mail.body_size; - mail_ctx.mail.body_size = 0; - - if (seq == 1) - mail_ctx.mail.space++; - - sync_ctx.expunged_space += mail_ctx.mail.space; - } else { - move_diff = need_space_seq != 0 ? 0 : - -sync_ctx.expunged_space; - - /* read the From-line */ - if (move_diff != 0 && - mbox_read_from_line(&mail_ctx) < 0) { - ret = -1; - break; - } - - mbox_sync_update_header(&mail_ctx, syncs); - ret = mbox_sync_try_rewrite(&mail_ctx, - move_diff); - - if (ret > 0) { - mail_ctx.mail.from_offset += move_diff; - mail_ctx.mail.offset += move_diff; - ret = mbox_write_from_line(&mail_ctx, - move_diff); - if (ret == 0) - ret = 1; - } - - if (ret < 0) - break; - } - - if (ret == 0 && need_space_seq == 0) { - /* first mail with no space to write it */ - need_space_seq = seq; - space_diff = 0; - - if (sync_ctx.expunged_space > 0) { - /* create dummy message to describe - the expunged data */ - struct mbox_sync_mail mail; - - memset(&mail, 0, sizeof(mail)); - mail.offset = mail_ctx.from_offset - - sync_ctx.expunged_space; - mail.space = sync_ctx.expunged_space; - - need_space_seq--; - buffer_append(mails, &mail, - sizeof(mail)); - } - } + if (ret == 0 && sync_ctx->need_space_seq == 0) { + /* first mail with no space to write it */ + sync_ctx->need_space_seq = sync_ctx->seq; + sync_ctx->space_diff = 0; + + if (sync_ctx->expunged_space > 0) { + /* create dummy message to describe the expunged data */ + struct mbox_sync_mail mail; + + memset(&mail, 0, sizeof(mail)); + mail.offset = mail_ctx->from_offset - + sync_ctx->expunged_space; + mail.space = sync_ctx->expunged_space; + + sync_ctx->need_space_seq--; + buffer_append(sync_ctx->mails, &mail, sizeof(mail)); } + } + return 0; +} + +static int +mbox_sync_handle_missing_space(struct mbox_sync_mail_context *mail_ctx) +{ + struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx; + uoff_t extra_space; - /* update index */ - do { - if (rec != NULL && rec->uid >= mail_ctx.mail.uid) - break; + buffer_append(sync_ctx->mails, &mail_ctx->mail, sizeof(mail_ctx->mail)); - if (idx_seq >= messages_count) { - rec = NULL; - break; - } + sync_ctx->space_diff += mail_ctx->mail.space; + if (sync_ctx->space_diff < 0) + return 0; - if (rec != NULL) - mail_index_expunge(t, idx_seq); + /* we have enough space now */ + extra_space = MBOX_HEADER_EXTRA_SPACE * + (sync_ctx->seq - sync_ctx->need_space_seq + 1); - ret = mail_index_lookup(sync_view, ++idx_seq, &rec); - } while (ret == 0); + if (mail_ctx->mail.uid == 0 && + (uoff_t)sync_ctx->space_diff > extra_space) { + /* don't waste too much on extra spacing */ + sync_ctx->expunged_space = sync_ctx->space_diff - extra_space; + sync_ctx->space_diff = extra_space; + } else { + sync_ctx->expunged_space = 0; + } - if (ret < 0) - break; - if (rec != NULL && rec->uid != mail_ctx.mail.uid) { - /* new UID in the middle of the mailbox - - shouldn't happen */ - mail_storage_set_critical(ibox->box.storage, - "mbox sync: UID inserted in the middle " - "of mailbox (%u > %u)", - rec->uid, mail_ctx.mail.uid); - mail_index_mark_corrupted(ibox->index); - ret = -1; - break; - } + if (mbox_sync_rewrite(sync_ctx, sync_ctx->need_space_seq, sync_ctx->seq, + sync_ctx->space_diff) < 0) + return -1; - if (sync_expunge) { - if (rec != NULL) - mail_index_expunge(t, idx_seq); - update_from_offset = FALSE; - } else if (rec != NULL) { - /* see if flags changed */ - keywords_mask_t old_keywords; - uint8_t old_flags; - - old_flags = rec->flags; - memcpy(old_keywords, rec->keywords, - INDEX_KEYWORDS_BYTE_COUNT); - mbox_sync_apply_index_syncs(syncs, &old_flags, - old_keywords); - - new_flags = (rec->flags & ~MAIL_FLAGS_MASK) | - (mail_ctx.mail.flags & - (MAIL_FLAGS_MASK^MAIL_RECENT)); - - if (old_flags != new_flags || - memcmp(old_keywords, mail_ctx.mail.keywords, - INDEX_KEYWORDS_BYTE_COUNT) != 0) { - mail_index_update_flags(t, idx_seq, - MODIFY_REPLACE, - new_flags, - mail_ctx.mail.keywords); - } - - update_from_offset = sync_ctx.expunged_space > 0; - - /* we used this record */ - rec = NULL; - } else { - /* new message */ - mail_index_append(t, mail_ctx.mail.uid, &idx_seq); - new_flags = mail_ctx.mail.flags & - (MAIL_FLAGS_MASK^MAIL_RECENT); - mail_index_update_flags(t, idx_seq, MODIFY_REPLACE, - new_flags, - mail_ctx.mail.keywords); - update_from_offset = TRUE; - } + update_from_offsets(sync_ctx->ibox, sync_ctx->t, sync_ctx->mails, + sync_ctx->need_space_seq, sync_ctx->seq); + + /* mail_ctx may contain wrong data after rewrite, so make sure we + don't try to access it */ + memset(mail_ctx, 0, sizeof(*mail_ctx)); - if (update_from_offset) { - /* from_offset needs updating */ - uint64_t offset; - - offset = mail_ctx.mail.from_offset; - mail_index_update_extra_rec(t, idx_seq, - ibox->mbox_extra_idx, &offset); - } else if (need_space_seq == 0 && !sync_expunge) { - /* see if from_offset needs updating */ - const void *data; - uint64_t offset; - - if (mail_index_lookup_extra(sync_view, idx_seq, - ibox->mbox_extra_idx, - &data) < 0) { - ret = -1; - break; - } - - offset = *((const uint64_t *)data); - if (offset != mail_ctx.mail.from_offset) { - offset = mail_ctx.mail.from_offset; - mail_index_update_extra_rec(t, idx_seq, - ibox->mbox_extra_idx, &offset); - } + sync_ctx->need_space_seq = 0; + buffer_set_used_size(sync_ctx->mails, 0); + return 0; +} + +static int mbox_sync_parse_all(struct mbox_sync_context *sync_ctx, + struct mbox_sync_mail_context *mail_ctx) +{ + const struct mail_index_record *rec; + uint32_t uid, messages_count; + uoff_t offset; + int ret, expunged; + + sync_ctx->file_input = sync_ctx->ibox->mbox_file_stream; + sync_ctx->input = sync_ctx->ibox->mbox_stream; + sync_ctx->fd = sync_ctx->ibox->mbox_fd; + + istream_raw_mbox_seek(sync_ctx->input, 0); + + while ((ret = mbox_sync_read_next_mail(sync_ctx, mail_ctx)) > 0) { + uid = mail_ctx->mail.uid; + + /* get all sync records related to this message */ + if (mbox_sync_read_index_syncs(sync_ctx, uid, &expunged) < 0) + return -1; + + if (!expunged) + ret = mbox_sync_handle_header(mail_ctx); + else { + mail_ctx->mail.uid = 0; + ret = mbox_sync_handle_expunge(mail_ctx); + } + if (ret < 0) { + /* -1 = error, -2 = need exclusive lock */ + return ret; } - istream_raw_mbox_next(input, mail_ctx.mail.body_size); - offset = istream_raw_mbox_get_start_offset(input); + if (mbox_sync_read_index_rec(sync_ctx, uid, &rec) < 0) + return -1; - if (sync_ctx.expunged_space > 0 && !sync_expunge && - need_space_seq == 0) { - /* move the body */ - if (mbox_move(&sync_ctx, - mail_ctx.body_offset - - sync_ctx.expunged_space, - mail_ctx.body_offset, - mail_ctx.mail.body_size) < 0) { - ret = -1; - break; - } - i_stream_seek(input, offset); + if (!expunged) { + if (mbox_sync_update_index(sync_ctx, &mail_ctx->mail, + rec) < 0) + return -1; } - if (need_space_seq != 0) { - if (sync_expunge) - mail_ctx.mail.uid = 0; - - buffer_append(mails, &mail_ctx.mail, - sizeof(mail_ctx.mail)); - - space_diff += mail_ctx.mail.space; - if (space_diff >= 0) { - /* we have enough space now */ - extra_space = MBOX_HEADER_EXTRA_SPACE * - (seq - need_space_seq + 1); - if (sync_expunge && - (size_t)space_diff > extra_space) { - /* don't waste too much on extra - spacing */ - sync_ctx.expunged_space = - space_diff - extra_space; - space_diff = extra_space; - } else { - sync_ctx.expunged_space = 0; - } - if (mbox_sync_rewrite(&sync_ctx, mails, - need_space_seq, seq, - space_diff) < 0) { - ret = -1; - break; - } - - update_from_offsets(ibox, t, mails, - need_space_seq, seq); - - /* mail_ctx may contain wrong data after - rewrite, so make sure we don't try to access - it */ - memset(&mail_ctx, 0, sizeof(mail_ctx)); - i_stream_seek(input, offset); - - need_space_seq = 0; - buffer_set_used_size(mails, 0); - } + istream_raw_mbox_next(sync_ctx->input, + mail_ctx->mail.body_size); + offset = istream_raw_mbox_get_start_offset(sync_ctx->input); + + if (sync_ctx->need_space_seq != 0) { + if (mbox_sync_handle_missing_space(mail_ctx) < 0) + return -1; + i_stream_seek(sync_ctx->input, offset); + } else if (sync_ctx->expunged_space > 0 && !expunged) { + /* move the body */ + if (mbox_move(sync_ctx, + mail_ctx->body_offset - + sync_ctx->expunged_space, + mail_ctx->body_offset, + mail_ctx->mail.body_size) < 0) + return -1; + i_stream_seek(sync_ctx->input, offset); } } - trailer_size = i_stream_get_size(sync_ctx.file_input) - offset; - if (need_space_seq != 0 && ret >= 0) { - i_assert(space_diff < 0); - extra_space = MBOX_HEADER_EXTRA_SPACE * - (seq - need_space_seq + 1); - space_diff -= extra_space; + /* rest of the messages in index don't exist -> expunge them */ + messages_count = mail_index_view_get_message_count(sync_ctx->sync_view); + while (sync_ctx->idx_seq < messages_count) + mail_index_expunge(sync_ctx->t, ++sync_ctx->idx_seq); - space_diff += sync_ctx.expunged_space; - sync_ctx.expunged_space -= -space_diff; + return 0; +} - if (mail_ctx.have_eoh && !mail_ctx.updated) - str_append_c(mail_ctx.header, '\n'); +static int mbox_sync_handle_eof_updates(struct mbox_sync_context *sync_ctx, + struct mbox_sync_mail_context *mail_ctx) +{ + uoff_t offset, extra_space, trailer_size; - if (space_diff < 0 && - mbox_sync_grow_file(&sync_ctx, &mail_ctx, -space_diff) < 0) - ret = -1; - else if (mbox_sync_try_rewrite(&mail_ctx, 0) < 0) - ret = -1; - else if (seq != need_space_seq) { - buffer_set_used_size(mails, (seq-need_space_seq) * - sizeof(mail_ctx.mail)); - buffer_append(mails, &mail_ctx.mail, - sizeof(mail_ctx.mail)); - - if (mbox_sync_rewrite(&sync_ctx, mails, need_space_seq, - seq, extra_space) < 0) - ret = -1; - - update_from_offsets(ibox, t, mails, - need_space_seq, seq); - } - } + trailer_size = i_stream_get_size(sync_ctx->file_input) - + sync_ctx->file_input->v_offset; - if (sync_ctx.expunged_space > 0) { - /* copy trailer, then truncate the file */ - offset = i_stream_get_size(sync_ctx.file_input) - - sync_ctx.expunged_space - trailer_size; + if (sync_ctx->need_space_seq != 0) { + i_assert(sync_ctx->space_diff < 0); + extra_space = MBOX_HEADER_EXTRA_SPACE * + (sync_ctx->seq - sync_ctx->need_space_seq + 1); + sync_ctx->space_diff -= extra_space; - if (mbox_move(&sync_ctx, offset, - offset + sync_ctx.expunged_space, - trailer_size) < 0) - ret = -1; - else if (ftruncate(ibox->mbox_fd, offset + trailer_size) < 0) - ret = -1; + sync_ctx->space_diff += sync_ctx->expunged_space; + sync_ctx->expunged_space -= -sync_ctx->space_diff; - istream_raw_mbox_flush(input); - } + if (mail_ctx->have_eoh && !mail_ctx->updated) + str_append_c(mail_ctx->header, '\n'); - if (ret >= 0) { - if (rec != NULL) - mail_index_expunge(t, idx_seq); - while (idx_seq < messages_count) - mail_index_expunge(t, ++idx_seq); + if (sync_ctx->space_diff < 0 && + mbox_sync_grow_file(sync_ctx, mail_ctx, + -sync_ctx->space_diff) < 0) + return -1; - if (sync_ctx.base_uid_last+1 != sync_ctx.next_uid) { + if (mbox_sync_try_rewrite(mail_ctx, 0) < 0) + return -1; - // FIXME: rewrite X-IMAPbase header + if (sync_ctx->seq != sync_ctx->need_space_seq) { + buffer_set_used_size(sync_ctx->mails, + (sync_ctx->seq - + sync_ctx->need_space_seq) * + sizeof(mail_ctx->mail)); + buffer_append(sync_ctx->mails, &mail_ctx->mail, + sizeof(mail_ctx->mail)); + + if (mbox_sync_rewrite(sync_ctx, + sync_ctx->need_space_seq, + sync_ctx->seq, extra_space) < 0) + return -1; + + update_from_offsets(sync_ctx->ibox, sync_ctx->t, + sync_ctx->mails, + sync_ctx->need_space_seq, + sync_ctx->seq); } } - if (ret >= 0) { - /* only syncs left should be just appends (and their updates) - which weren't synced yet for some reason (crash). we'll just - ignore them, as we've overwritten them above. */ - while (mail_index_sync_next(index_sync_ctx, sync_rec) > 0) - ; - } + if (sync_ctx->expunged_space > 0) { + /* copy trailer, then truncate the file */ + offset = i_stream_get_size(sync_ctx->file_input) - + sync_ctx->expunged_space - trailer_size; - if (ret == 0) { - if (fstat(ibox->mbox_fd, &st) < 0) { - mbox_set_syscall_error(ibox, "fstat()"); - ret = -1; + if (mbox_move(sync_ctx, offset, + offset + sync_ctx->expunged_space, + trailer_size) < 0) + return -1; + if (ftruncate(sync_ctx->ibox->mbox_fd, + offset + trailer_size) < 0) { + mbox_set_syscall_error(sync_ctx->ibox, "ftruncate()"); + return -1; } + + istream_raw_mbox_flush(sync_ctx->input); } - if (ret < 0) { - st.st_mtime = 0; - st.st_size = 0; + return 0; +} + +static int mbox_sync_update_index_header(struct mbox_sync_context *sync_ctx) +{ + struct stat st; + + if (fstat(sync_ctx->ibox->mbox_fd, &st) < 0) { + mbox_set_syscall_error(sync_ctx->ibox, "fstat()"); + return -1; } - if (sync_ctx.base_uid_validity != hdr->uid_validity) { - mail_index_update_header(t, + if (sync_ctx->base_uid_validity != sync_ctx->hdr->uid_validity) { + mail_index_update_header(sync_ctx->t, offsetof(struct mail_index_header, uid_validity), - &sync_ctx.base_uid_validity, - sizeof(sync_ctx.base_uid_validity)); + &sync_ctx->base_uid_validity, + sizeof(sync_ctx->base_uid_validity)); } - if (sync_ctx.next_uid != hdr->next_uid) { - mail_index_update_header(t, + if (sync_ctx->next_uid != sync_ctx->hdr->next_uid) { + mail_index_update_header(sync_ctx->t, offsetof(struct mail_index_header, next_uid), - &sync_ctx.next_uid, sizeof(sync_ctx.next_uid)); + &sync_ctx->next_uid, sizeof(sync_ctx->next_uid)); } - if ((uint32_t)st.st_mtime != hdr->sync_stamp) { + if ((uint32_t)st.st_mtime != sync_ctx->hdr->sync_stamp) { uint32_t sync_stamp = st.st_mtime; - mail_index_update_header(t, + mail_index_update_header(sync_ctx->t, offsetof(struct mail_index_header, sync_stamp), &sync_stamp, sizeof(sync_stamp)); } - if ((uint64_t)st.st_size != hdr->sync_size) { + if ((uint64_t)st.st_size != sync_ctx->hdr->sync_size) { uint64_t sync_size = st.st_size; - mail_index_update_header(t, + mail_index_update_header(sync_ctx->t, offsetof(struct mail_index_header, sync_size), &sync_size, sizeof(sync_size)); } - if (ret < 0) - mail_index_transaction_rollback(t); - else { - if (mail_index_transaction_commit(t, &seq, &offset) < 0) - ret = -1; - else if (seq != 0) { - ibox->commit_log_file_seq = seq; - ibox->commit_log_file_offset = offset; - } - } + return 0; +} - if (ret != -2) { - if (mail_index_sync_end(index_sync_ctx) < 0) - ret = -1; +static void mbox_sync_restart(struct mbox_sync_context *sync_ctx) +{ + sync_ctx->base_uid_validity = 0; + sync_ctx->base_uid_last = 0; + + sync_ctx->next_uid = 1; + sync_ctx->prev_msg_uid = sync_ctx->first_uid = 0; + sync_ctx->seq = sync_ctx->idx_seq = 0; + + mail_index_transaction_rollback(sync_ctx->t); + sync_ctx->t = mail_index_transaction_begin(sync_ctx->sync_view, FALSE); +} + +static int mbox_sync_do(struct mbox_sync_context *sync_ctx, int index_synced) +{ + struct index_mailbox *ibox = sync_ctx->ibox; + struct mbox_sync_mail_context mail_ctx; + int ret, lock_type; + + lock_type = mail_index_sync_have_more(sync_ctx->index_sync_ctx) ? + F_WRLCK : F_RDLCK; + if ((ret = mbox_lock(ibox, lock_type, &sync_ctx->lock_id)) <= 0) + return ret; + + if (mbox_file_open_stream(ibox) < 0) + return -1; + + if ((ret = mbox_sync_parse_all(sync_ctx, &mail_ctx)) == -1) + return -1; + + if (ret == -2) { + /* we want to modify mbox, get exclusive lock. this requires + dropping the read lock first, so we have to parse the whole + mbox again */ + (void)mbox_unlock(ibox, sync_ctx->lock_id); + sync_ctx->lock_id = 0; + + if ((ret = mbox_lock(ibox, F_WRLCK, &sync_ctx->lock_id)) <= 0) + return ret; + if (mbox_file_open_stream(ibox) < 0) + return -1; + + /* FIXME: if mbox timestamp hasn't changed and it's older than + 2 secs, we could continue from where we left */ + mbox_sync_restart(sync_ctx); + + if (mbox_sync_parse_all(sync_ctx, &mail_ctx) < 0) + return -1; } - if (ret == 0) { - ibox->commit_log_file_seq = 0; - ibox->commit_log_file_offset = 0; - } else { - mail_storage_set_index_error(ibox); + if (mbox_sync_handle_eof_updates(sync_ctx, &mail_ctx) < 0) + return -1; + + if (sync_ctx->base_uid_last+1 != sync_ctx->next_uid) { + // FIXME: rewrite X-IMAPbase header } - str_free(sync_ctx.header); - str_free(sync_ctx.from_line); - buffer_free(mails); - return ret < 0 ? ret : 0; + /* only syncs left should be just appends (and their updates) + which weren't synced yet for some reason (crash). we'll just + ignore them, as we've overwritten them above. */ + while (mail_index_sync_next(sync_ctx->index_sync_ctx, + &sync_ctx->sync_rec) > 0) + ; + + if (mbox_sync_update_index_header(sync_ctx) < 0) + return -1; + + return 0; } static int mbox_sync_has_changed(struct index_mailbox *ibox) @@ -688,12 +767,10 @@ int mbox_sync(struct index_mailbox *ibox, int last_commit) { struct mail_index_sync_ctx *index_sync_ctx; struct mail_index_view *sync_view; - unsigned int lock_id; + struct mbox_sync_context sync_ctx; uint32_t seq; uoff_t offset; - struct mail_index_sync_rec sync_rec; - buffer_t *syncs; - int ret, lock_type, index_synced; + int ret, index_synced; if ((ret = mbox_sync_has_changed(ibox)) < 0) return -1; @@ -717,40 +794,46 @@ int mbox_sync(struct index_mailbox *ibox, int last_commit) return ret; } - memset(&sync_rec, 0, sizeof(sync_rec)); - syncs = buffer_create_dynamic(default_pool, 256, (size_t)-1); + memset(&sync_ctx, 0, sizeof(sync_ctx)); + sync_ctx.ibox = ibox; + sync_ctx.from_line = str_new(default_pool, 256); + sync_ctx.header = str_new(default_pool, 4096); + + sync_ctx.index_sync_ctx = index_sync_ctx; + sync_ctx.sync_view = sync_view; + sync_ctx.t = mail_index_transaction_begin(sync_view, FALSE); - lock_type = mail_index_sync_have_more(index_sync_ctx) ? - F_WRLCK : F_RDLCK; - if (mbox_lock(ibox, lock_type, &lock_id) > 0 && - mbox_file_open_stream(ibox) == 0) { - ret = mbox_sync_do(ibox, index_sync_ctx, sync_view, - syncs, &sync_rec, index_synced); - if (ret == -2) { - /* read lock -> write lock. do it again. */ - (void)mbox_unlock(ibox, lock_id); - lock_id = 0; - if (mbox_lock(ibox, F_WRLCK, &lock_id) <= 0) - ret = -1; - else if (mbox_file_open_stream(ibox) < 0) - ret = -1; - else { - ret = mbox_sync_do(ibox, index_sync_ctx, - sync_view, syncs, &sync_rec, - FALSE); - } - } - } else { - (void)mail_index_sync_end(index_sync_ctx); + sync_ctx.mails = buffer_create_dynamic(default_pool, 4096, (size_t)-1); + sync_ctx.syncs = buffer_create_dynamic(default_pool, 256, (size_t)-1); + sync_ctx.next_uid = 1; + + ret = mail_index_get_header(sync_view, &sync_ctx.hdr); + i_assert(ret == 0); + + if (mbox_sync_do(&sync_ctx, index_synced) < 0) ret = -1; + + if (ret < 0) + mail_index_transaction_rollback(sync_ctx.t); + else if (mail_index_transaction_commit(sync_ctx.t, &seq, &offset) < 0) + ret = -1; + else { + ibox->commit_log_file_seq = 0; + ibox->commit_log_file_offset = 0; } - if (lock_id != 0) { - if (mbox_unlock(ibox, lock_id) < 0) + if (mail_index_sync_end(index_sync_ctx) < 0) + ret = -1; + + if (sync_ctx.lock_id != 0) { + if (mbox_unlock(ibox, sync_ctx.lock_id) < 0) ret = -1; } - buffer_free(syncs); + str_free(sync_ctx.header); + str_free(sync_ctx.from_line); + buffer_free(sync_ctx.mails); + buffer_free(sync_ctx.syncs); return ret; }