summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mail-index-transaction-export.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-index/mail-index-transaction-export.c')
-rw-r--r--src/lib-index/mail-index-transaction-export.c533
1 files changed, 533 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-transaction-export.c b/src/lib-index/mail-index-transaction-export.c
new file mode 100644
index 0000000..2aced67
--- /dev/null
+++ b/src/lib-index/mail-index-transaction-export.c
@@ -0,0 +1,533 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-index-private.h"
+#include "mail-index-modseq.h"
+#include "mail-transaction-log-private.h"
+#include "mail-index-transaction-private.h"
+
+struct mail_index_export_context {
+ struct mail_index_transaction *trans;
+ struct mail_transaction_log_append_ctx *append_ctx;
+};
+
+static void
+log_append_buffer(struct mail_index_export_context *ctx,
+ const buffer_t *buf, enum mail_transaction_type type)
+{
+ mail_transaction_log_append_add(ctx->append_ctx, type,
+ buf->data, buf->used);
+}
+
+static void log_append_flag_updates(struct mail_index_export_context *ctx,
+ struct mail_index_transaction *t)
+{
+ ARRAY(struct mail_transaction_flag_update) log_updates;
+ const struct mail_index_flag_update *updates;
+ struct mail_transaction_flag_update *log_update;
+ unsigned int i, count;
+
+ updates = array_get(&t->updates, &count);
+ if (count == 0)
+ return;
+
+ i_array_init(&log_updates, count);
+
+ for (i = 0; i < count; i++) {
+ log_update = array_append_space(&log_updates);
+ log_update->uid1 = updates[i].uid1;
+ log_update->uid2 = updates[i].uid2;
+ log_update->add_flags = updates[i].add_flags & 0xff;
+ log_update->remove_flags = updates[i].remove_flags & 0xff;
+ if ((updates[i].add_flags & MAIL_INDEX_MAIL_FLAG_UPDATE_MODSEQ) != 0)
+ log_update->modseq_inc_flag = 1;
+ }
+ log_append_buffer(ctx, log_updates.arr.buffer,
+ MAIL_TRANSACTION_FLAG_UPDATE);
+ array_free(&log_updates);
+}
+
+static const buffer_t *
+log_get_hdr_update_buffer(struct mail_index_transaction *t, bool prepend)
+{
+ buffer_t *buf;
+ const unsigned char *data, *mask;
+ struct mail_transaction_header_update u;
+ uint16_t offset;
+ int state = 0;
+
+ i_zero(&u);
+
+ data = prepend ? t->pre_hdr_change : t->post_hdr_change;
+ mask = prepend ? t->pre_hdr_mask : t->post_hdr_mask;
+
+ buf = t_buffer_create(256);
+ for (offset = 0; offset <= sizeof(t->pre_hdr_change); offset++) {
+ if (offset < sizeof(t->pre_hdr_change) && mask[offset] != 0) {
+ if (state == 0) {
+ u.offset = offset;
+ state++;
+ }
+ } else {
+ if (state > 0) {
+ u.size = offset - u.offset;
+ buffer_append(buf, &u, sizeof(u));
+ buffer_append(buf, data + u.offset, u.size);
+ state = 0;
+ }
+ }
+ }
+ return buf;
+}
+
+static unsigned int
+ext_hdr_update_get_size(const struct mail_index_transaction_ext_hdr_update *hu)
+{
+ unsigned int i;
+
+ for (i = hu->alloc_size; i > 0; i--) {
+ if (hu->mask[i-1] != 0)
+ return i;
+ }
+ return 0;
+}
+
+static void log_append_ext_intro(struct mail_index_export_context *ctx,
+ uint32_t ext_id, uint32_t reset_id,
+ unsigned int *hdr_size_r)
+{
+ struct mail_index_transaction *t = ctx->trans;
+ const struct mail_index_registered_ext *rext;
+ const struct mail_index_ext *ext;
+ struct mail_transaction_ext_intro *intro, *resizes;
+ buffer_t *buf;
+ uint32_t idx;
+ unsigned int count;
+
+ i_assert(ext_id != (uint32_t)-1);
+
+ if (t->reset ||
+ !mail_index_map_get_ext_idx(t->view->index->map, ext_id, &idx)) {
+ /* new extension */
+ idx = (uint32_t)-1;
+ }
+
+ rext = array_idx(&t->view->index->extensions, ext_id);
+ if (!array_is_created(&t->ext_resizes)) {
+ resizes = NULL;
+ count = 0;
+ } else {
+ resizes = array_get_modifiable(&t->ext_resizes, &count);
+ }
+
+ buf = t_buffer_create(128);
+ if (ext_id < count && resizes[ext_id].name_size != 0) {
+ /* we're resizing the extension. use the resize struct. */
+ intro = &resizes[ext_id];
+
+ if (idx != (uint32_t)-1) {
+ intro->ext_id = idx;
+ intro->name_size = 0;
+ } else {
+ intro->ext_id = (uint32_t)-1;
+ intro->name_size = strlen(rext->name);
+ }
+ buffer_append(buf, intro, sizeof(*intro));
+ } else {
+ /* generate a new intro structure */
+ intro = buffer_append_space_unsafe(buf, sizeof(*intro));
+ intro->ext_id = idx;
+ intro->record_size = rext->record_size;
+ intro->record_align = rext->record_align;
+ if (idx == (uint32_t)-1) {
+ intro->hdr_size = rext->hdr_size;
+ intro->name_size = strlen(rext->name);
+ } else {
+ ext = array_idx(&t->view->index->map->extensions, idx);
+ intro->hdr_size = ext->hdr_size;
+ intro->name_size = 0;
+ }
+ intro->flags = MAIL_TRANSACTION_EXT_INTRO_FLAG_NO_SHRINK;
+
+ /* handle increasing header size automatically */
+ if (array_is_created(&t->ext_hdr_updates) &&
+ ext_id < array_count(&t->ext_hdr_updates)) {
+ const struct mail_index_transaction_ext_hdr_update *hu;
+ unsigned int hdr_update_size;
+
+ hu = array_idx(&t->ext_hdr_updates, ext_id);
+ hdr_update_size = ext_hdr_update_get_size(hu);
+ if (intro->hdr_size < hdr_update_size)
+ intro->hdr_size = hdr_update_size;
+ }
+ }
+ i_assert(intro->record_size != 0 || intro->hdr_size != 0);
+ if (reset_id != 0) {
+ /* we're going to reset this extension in this transaction */
+ intro->reset_id = reset_id;
+ } else if (idx != (uint32_t)-1) {
+ /* use the existing reset_id */
+ const struct mail_index_ext *map_ext =
+ array_idx(&t->view->index->map->extensions, idx);
+ intro->reset_id = map_ext->reset_id;
+ } else {
+ /* new extension, reset_id defaults to 0 */
+ }
+ buffer_append(buf, rext->name, intro->name_size);
+ if ((buf->used % 4) != 0)
+ buffer_append_zero(buf, 4 - (buf->used % 4));
+
+ if (ctx->append_ctx->new_highest_modseq == 0 &&
+ strcmp(rext->name, MAIL_INDEX_MODSEQ_EXT_NAME) == 0) {
+ /* modseq tracking started */
+ ctx->append_ctx->new_highest_modseq = 1;
+ }
+
+ log_append_buffer(ctx, buf, MAIL_TRANSACTION_EXT_INTRO);
+ *hdr_size_r = intro->hdr_size;
+}
+
+static void
+log_append_ext_hdr_update(struct mail_index_export_context *ctx,
+ const struct mail_index_transaction_ext_hdr_update *hdr,
+ unsigned int ext_hdr_size)
+{
+ buffer_t *buf;
+ const unsigned char *data, *mask;
+ struct mail_transaction_ext_hdr_update u;
+ struct mail_transaction_ext_hdr_update32 u32;
+ size_t offset;
+ bool started = FALSE, use_32 = hdr->alloc_size >= 65536;
+
+ i_zero(&u);
+ i_zero(&u32);
+
+ data = hdr->data;
+ mask = hdr->mask;
+
+ buf = buffer_create_dynamic(default_pool, 256);
+ for (offset = 0; offset <= hdr->alloc_size; offset++) {
+ if (offset < hdr->alloc_size && mask[offset] != 0) {
+ if (!started) {
+ u32.offset = offset;
+ started = TRUE;
+ }
+ } else {
+ if (started) {
+ u32.size = offset - u32.offset;
+ if (use_32)
+ buffer_append(buf, &u32, sizeof(u32));
+ else {
+ u.offset = u32.offset;
+ u.size = u32.size;
+ buffer_append(buf, &u, sizeof(u));
+ }
+ i_assert(u32.offset + u32.size <= ext_hdr_size);
+ buffer_append(buf, data + u32.offset, u32.size);
+ started = FALSE;
+ }
+ }
+ }
+ if (buf->used % 4 != 0)
+ buffer_append_zero(buf, 4 - buf->used % 4);
+ log_append_buffer(ctx, buf, use_32 ? MAIL_TRANSACTION_EXT_HDR_UPDATE32 :
+ MAIL_TRANSACTION_EXT_HDR_UPDATE);
+ buffer_free(&buf);
+}
+
+static void
+mail_transaction_log_append_ext_intros(struct mail_index_export_context *ctx)
+{
+ struct mail_index_transaction *t = ctx->trans;
+ const struct mail_transaction_ext_intro *resize;
+ const struct mail_index_transaction_ext_hdr_update *hdrs;
+ struct mail_transaction_ext_reset ext_reset;
+ unsigned int resize_count, ext_count = 0;
+ unsigned int hdrs_count, reset_id_count, reset_count, hdr_size;
+ uint32_t ext_id, reset_id;
+ const struct mail_transaction_ext_reset *reset;
+ const uint32_t *reset_ids;
+ buffer_t reset_buf;
+
+ if (!array_is_created(&t->ext_resizes)) {
+ resize = NULL;
+ resize_count = 0;
+ } else {
+ resize = array_get(&t->ext_resizes, &resize_count);
+ if (ext_count < resize_count)
+ ext_count = resize_count;
+ }
+
+ if (!array_is_created(&t->ext_reset_ids)) {
+ reset_ids = NULL;
+ reset_id_count = 0;
+ } else {
+ reset_ids = array_get(&t->ext_reset_ids, &reset_id_count);
+ }
+
+ if (!array_is_created(&t->ext_resets)) {
+ reset = NULL;
+ reset_count = 0;
+ } else {
+ reset = array_get(&t->ext_resets, &reset_count);
+ if (ext_count < reset_count)
+ ext_count = reset_count;
+ }
+
+ if (!array_is_created(&t->ext_hdr_updates)) {
+ hdrs = NULL;
+ hdrs_count = 0;
+ } else {
+ hdrs = array_get(&t->ext_hdr_updates, &hdrs_count);
+ if (ext_count < hdrs_count)
+ ext_count = hdrs_count;
+ }
+
+ i_zero(&ext_reset);
+ buffer_create_from_data(&reset_buf, &ext_reset, sizeof(ext_reset));
+ buffer_set_used_size(&reset_buf, sizeof(ext_reset));
+
+ for (ext_id = 0; ext_id < ext_count; ext_id++) {
+ if (ext_id < reset_count)
+ ext_reset = reset[ext_id];
+ else
+ ext_reset.new_reset_id = 0;
+ if ((ext_id < resize_count && resize[ext_id].name_size > 0) ||
+ ext_reset.new_reset_id != 0 ||
+ (ext_id < hdrs_count && hdrs[ext_id].alloc_size > 0)) {
+ if (ext_reset.new_reset_id != 0) {
+ /* we're going to reset this extension
+ immediately after the intro */
+ reset_id = 0;
+ } else {
+ reset_id = ext_id < reset_id_count ?
+ reset_ids[ext_id] : 0;
+ }
+ log_append_ext_intro(ctx, ext_id, reset_id, &hdr_size);
+ } else {
+ hdr_size = 0;
+ }
+ if (ext_reset.new_reset_id != 0) {
+ i_assert(ext_id < reset_id_count &&
+ ext_reset.new_reset_id == reset_ids[ext_id]);
+ log_append_buffer(ctx, &reset_buf,
+ MAIL_TRANSACTION_EXT_RESET);
+ }
+ if (ext_id < hdrs_count && hdrs[ext_id].alloc_size > 0) {
+ T_BEGIN {
+ log_append_ext_hdr_update(ctx, &hdrs[ext_id],
+ hdr_size);
+ } T_END;
+ }
+ }
+}
+
+static void log_append_ext_recs(struct mail_index_export_context *ctx,
+ const ARRAY_TYPE(seq_array_array) *arr,
+ enum mail_transaction_type type)
+{
+ struct mail_index_transaction *t = ctx->trans;
+ const ARRAY_TYPE(seq_array) *updates;
+ const uint32_t *reset_ids;
+ unsigned int ext_id, count, reset_id_count, hdr_size;
+ uint32_t reset_id;
+
+ if (!array_is_created(&t->ext_reset_ids)) {
+ reset_ids = NULL;
+ reset_id_count = 0;
+ } else {
+ reset_ids = array_get_modifiable(&t->ext_reset_ids,
+ &reset_id_count);
+ }
+
+ updates = array_get(arr, &count);
+ for (ext_id = 0; ext_id < count; ext_id++) {
+ if (!array_is_created(&updates[ext_id]))
+ continue;
+
+ reset_id = ext_id < reset_id_count ? reset_ids[ext_id] : 0;
+ log_append_ext_intro(ctx, ext_id, reset_id, &hdr_size);
+
+ log_append_buffer(ctx, updates[ext_id].arr.buffer, type);
+ }
+}
+
+static void
+log_append_keyword_update(struct mail_index_export_context *ctx,
+ buffer_t *tmp_buf, enum modify_type modify_type,
+ const char *keyword, const buffer_t *uid_buffer)
+{
+ struct mail_transaction_keyword_update kt_hdr;
+
+ i_assert(uid_buffer->used > 0);
+
+ i_zero(&kt_hdr);
+ kt_hdr.modify_type = modify_type;
+ kt_hdr.name_size = strlen(keyword);
+
+ buffer_set_used_size(tmp_buf, 0);
+ buffer_append(tmp_buf, &kt_hdr, sizeof(kt_hdr));
+ buffer_append(tmp_buf, keyword, kt_hdr.name_size);
+ if ((tmp_buf->used % 4) != 0)
+ buffer_append_zero(tmp_buf, 4 - (tmp_buf->used % 4));
+ buffer_append(tmp_buf, uid_buffer->data, uid_buffer->used);
+
+ log_append_buffer(ctx, tmp_buf, MAIL_TRANSACTION_KEYWORD_UPDATE);
+}
+
+static bool
+log_append_keyword_updates(struct mail_index_export_context *ctx)
+{
+ const struct mail_index_transaction_keyword_update *updates;
+ const char *const *keywords;
+ buffer_t *tmp_buf;
+ unsigned int i, count, keywords_count;
+ bool changed = FALSE;
+
+ tmp_buf = t_buffer_create(64);
+
+ keywords = array_get_modifiable(&ctx->trans->view->index->keywords,
+ &keywords_count);
+ updates = array_get_modifiable(&ctx->trans->keyword_updates, &count);
+ i_assert(count <= keywords_count);
+
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&updates[i].add_seq) &&
+ array_count(&updates[i].add_seq) > 0) {
+ changed = TRUE;
+ log_append_keyword_update(ctx, tmp_buf,
+ MODIFY_ADD, keywords[i],
+ updates[i].add_seq.arr.buffer);
+ }
+ if (array_is_created(&updates[i].remove_seq) &&
+ array_count(&updates[i].remove_seq) > 0) {
+ changed = TRUE;
+ log_append_keyword_update(ctx, tmp_buf,
+ MODIFY_REMOVE, keywords[i],
+ updates[i].remove_seq.arr.buffer);
+ }
+ }
+ return changed;
+}
+
+void mail_index_transaction_export(struct mail_index_transaction *t,
+ struct mail_transaction_log_append_ctx *append_ctx,
+ enum mail_index_transaction_change *changes_r)
+{
+ static uint8_t null4[4] = { 0, 0, 0, 0 };
+ enum mail_index_fsync_mask change_mask = 0;
+ struct mail_index_export_context ctx;
+
+ *changes_r = 0;
+
+ i_zero(&ctx);
+ ctx.trans = t;
+ ctx.append_ctx = append_ctx;
+
+ if (t->index_undeleted) {
+ i_assert(!t->index_deleted);
+ mail_transaction_log_append_add(ctx.append_ctx,
+ MAIL_TRANSACTION_INDEX_UNDELETED, &null4, 4);
+ }
+
+ /* send all extension introductions and resizes before appends
+ to avoid resize overhead as much as possible */
+ mail_transaction_log_append_ext_intros(&ctx);
+
+ if (t->pre_hdr_changed) {
+ log_append_buffer(&ctx, log_get_hdr_update_buffer(t, TRUE),
+ MAIL_TRANSACTION_HEADER_UPDATE);
+ }
+
+ if (append_ctx->output->used > 0)
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
+
+ if (t->attribute_updates != NULL) {
+ buffer_append_c(t->attribute_updates, '\0');
+ /* need to have 32bit alignment */
+ if (t->attribute_updates->used % 4 != 0) {
+ buffer_append_zero(t->attribute_updates,
+ 4 - t->attribute_updates->used % 4);
+ }
+ /* append the timestamp and value lengths */
+ buffer_append(t->attribute_updates,
+ t->attribute_updates_suffix->data,
+ t->attribute_updates_suffix->used);
+ i_assert(t->attribute_updates->used % 4 == 0);
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_ATTRIBUTE;
+ log_append_buffer(&ctx, t->attribute_updates,
+ MAIL_TRANSACTION_ATTRIBUTE_UPDATE);
+ }
+ if (array_is_created(&t->appends)) {
+ change_mask |= MAIL_INDEX_FSYNC_MASK_APPENDS;
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_APPEND;
+ log_append_buffer(&ctx, t->appends.arr.buffer,
+ MAIL_TRANSACTION_APPEND);
+ }
+
+ if (array_is_created(&t->updates)) {
+ change_mask |= MAIL_INDEX_FSYNC_MASK_FLAGS;
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_FLAGS;
+ log_append_flag_updates(&ctx, t);
+ }
+
+ if (array_is_created(&t->ext_rec_updates)) {
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
+ log_append_ext_recs(&ctx, &t->ext_rec_updates,
+ MAIL_TRANSACTION_EXT_REC_UPDATE);
+ }
+ if (array_is_created(&t->ext_rec_atomics)) {
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
+ log_append_ext_recs(&ctx, &t->ext_rec_atomics,
+ MAIL_TRANSACTION_EXT_ATOMIC_INC);
+ }
+
+ if (array_is_created(&t->keyword_updates)) {
+ if (log_append_keyword_updates(&ctx)) {
+ change_mask |= MAIL_INDEX_FSYNC_MASK_KEYWORDS;
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_KEYWORDS;
+ }
+ }
+ /* keep modseq updates almost last */
+ if (array_is_created(&t->modseq_updates)) {
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_MODSEQ;
+ log_append_buffer(&ctx, t->modseq_updates.arr.buffer,
+ MAIL_TRANSACTION_MODSEQ_UPDATE);
+ }
+
+ if (array_is_created(&t->expunges)) {
+ /* non-external expunges are only requests, ignore them when
+ checking fsync_mask */
+ if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0) {
+ change_mask |= MAIL_INDEX_FSYNC_MASK_EXPUNGES;
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_EXPUNGE;
+ } else {
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
+ }
+ log_append_buffer(&ctx, t->expunges.arr.buffer,
+ MAIL_TRANSACTION_EXPUNGE_GUID);
+ }
+
+ if (t->post_hdr_changed) {
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
+ log_append_buffer(&ctx, log_get_hdr_update_buffer(t, FALSE),
+ MAIL_TRANSACTION_HEADER_UPDATE);
+ }
+
+ if (t->index_deleted) {
+ i_assert(!t->index_undeleted);
+ *changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
+ mail_transaction_log_append_add(ctx.append_ctx,
+ MAIL_TRANSACTION_INDEX_DELETED,
+ &null4, 4);
+ }
+
+ i_assert((append_ctx->output->used > 0) == (*changes_r != 0));
+
+ append_ctx->index_sync_transaction = t->sync_transaction;
+ append_ctx->tail_offset_changed = t->tail_offset_changed;
+ append_ctx->want_fsync =
+ (t->view->index->set.fsync_mask & change_mask) != 0 ||
+ (t->flags & MAIL_INDEX_TRANSACTION_FLAG_FSYNC) != 0;
+}