diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-index/mail-index-transaction.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-transaction.c b/src/lib-index/mail-index-transaction.c new file mode 100644 index 0000000..0c61170 --- /dev/null +++ b/src/lib-index/mail-index-transaction.c @@ -0,0 +1,360 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "hook-build.h" +#include "bsearch-insert-pos.h" +#include "llist.h" +#include "mail-index-private.h" +#include "mail-transaction-log-private.h" +#include "mail-index-transaction-private.h" + +static ARRAY(hook_mail_index_transaction_created_t *) + hook_mail_index_transaction_created; + +void mail_index_transaction_hook_register(hook_mail_index_transaction_created_t *hook) +{ + if (!array_is_created(&hook_mail_index_transaction_created)) + i_array_init(&hook_mail_index_transaction_created, 8); + array_push_back(&hook_mail_index_transaction_created, &hook); +} + +void mail_index_transaction_hook_unregister(hook_mail_index_transaction_created_t *hook) +{ + unsigned int idx; + bool found = FALSE; + + i_assert(array_is_created(&hook_mail_index_transaction_created)); + for(idx = 0; idx < array_count(&hook_mail_index_transaction_created); idx++) { + hook_mail_index_transaction_created_t *arr_hook = + array_idx_elem(&hook_mail_index_transaction_created, idx); + if (arr_hook == hook) { + array_delete(&hook_mail_index_transaction_created, idx, 1); + found = TRUE; + break; + } + } + i_assert(found == TRUE); + if (array_count(&hook_mail_index_transaction_created) == 0) + array_free(&hook_mail_index_transaction_created); +} + + +struct mail_index_view * +mail_index_transaction_get_view(struct mail_index_transaction *t) +{ + return t->view; +} + +bool mail_index_transaction_is_expunged(struct mail_index_transaction *t, + uint32_t seq) +{ + struct mail_transaction_expunge_guid key; + + if (!array_is_created(&t->expunges)) + return FALSE; + + if (t->expunges_nonsorted) + mail_index_transaction_sort_expunges(t); + + key.uid = seq; + return array_bsearch(&t->expunges, &key, + mail_transaction_expunge_guid_cmp) != NULL; +} + +void mail_index_transaction_ref(struct mail_index_transaction *t) +{ + t->refcount++; +} + +void mail_index_transaction_unref(struct mail_index_transaction **_t) +{ + struct mail_index_transaction *t = *_t; + + *_t = NULL; + if (--t->refcount > 0) + return; + + mail_index_transaction_reset_v(t); + + DLLIST_REMOVE(&t->view->transactions_list, t); + array_free(&t->module_contexts); + if (t->latest_view != NULL) + mail_index_view_close(&t->latest_view); + mail_index_view_close(&t->view); + i_free(t); +} + +uint32_t mail_index_transaction_get_next_uid(struct mail_index_transaction *t) +{ + const struct mail_index_header *head_hdr, *hdr; + unsigned int offset; + uint32_t next_uid; + + head_hdr = &t->view->index->map->hdr; + hdr = &t->view->map->hdr; + next_uid = t->reset || head_hdr->uid_validity != hdr->uid_validity ? + 1 : hdr->next_uid; + if (array_is_created(&t->appends) && t->highest_append_uid != 0) { + /* get next_uid from appends if they have UIDs. it's possible + that some appends have too low UIDs, they'll be caught + later. */ + if (next_uid <= t->highest_append_uid) + next_uid = t->highest_append_uid + 1; + } + + /* see if it's been updated in pre/post header changes */ + offset = offsetof(struct mail_index_header, next_uid); + if (t->post_hdr_mask[offset] != 0) { + hdr = (const void *)t->post_hdr_change; + if (hdr->next_uid > next_uid) + next_uid = hdr->next_uid; + } + if (t->pre_hdr_mask[offset] != 0) { + hdr = (const void *)t->pre_hdr_change; + if (hdr->next_uid > next_uid) + next_uid = hdr->next_uid; + } + return next_uid; +} + +void mail_index_transaction_lookup_latest_keywords(struct mail_index_transaction *t, + uint32_t seq, + ARRAY_TYPE(keyword_indexes) *keywords) +{ + uint32_t uid, latest_seq; + + /* seq points to the transaction's primary view */ + mail_index_lookup_uid(t->view, seq, &uid); + + /* get the latest keywords from the updated index, or fallback to the + primary view if the message is already expunged */ + if (t->latest_view == NULL) { + mail_index_refresh(t->view->index); + t->latest_view = mail_index_view_open(t->view->index); + } + if (mail_index_lookup_seq(t->latest_view, uid, &latest_seq)) + mail_index_lookup_keywords(t->latest_view, latest_seq, keywords); + else + mail_index_lookup_keywords(t->view, seq, keywords); +} + +static int +mail_transaction_log_file_refresh(struct mail_index_transaction *t, + struct mail_transaction_log_append_ctx *ctx) +{ + struct mail_transaction_log_file *file; + + if (t->reset) { + /* Reset the whole index, preserving only indexid. Begin by + rotating the log. We don't care if we skip some non-synced + transactions. */ + if (mail_transaction_log_rotate(t->view->index->log, TRUE) < 0) + return -1; + + if (!MAIL_INDEX_TRANSACTION_HAS_CHANGES(t)) { + /* we only wanted to reset */ + return 0; + } + } + file = t->view->index->log->head; + + /* make sure we have everything mapped */ + if (mail_index_map(t->view->index, MAIL_INDEX_SYNC_HANDLER_HEAD) <= 0) + return -1; + + i_assert(file->sync_offset >= file->buffer_offset); + ctx->new_highest_modseq = file->sync_highest_modseq; + return 1; +} + +static int +mail_index_transaction_commit_real(struct mail_index_transaction *t, + uoff_t *commit_size_r, + enum mail_index_transaction_change *changes_r) +{ + struct mail_transaction_log *log = t->view->index->log; + struct mail_transaction_log_append_ctx *ctx; + enum mail_transaction_type trans_flags = 0; + uint32_t log_seq1, log_seq2; + uoff_t log_offset1, log_offset2; + int ret; + + *changes_r = 0; + + if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0) + trans_flags |= MAIL_TRANSACTION_EXTERNAL; + if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_SYNC) != 0) + trans_flags |= MAIL_TRANSACTION_SYNC; + + if (mail_transaction_log_append_begin(log->index, trans_flags, &ctx) < 0) + return -1; + ret = mail_transaction_log_file_refresh(t, ctx); + if (ret > 0) T_BEGIN { + mail_index_transaction_finish(t); + mail_index_transaction_export(t, ctx, changes_r); + } T_END; + + mail_transaction_log_get_head(log, &log_seq1, &log_offset1); + if (mail_transaction_log_append_commit(&ctx) < 0 || ret < 0) + return -1; + mail_transaction_log_get_head(log, &log_seq2, &log_offset2); + i_assert(log_seq1 == log_seq2); + + if (t->reset) { + /* get rid of the old index. it might just confuse readers, + especially if it's broken. */ + i_unlink_if_exists(log->index->filepath); + } + + *commit_size_r = log_offset2 - log_offset1; + + if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_HIDE) != 0 && + log_offset1 != log_offset2) { + /* mark the area covered by this transaction hidden */ + mail_index_view_add_hidden_transaction(t->view, log_seq1, + log_offset1, log_offset2 - log_offset1); + } + return 0; +} + +static int mail_index_transaction_commit_v(struct mail_index_transaction *t, + struct mail_index_transaction_commit_result *result_r) +{ + struct mail_index *index = t->view->index; + bool changed; + int ret; + + i_assert(t->first_new_seq > + mail_index_view_get_messages_count(t->view)); + + changed = MAIL_INDEX_TRANSACTION_HAS_CHANGES(t) || t->reset; + ret = !changed ? 0 : + mail_index_transaction_commit_real(t, &result_r->commit_size, + &result_r->changes_mask); + mail_transaction_log_get_head(index->log, &result_r->log_file_seq, + &result_r->log_file_offset); + + if (ret == 0 && !index->syncing && changed) { + /* if we're committing a normal transaction, we want to + have those changes in the index mapping immediately. this + is especially important when committing cache offset + updates. + + however if we're syncing the index now, the mapping must + be done later as MAIL_INDEX_SYNC_HANDLER_FILE so that + expunge handlers get run for the newly expunged messages + (and sync handlers that require HANDLER_FILE as well). */ + index->sync_commit_result = result_r; + mail_index_refresh(index); + index->sync_commit_result = NULL; + } + + mail_index_transaction_unref(&t); + return ret; +} + +static void mail_index_transaction_rollback_v(struct mail_index_transaction *t) +{ + mail_index_transaction_unref(&t); +} + +int mail_index_transaction_commit(struct mail_index_transaction **t) +{ + struct mail_index_transaction_commit_result result; + + return mail_index_transaction_commit_full(t, &result); +} + +int mail_index_transaction_commit_full(struct mail_index_transaction **_t, + struct mail_index_transaction_commit_result *result_r) +{ + struct mail_index_transaction *t = *_t; + struct mail_index *index = t->view->index; + bool index_undeleted = t->index_undeleted; + + if (mail_index_view_is_inconsistent(t->view)) { + mail_index_set_error_nolog(index, "View is inconsistent"); + mail_index_transaction_rollback(_t); + return -1; + } + if (!index_undeleted && !t->commit_deleted_index) { + if (t->view->index->index_deleted || + (t->view->index->index_delete_requested && + !t->view->index->syncing)) { + /* no further changes allowed */ + mail_index_set_error_nolog(index, "Index is marked deleted"); + mail_index_transaction_rollback(_t); + return -1; + } + } + + *_t = NULL; + i_zero(result_r); + if (t->v.commit(t, result_r) < 0) + return -1; + + if (index_undeleted) { + index->index_deleted = FALSE; + index->index_delete_requested = FALSE; + } + return 0; +} + +void mail_index_transaction_rollback(struct mail_index_transaction **_t) +{ + struct mail_index_transaction *t = *_t; + + *_t = NULL; + t->v.rollback(t); +} + +static struct mail_index_transaction_vfuncs trans_vfuncs = { + mail_index_transaction_reset_v, + mail_index_transaction_commit_v, + mail_index_transaction_rollback_v +}; + +struct mail_index_transaction * +mail_index_transaction_begin(struct mail_index_view *view, + enum mail_index_transaction_flags flags) +{ + struct mail_index_transaction *t; + + /* don't allow syncing view while there's ongoing transactions */ + mail_index_view_ref(view); + + t = i_new(struct mail_index_transaction, 1); + t->refcount = 1; + t->v = trans_vfuncs; + t->view = view; + t->flags = flags; + + if (view->syncing) { + /* transaction view cannot work if new records are being added + in two places. make sure it doesn't happen. */ + t->no_appends = TRUE; + t->first_new_seq = (uint32_t)-1; + } else { + t->first_new_seq = + mail_index_view_get_messages_count(t->view) + 1; + } + + i_array_init(&t->module_contexts, + I_MIN(5, mail_index_module_register.id)); + DLLIST_PREPEND(&view->transactions_list, t); + + if (array_is_created(&hook_mail_index_transaction_created)) { + struct hook_build_context *ctx = + hook_build_init((void *)&t->v, sizeof(t->v)); + hook_mail_index_transaction_created_t *callback; + array_foreach_elem(&hook_mail_index_transaction_created, callback) { + callback(t); + hook_build_update(ctx, t->vlast); + } + t->vlast = NULL; + hook_build_deinit(&ctx); + } + return t; +} |