mail-index-transaction-export.c \
mail-index-transaction-finish.c \
mail-index-transaction-sort-appends.c \
+ mail-index-transaction-update.c \
mail-index-transaction-view.c \
mail-index-strmap.c \
mail-index-sync.c \
mailbox-list-index-private.h
test_programs = \
+ test-mail-index-transaction-update \
test-mail-transaction-log-append \
test-mail-transaction-log-view
../lib-test/libtest.la \
../lib/liblib.la
+test_mail_index_transaction_update_SOURCES = test-mail-index-transaction-update.c
+test_mail_index_transaction_update_LDADD = mail-index-transaction-update.lo $(test_libs)
+test_mail_index_transaction_update_DEPENDENCIES = mail-index-transaction-update.lo $(test_libs)
+
test_mail_transaction_log_append_SOURCES = test-mail-transaction-log-append.c
test_mail_transaction_log_append_LDADD = mail-transaction-log-append.lo $(test_libs)
test_mail_transaction_log_append_DEPENDENCIES = mail-transaction-log-append.lo $(test_libs)
int mail_index_transaction_finish(struct mail_index_transaction *t)
{
- mail_index_transaction_sort_appends(t);
+ if (array_is_created(&t->appends)) {
+ mail_index_update_day_headers(t);
+ mail_index_transaction_sort_appends(t);
+ }
mail_index_transaction_finish_flag_updates(t);
if (array_is_created(&t->ext_reset_atomic) || t->max_modseq != 0) {
void mail_index_transaction_ref(struct mail_index_transaction *t);
void mail_index_transaction_unref(struct mail_index_transaction **t);
+void mail_index_transaction_reset_v(struct mail_index_transaction *t);
void mail_index_transaction_sort_appends(struct mail_index_transaction *t);
uint32_t mail_index_transaction_get_next_uid(struct mail_index_transaction *t);
void mail_index_transaction_set_log_updates(struct mail_index_transaction *t);
+void mail_index_update_day_headers(struct mail_index_transaction *t);
unsigned int
mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t,
int mail_index_transaction_finish(struct mail_index_transaction *t);
void mail_index_transaction_export(struct mail_index_transaction *t,
struct mail_transaction_log_append_ctx *append_ctx);
+unsigned int
+mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t,
+ unsigned int left_idx,
+ unsigned int right_idx,
+ uint32_t seq);
#endif
--- /dev/null
+/* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */
+
+/* Inside transaction we keep messages stored in sequences in uid fields.
+ Before they're written to transaction log the sequences are changed to
+ UIDs. */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "mail-index-private.h"
+#include "mail-index-transaction-private.h"
+
+static bool
+mail_index_transaction_has_ext_changes(struct mail_index_transaction *t);
+
+struct mail_index_record *
+mail_index_transaction_lookup(struct mail_index_transaction *t, uint32_t seq)
+{
+ i_assert(seq >= t->first_new_seq && seq <= t->last_new_seq);
+
+ return array_idx_modifiable(&t->appends, seq - t->first_new_seq);
+}
+
+void mail_index_transaction_reset_v(struct mail_index_transaction *t)
+{
+ ARRAY_TYPE(seq_array) *recs;
+ struct mail_index_transaction_ext_hdr_update *ext_hdrs;
+ unsigned i, count;
+
+ if (array_is_created(&t->ext_rec_updates)) {
+ recs = array_get_modifiable(&t->ext_rec_updates, &count);
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&recs[i]))
+ array_free(&recs[i]);
+ }
+ array_free(&t->ext_rec_updates);
+ }
+ if (array_is_created(&t->ext_rec_atomics)) {
+ recs = array_get_modifiable(&t->ext_rec_atomics, &count);
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&recs[i]))
+ array_free(&recs[i]);
+ }
+ array_free(&t->ext_rec_atomics);
+ }
+ if (array_is_created(&t->ext_hdr_updates)) {
+ ext_hdrs = array_get_modifiable(&t->ext_hdr_updates, &count);
+ for (i = 0; i < count; i++) {
+ i_free(ext_hdrs[i].data);
+ i_free(ext_hdrs[i].mask);
+ }
+ array_free(&t->ext_hdr_updates);
+ }
+
+ if (array_is_created(&t->keyword_updates)) {
+ struct mail_index_transaction_keyword_update *u;
+
+ u = array_get_modifiable(&t->keyword_updates, &count);
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&u[i].add_seq))
+ array_free(&u[i].add_seq);
+ if (array_is_created(&u[i].remove_seq))
+ array_free(&u[i].remove_seq);
+ }
+ array_free(&t->keyword_updates);
+ }
+ if (array_is_created(&t->keyword_resets))
+ array_free(&t->keyword_resets);
+
+ if (array_is_created(&t->appends))
+ array_free(&t->appends);
+ if (array_is_created(&t->expunges))
+ array_free(&t->expunges);
+ if (array_is_created(&t->updates))
+ array_free(&t->updates);
+ if (array_is_created(&t->ext_resizes))
+ array_free(&t->ext_resizes);
+ if (array_is_created(&t->ext_resets))
+ array_free(&t->ext_resets);
+ if (array_is_created(&t->ext_reset_ids))
+ array_free(&t->ext_reset_ids);
+ if (array_is_created(&t->ext_reset_atomic))
+ array_free(&t->ext_reset_atomic);
+
+ t->first_new_seq = mail_index_view_get_messages_count(t->view)+1;
+ t->last_new_seq = 0;
+ t->last_update_idx = 0;
+ t->min_flagupdate_seq = 0;
+ t->max_flagupdate_seq = 0;
+
+ memset(t->pre_hdr_mask, 0, sizeof(t->pre_hdr_mask));
+ memset(t->post_hdr_mask, 0, sizeof(t->post_hdr_mask));
+
+ t->appends_nonsorted = FALSE;
+ t->drop_unnecessary_flag_updates = FALSE;
+ t->pre_hdr_changed = FALSE;
+ t->post_hdr_changed = FALSE;
+ t->reset = FALSE;
+ t->log_updates = FALSE;
+ t->log_ext_updates = FALSE;
+}
+
+void mail_index_transaction_set_log_updates(struct mail_index_transaction *t)
+{
+ /* flag updates aren't included in log_updates */
+ t->log_updates = array_is_created(&t->appends) ||
+ array_is_created(&t->expunges) ||
+ array_is_created(&t->keyword_resets) ||
+ array_is_created(&t->keyword_updates) ||
+ t->pre_hdr_changed || t->post_hdr_changed;
+}
+
+void mail_index_update_day_headers(struct mail_index_transaction *t)
+{
+ struct mail_index_header hdr;
+ const struct mail_index_record *rec;
+ const int max_days = N_ELEMENTS(hdr.day_first_uid);
+ struct tm tm;
+ time_t stamp;
+ int i, days;
+
+ hdr = *mail_index_get_header(t->view);
+ rec = array_idx(&t->appends, 0);
+
+ /* get beginning of today */
+ tm = *localtime(&ioloop_time);
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+ stamp = mktime(&tm);
+ i_assert(stamp != (time_t)-1);
+
+ if ((time_t)hdr.day_stamp >= stamp)
+ return;
+
+ /* get number of days since last message */
+ days = (stamp - hdr.day_stamp) / (3600*24);
+ if (days > max_days)
+ days = max_days;
+
+ /* @UNSAFE: move days forward and fill the missing days with old
+ day_first_uid[0]. */
+ memmove(hdr.day_first_uid + days, hdr.day_first_uid, max_days - days);
+ for (i = 1; i < days; i++)
+ hdr.day_first_uid[i] = hdr.day_first_uid[0];
+
+ hdr.day_stamp = stamp;
+ hdr.day_first_uid[0] = rec->uid;
+
+ mail_index_update_header(t,
+ offsetof(struct mail_index_header, day_stamp),
+ &hdr.day_stamp, sizeof(hdr.day_stamp), FALSE);
+ mail_index_update_header(t,
+ offsetof(struct mail_index_header, day_first_uid),
+ hdr.day_first_uid, sizeof(hdr.day_first_uid), FALSE);
+}
+
+void mail_index_append(struct mail_index_transaction *t, uint32_t uid,
+ uint32_t *seq_r)
+{
+ struct mail_index_record *rec;
+
+ i_assert(!t->no_appends);
+
+ t->log_updates = TRUE;
+
+ if (!array_is_created(&t->appends))
+ i_array_init(&t->appends, 32);
+
+ /* sequence number is visible only inside given view,
+ so let it generate it */
+ if (t->last_new_seq != 0)
+ *seq_r = ++t->last_new_seq;
+ else
+ *seq_r = t->last_new_seq = t->first_new_seq;
+
+ rec = array_append_space(&t->appends);
+ if (uid != 0) {
+ rec->uid = uid;
+ if (!t->appends_nonsorted &&
+ t->last_new_seq != t->first_new_seq) {
+ /* if previous record's UID is larger than this one,
+ we'll have to sort the appends later */
+ rec = mail_index_transaction_lookup(t, *seq_r - 1);
+ if (rec->uid > uid)
+ t->appends_nonsorted = TRUE;
+ else if (rec->uid == uid)
+ i_panic("Duplicate UIDs added in transaction");
+ }
+ if (t->highest_append_uid < uid)
+ t->highest_append_uid = uid;
+ }
+}
+
+void mail_index_append_assign_uids(struct mail_index_transaction *t,
+ uint32_t first_uid, uint32_t *next_uid_r)
+{
+ struct mail_index_record *recs;
+ unsigned int i, count;
+
+ if (!array_is_created(&t->appends))
+ return;
+
+ i_assert(first_uid > t->highest_append_uid);
+
+ recs = array_get_modifiable(&t->appends, &count);
+ for (i = 0; i < count; i++) {
+ if (recs[i].uid == 0)
+ recs[i].uid = first_uid++;
+ }
+
+ *next_uid_r = first_uid;
+}
+
+static void
+mail_index_expunge_last_append_ext(ARRAY_TYPE(seq_array_array) *ext_updates,
+ uint32_t seq)
+{
+ ARRAY_TYPE(seq_array) *seqs;
+ unsigned int i, count, idx;
+
+ if (!array_is_created(ext_updates))
+ return;
+
+ seqs = array_get_modifiable(ext_updates, &count);
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&seqs[i]) &&
+ mail_index_seq_array_lookup(&seqs[i], seq, &idx))
+ array_delete(&seqs[i], idx, 1);
+ }
+}
+
+static void
+mail_index_expunge_last_append(struct mail_index_transaction *t, uint32_t seq)
+{
+ struct mail_index_transaction_keyword_update *kw_updates;
+ unsigned int i, count;
+
+ i_assert(seq == t->last_new_seq);
+
+ /* remove extension updates */
+ mail_index_expunge_last_append_ext(&t->ext_rec_updates, seq);
+ mail_index_expunge_last_append_ext(&t->ext_rec_atomics, seq);
+ t->log_ext_updates = mail_index_transaction_has_ext_changes(t);
+
+ /* remove keywords */
+ if (array_is_created(&t->keyword_resets))
+ seq_range_array_remove(&t->keyword_resets, seq);
+ if (array_is_created(&t->keyword_updates)) {
+ kw_updates = array_get_modifiable(&t->keyword_updates, &count);
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&kw_updates[i].add_seq)) {
+ seq_range_array_remove(&kw_updates[i].add_seq,
+ seq);
+ }
+ if (array_is_created(&kw_updates[i].remove_seq)) {
+ seq_range_array_remove(
+ &kw_updates[i].remove_seq, seq);
+ }
+ }
+ }
+
+ /* and finally remove the append itself */
+ array_delete(&t->appends, seq - t->first_new_seq, 1);
+ t->last_new_seq--;
+ if (t->first_new_seq > t->last_new_seq) {
+ t->last_new_seq = 0;
+ t->appends_nonsorted = FALSE;
+ array_free(&t->appends);
+ }
+ mail_index_transaction_set_log_updates(t);
+}
+
+void mail_index_expunge(struct mail_index_transaction *t, uint32_t seq)
+{
+ i_assert(seq > 0);
+ if (seq >= t->first_new_seq) {
+ /* we can handle only the last append. otherwise we'd have to
+ renumber sequences and that gets tricky. for now this is
+ enough, since we typically want to expunge all the
+ appends. */
+ mail_index_expunge_last_append(t, seq);
+ } else {
+ t->log_updates = TRUE;
+
+ /* expunges is a sorted array of {seq1, seq2, ..}, .. */
+ seq_range_array_add(&t->expunges, 128, seq);
+ }
+}
+
+static void update_minmax_flagupdate_seq(struct mail_index_transaction *t,
+ uint32_t seq1, uint32_t seq2)
+{
+ if (t->min_flagupdate_seq == 0) {
+ t->min_flagupdate_seq = seq1;
+ t->max_flagupdate_seq = seq2;
+ } else {
+ if (t->min_flagupdate_seq > seq1)
+ t->min_flagupdate_seq = seq1;
+ if (t->max_flagupdate_seq < seq2)
+ t->max_flagupdate_seq = seq2;
+ }
+}
+
+unsigned int
+mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t,
+ unsigned int left_idx,
+ unsigned int right_idx,
+ uint32_t seq)
+{
+ const struct mail_transaction_flag_update *updates;
+ unsigned int idx, count;
+
+ updates = array_get(&t->updates, &count);
+ i_assert(left_idx <= right_idx && right_idx <= count);
+
+ /* find the first update with either overlapping range,
+ or the update which will come after our insert */
+ idx = left_idx;
+ while (left_idx < right_idx) {
+ idx = (left_idx + right_idx) / 2;
+
+ if (updates[idx].uid2 < seq)
+ left_idx = idx+1;
+ else if (updates[idx].uid1 > seq)
+ right_idx = idx;
+ else
+ break;
+ }
+ if (left_idx > idx)
+ idx++;
+ return idx;
+}
+
+static void
+mail_index_insert_flag_update(struct mail_index_transaction *t,
+ struct mail_transaction_flag_update u,
+ unsigned int idx)
+{
+ struct mail_transaction_flag_update *updates, tmp_update;
+ unsigned int count, first_idx, max;
+
+ updates = array_get_modifiable(&t->updates, &count);
+
+ /* overlapping ranges, split/merge them */
+ i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1);
+ i_assert(idx == count || updates[idx].uid2 >= u.uid1);
+
+ /* first we'll just add the changes without trying to merge anything */
+ first_idx = idx;
+ for (; idx < count && u.uid2 >= updates[idx].uid1; idx++) {
+ i_assert(u.uid1 <= updates[idx].uid2);
+ if (u.uid1 != updates[idx].uid1 &&
+ (updates[idx].add_flags != u.add_flags ||
+ updates[idx].remove_flags != u.remove_flags)) {
+ if (u.uid1 < updates[idx].uid1) {
+ /* insert new update */
+ tmp_update = u;
+ tmp_update.uid2 = updates[idx].uid1 - 1;
+ } else {
+ /* split existing update from beginning */
+ tmp_update = updates[idx];
+ tmp_update.uid2 = u.uid1 - 1;
+ updates[idx].uid1 = u.uid1;
+ }
+
+ i_assert(tmp_update.uid1 <= tmp_update.uid2);
+ i_assert(updates[idx].uid1 <= updates[idx].uid2);
+
+ array_insert(&t->updates, idx, &tmp_update, 1);
+ updates = array_get_modifiable(&t->updates, &count);
+ idx++;
+ } else if (u.uid1 < updates[idx].uid1) {
+ updates[idx].uid1 = u.uid1;
+ }
+
+ if (u.uid2 < updates[idx].uid2 &&
+ (updates[idx].add_flags != u.add_flags ||
+ updates[idx].remove_flags != u.remove_flags)) {
+ /* split existing update from end */
+ tmp_update = updates[idx];
+ tmp_update.uid2 = u.uid2;
+ updates[idx].uid1 = u.uid2 + 1;
+
+ i_assert(tmp_update.uid1 <= tmp_update.uid2);
+ i_assert(updates[idx].uid1 <= updates[idx].uid2);
+
+ array_insert(&t->updates, idx, &tmp_update, 1);
+ updates = array_get_modifiable(&t->updates, &count);
+ }
+
+ updates[idx].add_flags =
+ (updates[idx].add_flags | u.add_flags) &
+ ~u.remove_flags;
+ updates[idx].remove_flags =
+ (updates[idx].remove_flags | u.remove_flags) &
+ ~u.add_flags;
+ u.uid1 = updates[idx].uid2 + 1;
+
+ if (updates[idx].add_flags == 0 &&
+ updates[idx].remove_flags == 0) {
+ /* we can remove this update completely */
+ array_delete(&t->updates, idx, 1);
+ updates = array_get_modifiable(&t->updates, &count);
+ }
+
+ if (u.uid1 > u.uid2) {
+ /* break here before idx++ so last_update_idx is set
+ correctly */
+ break;
+ }
+ }
+ i_assert(idx <= count);
+
+ if (u.uid1 <= u.uid2) {
+ i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1);
+ i_assert(idx == count || updates[idx].uid1 > u.uid2);
+ array_insert(&t->updates, idx, &u, 1);
+ }
+ updates = array_get_modifiable(&t->updates, &count);
+ t->last_update_idx = idx == count ? count-1 : idx;
+
+ /* merge everything */
+ idx = first_idx == 0 ? 0 : first_idx - 1;
+ max = I_MIN(t->last_update_idx + 1, count);
+ for (; idx < max; ) {
+ if (updates[idx].uid2 + 1 == updates[idx+1].uid1 &&
+ updates[idx].add_flags == updates[idx+1].add_flags &&
+ updates[idx].remove_flags == updates[idx+1].remove_flags) {
+ /* merge */
+ updates[idx].uid2 = updates[idx+1].uid2;
+ array_delete(&t->updates, idx + 1, 1);
+ max--;
+ if (t->last_update_idx > idx)
+ t->last_update_idx--;
+ updates = array_get_modifiable(&t->updates, &count);
+ } else {
+ idx++;
+ }
+ }
+}
+
+static void mail_index_record_modify_flags(struct mail_index_record *rec,
+ enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ switch (modify_type) {
+ case MODIFY_REPLACE:
+ rec->flags = flags;
+ break;
+ case MODIFY_ADD:
+ rec->flags |= flags;
+ break;
+ case MODIFY_REMOVE:
+ rec->flags &= ~flags;
+ break;
+ }
+}
+
+void mail_index_update_flags_range(struct mail_index_transaction *t,
+ uint32_t seq1, uint32_t seq2,
+ enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct mail_index_record *rec;
+ struct mail_transaction_flag_update u, *last_update;
+ unsigned int idx, first_idx, count;
+
+ update_minmax_flagupdate_seq(t, seq1, seq2);
+ if (seq2 >= t->first_new_seq) {
+ /* updates for appended messages, modify them directly */
+ uint32_t seq;
+
+ for (seq = I_MAX(t->first_new_seq, seq1); seq <= seq2; seq++) {
+ rec = mail_index_transaction_lookup(t, seq);
+ mail_index_record_modify_flags(rec, modify_type, flags);
+ }
+ if (seq1 >= t->first_new_seq)
+ return;
+
+ /* range contains also existing messages. update them next. */
+ seq2 = t->first_new_seq - 1;
+ }
+
+ i_assert(seq1 <= seq2 && seq1 > 0);
+ i_assert(seq2 <= mail_index_view_get_messages_count(t->view));
+
+ if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0)
+ t->drop_unnecessary_flag_updates = TRUE;
+
+ memset(&u, 0, sizeof(u));
+ u.uid1 = seq1;
+ u.uid2 = seq2;
+
+ switch (modify_type) {
+ case MODIFY_REPLACE:
+ u.add_flags = flags;
+ u.remove_flags = ~flags & MAIL_INDEX_FLAGS_MASK;
+ break;
+ case MODIFY_ADD:
+ if (flags == 0)
+ return;
+ u.add_flags = flags;
+ break;
+ case MODIFY_REMOVE:
+ if (flags == 0)
+ return;
+ u.remove_flags = flags;
+ break;
+ }
+
+ if (!array_is_created(&t->updates)) {
+ i_array_init(&t->updates, 256);
+ array_append(&t->updates, &u, 1);
+ return;
+ }
+
+ last_update = array_get_modifiable(&t->updates, &count);
+ if (t->last_update_idx < count) {
+ /* fast path - hopefully we're updating the next message,
+ or a message that is to be appended as last update */
+ last_update += t->last_update_idx;
+ if (seq1 - 1 == last_update->uid2) {
+ if (u.add_flags == last_update->add_flags &&
+ u.remove_flags == last_update->remove_flags &&
+ (t->last_update_idx + 1 == count ||
+ last_update[1].uid1 > seq2)) {
+ /* we can just update the UID range */
+ last_update->uid2 = seq2;
+ return;
+ }
+ } else if (seq1 > last_update->uid2) {
+ /* hopefully we can just append it */
+ t->last_update_idx++;
+ last_update++;
+ }
+ }
+
+ if (t->last_update_idx == count)
+ array_append(&t->updates, &u, 1);
+ else {
+ i_assert(t->last_update_idx < count);
+
+ /* slow path */
+ if (seq1 > last_update->uid2) {
+ /* added after this */
+ first_idx = t->last_update_idx + 1;
+ } else {
+ /* added before this or on top of this */
+ first_idx = 0;
+ count = t->last_update_idx + 1;
+ }
+ idx = mail_index_transaction_get_flag_update_pos(t, first_idx,
+ count, u.uid1);
+ mail_index_insert_flag_update(t, u, idx);
+ }
+}
+
+void mail_index_update_flags(struct mail_index_transaction *t, uint32_t seq,
+ enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ mail_index_update_flags_range(t, seq, seq, modify_type, flags);
+}
+
+void mail_index_update_header(struct mail_index_transaction *t,
+ size_t offset, const void *data, size_t size,
+ bool prepend)
+{
+ i_assert(offset < sizeof(t->pre_hdr_change));
+ i_assert(size <= sizeof(t->pre_hdr_change) - offset);
+
+ t->log_updates = TRUE;
+
+ if (prepend) {
+ t->pre_hdr_changed = TRUE;
+ memcpy(t->pre_hdr_change + offset, data, size);
+ for (; size > 0; size--)
+ t->pre_hdr_mask[offset++] = 1;
+ } else {
+ t->post_hdr_changed = TRUE;
+ memcpy(t->post_hdr_change + offset, data, size);
+ for (; size > 0; size--)
+ t->post_hdr_mask[offset++] = 1;
+ }
+}
+
+void mail_index_ext_resize(struct mail_index_transaction *t, uint32_t ext_id,
+ uint32_t hdr_size, uint16_t record_size,
+ uint16_t record_align)
+{
+ struct mail_transaction_ext_intro intro;
+ uint32_t old_record_size, old_record_align;
+
+ memset(&intro, 0, sizeof(intro));
+
+ /* get ext_id from transaction's map if it's there */
+ if (!mail_index_map_get_ext_idx(t->view->map, ext_id, &intro.ext_id)) {
+ /* have to create it */
+ const struct mail_index_registered_ext *rext;
+
+ intro.ext_id = (uint32_t)-1;
+ rext = array_idx(&t->view->index->extensions, ext_id);
+ old_record_size = rext->record_size;
+ old_record_align = rext->record_align;
+ } else {
+ const struct mail_index_ext *ext;
+
+ ext = array_idx(&t->view->map->extensions, intro.ext_id);
+ old_record_size = ext->record_size;
+ old_record_align = ext->record_align;
+ }
+
+ /* allow only header size changes if extension records have already
+ been changed in transaction */
+ i_assert(!array_is_created(&t->ext_rec_updates) ||
+ (old_record_size == record_size &&
+ old_record_align == record_align));
+
+ t->log_ext_updates = TRUE;
+
+ if (!array_is_created(&t->ext_resizes))
+ i_array_init(&t->ext_resizes, ext_id + 2);
+
+ intro.hdr_size = hdr_size;
+ intro.record_size = record_size;
+ intro.record_align = record_align;
+ intro.name_size = 1;
+ array_idx_set(&t->ext_resizes, ext_id, &intro);
+}
+
+void mail_index_ext_reset(struct mail_index_transaction *t, uint32_t ext_id,
+ uint32_t reset_id, bool clear_data)
+{
+ struct mail_transaction_ext_reset reset;
+
+ i_assert(reset_id != 0);
+
+ memset(&reset, 0, sizeof(reset));
+ reset.new_reset_id = reset_id;
+ reset.preserve_data = !clear_data;
+
+ mail_index_ext_set_reset_id(t, ext_id, reset_id);
+
+ if (!array_is_created(&t->ext_resets))
+ i_array_init(&t->ext_resets, ext_id + 2);
+ array_idx_set(&t->ext_resets, ext_id, &reset);
+ t->log_ext_updates = TRUE;
+}
+
+void mail_index_ext_reset_inc(struct mail_index_transaction *t, uint32_t ext_id,
+ uint32_t prev_reset_id, bool clear_data)
+{
+ uint32_t expected_reset_id = prev_reset_id + 1;
+
+ mail_index_ext_reset(t, ext_id, (uint32_t)-1, clear_data);
+
+ if (!array_is_created(&t->ext_reset_atomic))
+ i_array_init(&t->ext_reset_atomic, ext_id + 2);
+ array_idx_set(&t->ext_reset_atomic, ext_id, &expected_reset_id);
+}
+
+static bool
+mail_index_transaction_has_ext_updates(const ARRAY_TYPE(seq_array_array) *arr)
+{
+ const ARRAY_TYPE(seq_array) *array;
+ unsigned int i, count;
+
+ if (array_is_created(arr)) {
+ array = array_get(arr, &count);
+ for (i = 0; i < count; i++) {
+ if (array_is_created(&array[i]))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool
+mail_index_transaction_has_ext_changes(struct mail_index_transaction *t)
+{
+ unsigned int i, count;
+
+ if (mail_index_transaction_has_ext_updates(&t->ext_rec_updates))
+ return TRUE;
+ if (mail_index_transaction_has_ext_updates(&t->ext_rec_atomics))
+ return TRUE;
+
+ if (array_is_created(&t->ext_hdr_updates)) {
+ const struct mail_index_transaction_ext_hdr_update *hdr;
+
+ hdr = array_get(&t->ext_hdr_updates, &count);
+ for (i = 0; i < count; i++) {
+ if (hdr[i].alloc_size > 0)
+ return TRUE;
+ }
+ }
+ if (array_is_created(&t->ext_resets)) {
+ const struct mail_transaction_ext_reset *resets;
+
+ resets = array_get(&t->ext_resets, &count);
+ for (i = 0; i < count; i++) {
+ if (resets[i].new_reset_id != 0)
+ return TRUE;
+ }
+ }
+ if (array_is_created(&t->ext_resizes)) {
+ const struct mail_transaction_ext_intro *resizes;
+
+ resizes = array_get(&t->ext_resizes, &count);
+ for (i = 0; i < count; i++) {
+ if (resizes[i].name_size > 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+mail_index_ext_update_reset(ARRAY_TYPE(seq_array_array) *arr, uint32_t ext_id)
+{
+ if (array_is_created(arr) && ext_id < array_count(arr)) {
+ /* if extension records have been updated, clear them */
+ ARRAY_TYPE(seq_array) *array;
+
+ array = array_idx_modifiable(arr, ext_id);
+ if (array_is_created(array))
+ array_clear(array);
+ }
+}
+
+void mail_index_ext_set_reset_id(struct mail_index_transaction *t,
+ uint32_t ext_id, uint32_t reset_id)
+{
+ mail_index_ext_update_reset(&t->ext_rec_updates, ext_id);
+ mail_index_ext_update_reset(&t->ext_rec_atomics, ext_id);
+ if (array_is_created(&t->ext_hdr_updates) &&
+ ext_id < array_count(&t->ext_hdr_updates)) {
+ /* if extension headers have been updated, clear them */
+ struct mail_index_transaction_ext_hdr_update *hdr;
+
+ hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id);
+ if (hdr->alloc_size > 0) {
+ i_free_and_null(hdr->mask);
+ i_free_and_null(hdr->data);
+ }
+ hdr->alloc_size = 0;
+ }
+ if (array_is_created(&t->ext_resets) &&
+ ext_id < array_count(&t->ext_resets)) {
+ /* clear resets */
+ array_idx_clear(&t->ext_resets, ext_id);
+ }
+ if (array_is_created(&t->ext_resizes) &&
+ ext_id < array_count(&t->ext_resizes)) {
+ /* clear resizes */
+ array_idx_clear(&t->ext_resizes, ext_id);
+ }
+
+ if (!array_is_created(&t->ext_reset_ids))
+ i_array_init(&t->ext_reset_ids, ext_id + 2);
+ array_idx_set(&t->ext_reset_ids, ext_id, &reset_id);
+
+ t->log_ext_updates = mail_index_transaction_has_ext_changes(t);
+}
+
+void mail_index_update_header_ext(struct mail_index_transaction *t,
+ uint32_t ext_id, size_t offset,
+ const void *data, size_t size)
+{
+ struct mail_index_transaction_ext_hdr_update *hdr;
+ size_t new_size;
+
+ i_assert(offset <= (uint16_t)-1 && size <= (uint16_t)-1 &&
+ offset + size <= (uint16_t)-1);
+
+ if (!array_is_created(&t->ext_hdr_updates))
+ i_array_init(&t->ext_hdr_updates, ext_id + 2);
+
+ hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id);
+ if (hdr->alloc_size < offset || hdr->alloc_size - offset < size) {
+ i_assert(size < (size_t)-1 - offset);
+ new_size = nearest_power(offset + size);
+ hdr->mask = i_realloc(hdr->mask, hdr->alloc_size, new_size);
+ hdr->data = i_realloc(hdr->data, hdr->alloc_size, new_size);
+ hdr->alloc_size = new_size;
+ }
+ memset(hdr->mask + offset, 1, size);
+ memcpy(hdr->data + offset, data, size);
+
+ t->log_ext_updates = TRUE;
+}
+
+void mail_index_update_ext(struct mail_index_transaction *t, uint32_t seq,
+ uint32_t ext_id, const void *data, void *old_data_r)
+{
+ struct mail_index *index = t->view->index;
+ const struct mail_index_registered_ext *rext;
+ const struct mail_transaction_ext_intro *intro;
+ uint16_t record_size;
+ ARRAY_TYPE(seq_array) *array;
+ unsigned int count;
+
+ i_assert(seq > 0 &&
+ (seq <= mail_index_view_get_messages_count(t->view) ||
+ seq <= t->last_new_seq));
+ i_assert(ext_id < array_count(&index->extensions));
+
+ t->log_ext_updates = TRUE;
+
+ if (!array_is_created(&t->ext_resizes)) {
+ intro = NULL;
+ count = 0;
+ } else {
+ intro = array_get(&t->ext_resizes, &count);
+ }
+ if (ext_id < count && intro[ext_id].name_size != 0) {
+ /* resized record */
+ record_size = intro[ext_id].record_size;
+ } else {
+ rext = array_idx(&index->extensions, ext_id);
+ record_size = rext->record_size;
+ }
+
+ if (!array_is_created(&t->ext_rec_updates))
+ i_array_init(&t->ext_rec_updates, ext_id + 2);
+ array = array_idx_modifiable(&t->ext_rec_updates, ext_id);
+
+ /* @UNSAFE */
+ if (!mail_index_seq_array_add(array, seq, data, record_size,
+ old_data_r)) {
+ /* not found, clear old_data if it was given */
+ if (old_data_r != NULL)
+ memset(old_data_r, 0, record_size);
+ }
+}
+
+int mail_index_atomic_inc_ext(struct mail_index_transaction *t,
+ uint32_t seq, uint32_t ext_id, int diff)
+{
+ ARRAY_TYPE(seq_array) *array;
+ int32_t old_diff32, diff32 = diff;
+
+ i_assert(seq > 0 &&
+ (seq <= mail_index_view_get_messages_count(t->view) ||
+ seq <= t->last_new_seq));
+ i_assert(ext_id < array_count(&t->view->index->extensions));
+ /* currently non-external transactions can be applied multiple times,
+ causing multiple increments. */
+ //FIXME:i_assert((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+ t->log_ext_updates = TRUE;
+ if (!array_is_created(&t->ext_rec_atomics))
+ i_array_init(&t->ext_rec_atomics, ext_id + 2);
+ array = array_idx_modifiable(&t->ext_rec_atomics, ext_id);
+ if (mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32),
+ &old_diff32)) {
+ /* already incremented this sequence in this transaction */
+ diff32 += old_diff32;
+ mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32),
+ NULL);
+ }
+ return diff32;
+}
+
+static bool
+keyword_update_has_changes(struct mail_index_transaction *t, uint32_t seq,
+ enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct mail_index_transaction_keyword_update *u;
+ ARRAY_TYPE(keyword_indexes) existing;
+ const unsigned int *existing_idx;
+ unsigned int i, j, existing_count;
+ bool found;
+
+ t_array_init(&existing, 32);
+ if (seq < t->first_new_seq)
+ mail_index_lookup_keywords(t->view, seq, &existing);
+ existing_idx = array_get(&existing, &existing_count);
+
+ if (modify_type == MODIFY_REPLACE && existing_count != keywords->count)
+ return TRUE;
+
+ for (i = 0; i < keywords->count; i++) {
+ u = array_idx_modifiable(&t->keyword_updates,
+ keywords->idx[i]);
+ if (array_is_created(&u->add_seq) ||
+ array_is_created(&u->remove_seq))
+ return TRUE;
+
+ found = FALSE;
+ for (j = 0; j < existing_count; j++) {
+ if (existing_idx[j] == keywords->idx[i]) {
+ found = TRUE;
+ break;
+ }
+ }
+ switch (modify_type) {
+ case MODIFY_ADD:
+ case MODIFY_REPLACE:
+ if (!found)
+ return TRUE;
+ break;
+ case MODIFY_REMOVE:
+ if (found)
+ return TRUE;
+ break;
+ }
+ }
+ return FALSE;
+}
+
+void mail_index_update_keywords(struct mail_index_transaction *t, uint32_t seq,
+ enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct mail_index_transaction_keyword_update *u;
+ unsigned int i, ku_count;
+ bool changed;
+
+ i_assert(seq > 0 &&
+ (seq <= mail_index_view_get_messages_count(t->view) ||
+ seq <= t->last_new_seq));
+ i_assert(keywords->count > 0 || modify_type == MODIFY_REPLACE);
+ i_assert(keywords->index == t->view->index);
+
+ update_minmax_flagupdate_seq(t, seq, seq);
+
+ if (!array_is_created(&t->keyword_updates) && keywords->count > 0) {
+ uint32_t max_idx = keywords->idx[keywords->count-1];
+
+ i_array_init(&t->keyword_updates, max_idx + 1);
+ }
+
+ if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) {
+ T_BEGIN {
+ changed = keyword_update_has_changes(t, seq,
+ modify_type,
+ keywords);
+ } T_END;
+ if (!changed)
+ return;
+ }
+
+ /* Update add_seq and remove_seq arrays which describe the keyword
+ changes. Don't bother updating remove_seq or keyword resets for
+ newly added messages since they default to not having any
+ keywords anyway. */
+ switch (modify_type) {
+ case MODIFY_ADD:
+ for (i = 0; i < keywords->count; i++) {
+ u = array_idx_modifiable(&t->keyword_updates,
+ keywords->idx[i]);
+ seq_range_array_add(&u->add_seq, 16, seq);
+ seq_range_array_remove(&u->remove_seq, seq);
+ }
+ break;
+ case MODIFY_REMOVE:
+ for (i = 0; i < keywords->count; i++) {
+ u = array_idx_modifiable(&t->keyword_updates,
+ keywords->idx[i]);
+ seq_range_array_remove(&u->add_seq, seq);
+ if (seq < t->first_new_seq)
+ seq_range_array_add(&u->remove_seq, 16, seq);
+ }
+ break;
+ case MODIFY_REPLACE:
+ /* Remove sequence from all add/remove arrays */
+ if (array_is_created(&t->keyword_updates)) {
+ u = array_get_modifiable(&t->keyword_updates,
+ &ku_count);
+ for (i = 0; i < ku_count; i++) {
+ seq_range_array_remove(&u[i].add_seq, seq);
+ seq_range_array_remove(&u[i].remove_seq, seq);
+ }
+ }
+ /* Add the wanted keyword back */
+ for (i = 0; i < keywords->count; i++) {
+ u = array_idx_modifiable(&t->keyword_updates,
+ keywords->idx[i]);
+ seq_range_array_add(&u->add_seq, 16, seq);
+ }
+
+ if (seq < t->first_new_seq)
+ seq_range_array_add(&t->keyword_resets, 16, seq);
+ break;
+ }
+
+ t->log_updates = TRUE;
+}
+
+void mail_index_transaction_reset(struct mail_index_transaction *t)
+{
+ t->v.reset(t);
+}
+
+void mail_index_reset(struct mail_index_transaction *t)
+{
+ mail_index_transaction_reset(t);
+
+ t->reset = TRUE;
+}
+
+void mail_index_transaction_set_max_modseq(struct mail_index_transaction *t,
+ uint64_t max_modseq,
+ ARRAY_TYPE(seq_range) *seqs)
+{
+ i_assert(array_is_created(seqs));
+
+ t->max_modseq = max_modseq;
+ t->conflict_seqs = seqs;
+}
/* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */
-/* Inside transaction we keep messages stored in sequences in uid fields.
- Before they're written to transaction log the sequences are changed to
- UIDs. This is because we're able to compress sequence ranges better. */
-
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "mail-transaction-log-private.h"
#include "mail-index-transaction-private.h"
+#include <stdlib.h>
+
void (*hook_mail_index_transaction_created)
(struct mail_index_transaction *t) = NULL;
-static bool
-mail_index_transaction_has_ext_changes(struct mail_index_transaction *t);
-
-static void mail_index_transaction_reset_v(struct mail_index_transaction *t)
-{
- ARRAY_TYPE(seq_array) *recs;
- struct mail_index_transaction_ext_hdr_update *ext_hdrs;
- unsigned i, count;
-
- if (array_is_created(&t->ext_rec_updates)) {
- recs = array_get_modifiable(&t->ext_rec_updates, &count);
- for (i = 0; i < count; i++) {
- if (array_is_created(&recs[i]))
- array_free(&recs[i]);
- }
- array_free(&t->ext_rec_updates);
- }
- if (array_is_created(&t->ext_rec_atomics)) {
- recs = array_get_modifiable(&t->ext_rec_atomics, &count);
- for (i = 0; i < count; i++) {
- if (array_is_created(&recs[i]))
- array_free(&recs[i]);
- }
- array_free(&t->ext_rec_atomics);
- }
- if (array_is_created(&t->ext_hdr_updates)) {
- ext_hdrs = array_get_modifiable(&t->ext_hdr_updates, &count);
- for (i = 0; i < count; i++) {
- i_free(ext_hdrs[i].data);
- i_free(ext_hdrs[i].mask);
- }
- array_free(&t->ext_hdr_updates);
- }
-
- if (array_is_created(&t->keyword_updates)) {
- struct mail_index_transaction_keyword_update *u;
-
- u = array_get_modifiable(&t->keyword_updates, &count);
- for (i = 0; i < count; i++) {
- if (array_is_created(&u[i].add_seq))
- array_free(&u[i].add_seq);
- if (array_is_created(&u[i].remove_seq))
- array_free(&u[i].remove_seq);
- }
- array_free(&t->keyword_updates);
- }
- if (array_is_created(&t->keyword_resets))
- array_free(&t->keyword_resets);
-
- if (array_is_created(&t->appends))
- array_free(&t->appends);
- if (array_is_created(&t->expunges))
- array_free(&t->expunges);
- if (array_is_created(&t->updates))
- array_free(&t->updates);
- if (array_is_created(&t->ext_resizes))
- array_free(&t->ext_resizes);
- if (array_is_created(&t->ext_resets))
- array_free(&t->ext_resets);
- if (array_is_created(&t->ext_reset_ids))
- array_free(&t->ext_reset_ids);
- if (array_is_created(&t->ext_reset_atomic))
- array_free(&t->ext_reset_atomic);
-
- t->first_new_seq = mail_index_view_get_messages_count(t->view)+1;
- t->last_new_seq = 0;
- t->last_update_idx = 0;
- t->min_flagupdate_seq = 0;
- t->max_flagupdate_seq = 0;
-
- memset(t->pre_hdr_mask, 0, sizeof(t->pre_hdr_mask));
- memset(t->post_hdr_mask, 0, sizeof(t->post_hdr_mask));
-
- t->appends_nonsorted = FALSE;
- t->pre_hdr_changed = FALSE;
- t->post_hdr_changed = FALSE;
- t->reset = FALSE;
- t->log_updates = FALSE;
- t->log_ext_updates = FALSE;
-}
-
-void mail_index_transaction_set_log_updates(struct mail_index_transaction *t)
-{
- /* flag updates aren't included in log_updates */
- t->log_updates = array_is_created(&t->appends) ||
- array_is_created(&t->expunges) ||
- array_is_created(&t->keyword_resets) ||
- array_is_created(&t->keyword_updates) ||
- t->pre_hdr_changed || t->post_hdr_changed;
-}
-
-static void mail_index_transaction_free(struct mail_index_transaction *t)
-{
- mail_index_transaction_reset(t);
-
- array_free(&t->module_contexts);
- mail_index_view_transaction_unref(t->view);
- mail_index_view_close(&t->view);
- i_free(t);
-}
-
struct mail_index_view *
mail_index_transaction_get_view(struct mail_index_transaction *t)
{
struct mail_index_transaction *t = *_t;
*_t = NULL;
- if (--t->refcount == 0)
- mail_index_transaction_free(t);
+ if (--t->refcount > 0)
+ return;
+
+ mail_index_transaction_reset(t);
+
+ array_free(&t->module_contexts);
+ mail_index_view_transaction_unref(t->view);
+ mail_index_view_close(&t->view);
+ i_free(t);
}
uint32_t mail_index_transaction_get_next_uid(struct mail_index_transaction *t)
return next_uid;
}
-void mail_index_transaction_reset(struct mail_index_transaction *t)
-{
- t->v.reset(t);
-}
-
static int
mail_transaction_log_file_refresh(struct mail_index_transaction *t,
struct mail_transaction_log_append_ctx *ctx)
return 0;
}
-static void
-mail_index_update_day_headers(struct mail_index_transaction *t)
-{
- struct mail_index_header hdr;
- const struct mail_index_record *rec;
- const int max_days = N_ELEMENTS(hdr.day_first_uid);
- struct tm tm;
- time_t stamp;
- int i, days;
-
- hdr = *mail_index_get_header(t->view);
- rec = array_idx(&t->appends, 0);
-
- /* get beginning of today */
- tm = *localtime(&ioloop_time);
- tm.tm_hour = 0;
- tm.tm_min = 0;
- tm.tm_sec = 0;
- stamp = mktime(&tm);
- i_assert(stamp != (time_t)-1);
-
- if ((time_t)hdr.day_stamp >= stamp)
- return;
-
- /* get number of days since last message */
- days = (stamp - hdr.day_stamp) / (3600*24);
- if (days > max_days)
- days = max_days;
-
- /* @UNSAFE: move days forward and fill the missing days with old
- day_first_uid[0]. */
- memmove(hdr.day_first_uid + days, hdr.day_first_uid, max_days - days);
- for (i = 1; i < days; i++)
- hdr.day_first_uid[i] = hdr.day_first_uid[0];
-
- hdr.day_stamp = stamp;
- hdr.day_first_uid[0] = rec->uid;
-
- mail_index_update_header(t,
- offsetof(struct mail_index_header, day_stamp),
- &hdr.day_stamp, sizeof(hdr.day_stamp), FALSE);
- mail_index_update_header(t,
- offsetof(struct mail_index_header, day_first_uid),
- hdr.day_first_uid, sizeof(hdr.day_first_uid), FALSE);
-}
-
static int mail_index_transaction_commit_v(struct mail_index_transaction *t,
uint32_t *log_file_seq_r,
uoff_t *log_file_offset_r)
i_assert(t->first_new_seq >
mail_index_view_get_messages_count(t->view));
- if (array_is_created(&t->appends))
- mail_index_update_day_headers(t);
-
if (!MAIL_INDEX_TRANSACTION_HAS_CHANGES(t) && !t->reset) {
/* nothing to append */
ret = 0;
t->v.rollback(t);
}
-struct mail_index_record *
-mail_index_transaction_lookup(struct mail_index_transaction *t, uint32_t seq)
-{
- i_assert(seq >= t->first_new_seq && seq <= t->last_new_seq);
-
- return array_idx_modifiable(&t->appends, seq - t->first_new_seq);
-}
-
-void mail_index_append(struct mail_index_transaction *t, uint32_t uid,
- uint32_t *seq_r)
-{
- struct mail_index_record *rec;
-
- i_assert(!t->no_appends);
-
- t->log_updates = TRUE;
-
- if (!array_is_created(&t->appends))
- i_array_init(&t->appends, 32);
-
- /* sequence number is visible only inside given view,
- so let it generate it */
- if (t->last_new_seq != 0)
- *seq_r = ++t->last_new_seq;
- else
- *seq_r = t->last_new_seq = t->first_new_seq;
-
- rec = array_append_space(&t->appends);
- if (uid != 0) {
- rec->uid = uid;
- if (!t->appends_nonsorted &&
- t->last_new_seq != t->first_new_seq) {
- /* if previous record's UID is larger than this one,
- we'll have to sort the appends later */
- rec = mail_index_transaction_lookup(t, *seq_r - 1);
- if (rec->uid > uid)
- t->appends_nonsorted = TRUE;
- else if (rec->uid == uid)
- i_panic("Duplicate UIDs added in transaction");
- }
- if (t->highest_append_uid < uid)
- t->highest_append_uid = uid;
- }
-}
-
-void mail_index_append_assign_uids(struct mail_index_transaction *t,
- uint32_t first_uid, uint32_t *next_uid_r)
-{
- struct mail_index_record *recs;
- unsigned int i, count;
-
- i_assert(first_uid != 0);
-
- if (!array_is_created(&t->appends))
- return;
-
- recs = array_get_modifiable(&t->appends, &count);
-
- /* find the first mail with uid = 0 */
- for (i = 0; i < count; i++) {
- if (recs[i].uid == 0)
- break;
- }
-
- for (; i < count; i++) {
- i_assert(recs[i].uid == 0);
- recs[i].uid = first_uid++;
- }
-
- *next_uid_r = first_uid;
-}
-
-static void
-mail_index_expunge_last_append_ext(ARRAY_TYPE(seq_array_array) *ext_updates,
- uint32_t seq)
-{
- ARRAY_TYPE(seq_array) *seqs;
- unsigned int i, count, idx;
-
- if (!array_is_created(ext_updates))
- return;
-
- seqs = array_get_modifiable(ext_updates, &count);
- for (i = 0; i < count; i++) {
- if (array_is_created(&seqs[i]) &&
- mail_index_seq_array_lookup(&seqs[i], seq, &idx))
- array_delete(&seqs[i], idx, 1);
- }
-}
-
-static void
-mail_index_expunge_last_append(struct mail_index_transaction *t, uint32_t seq)
-{
- struct mail_index_transaction_keyword_update *kw_updates;
- unsigned int i, count;
-
- i_assert(seq == t->last_new_seq);
-
- /* remove extension updates */
- mail_index_expunge_last_append_ext(&t->ext_rec_updates, seq);
- mail_index_expunge_last_append_ext(&t->ext_rec_atomics, seq);
- t->log_ext_updates = mail_index_transaction_has_ext_changes(t);
-
- /* remove keywords */
- if (array_is_created(&t->keyword_resets))
- seq_range_array_remove(&t->keyword_resets, seq);
- if (array_is_created(&t->keyword_updates)) {
- kw_updates = array_get_modifiable(&t->keyword_updates, &count);
- for (i = 0; i < count; i++) {
- if (array_is_created(&kw_updates[i].add_seq)) {
- seq_range_array_remove(&kw_updates[i].add_seq,
- seq);
- }
- if (array_is_created(&kw_updates[i].remove_seq)) {
- seq_range_array_remove(
- &kw_updates[i].remove_seq, seq);
- }
- }
- }
-
- /* and finally remove the append itself */
- array_delete(&t->appends, seq - t->first_new_seq, 1);
- t->last_new_seq--;
- if (t->first_new_seq > t->last_new_seq) {
- t->last_new_seq = 0;
- t->appends_nonsorted = FALSE;
- array_free(&t->appends);
- }
- mail_index_transaction_set_log_updates(t);
-}
-
-void mail_index_expunge(struct mail_index_transaction *t, uint32_t seq)
-{
- i_assert(seq > 0);
- if (seq >= t->first_new_seq) {
- /* we can handle only the last append. otherwise we'd have to
- renumber sequences and that gets tricky. for now this is
- enough, since we typically want to expunge all the
- appends. */
- mail_index_expunge_last_append(t, seq);
- } else {
- t->log_updates = TRUE;
-
- /* expunges is a sorted array of {seq1, seq2, ..}, .. */
- seq_range_array_add(&t->expunges, 128, seq);
- }
-}
-
-static void update_minmax_flagupdate_seq(struct mail_index_transaction *t,
- uint32_t seq1, uint32_t seq2)
-{
- if (t->min_flagupdate_seq == 0) {
- t->min_flagupdate_seq = seq1;
- t->max_flagupdate_seq = seq2;
- } else {
- if (t->min_flagupdate_seq > seq1)
- t->min_flagupdate_seq = seq1;
- if (t->max_flagupdate_seq < seq2)
- t->max_flagupdate_seq = seq2;
- }
-}
-
-unsigned int
-mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t,
- unsigned int left_idx,
- unsigned int right_idx,
- uint32_t seq)
-{
- const struct mail_transaction_flag_update *updates;
- unsigned int idx, count;
-
- updates = array_get(&t->updates, &count);
- i_assert(left_idx <= right_idx && right_idx <= count);
-
- /* find the first update with either overlapping range,
- or the update which will come after our insert */
- idx = left_idx;
- while (left_idx < right_idx) {
- idx = (left_idx + right_idx) / 2;
-
- if (updates[idx].uid2 < seq)
- left_idx = idx+1;
- else if (updates[idx].uid1 > seq)
- right_idx = idx;
- else
- break;
- }
- if (left_idx > idx)
- idx++;
- return idx;
-}
-
-static void
-mail_index_insert_flag_update(struct mail_index_transaction *t,
- struct mail_transaction_flag_update u,
- unsigned int idx)
-{
- struct mail_transaction_flag_update *updates, tmp_update;
- unsigned int count, first_idx, max;
-
- updates = array_get_modifiable(&t->updates, &count);
-
- /* overlapping ranges, split/merge them */
- i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1);
- i_assert(idx == count || updates[idx].uid2 >= u.uid1);
-
- /* first we'll just add the changes without trying to merge anything */
- first_idx = idx;
- for (; idx < count && u.uid2 >= updates[idx].uid1; idx++) {
- i_assert(u.uid1 <= updates[idx].uid2);
- if (u.uid1 != updates[idx].uid1 &&
- (updates[idx].add_flags != u.add_flags ||
- updates[idx].remove_flags != u.remove_flags)) {
- if (u.uid1 < updates[idx].uid1) {
- /* insert new update */
- tmp_update = u;
- tmp_update.uid2 = updates[idx].uid1 - 1;
- } else {
- /* split existing update from beginning */
- tmp_update = updates[idx];
- tmp_update.uid2 = u.uid1 - 1;
- updates[idx].uid1 = u.uid1;
- }
-
- i_assert(tmp_update.uid1 <= tmp_update.uid2);
- i_assert(updates[idx].uid1 <= updates[idx].uid2);
-
- array_insert(&t->updates, idx, &tmp_update, 1);
- updates = array_get_modifiable(&t->updates, &count);
- idx++;
- } else if (u.uid1 < updates[idx].uid1) {
- updates[idx].uid1 = u.uid1;
- }
-
- if (u.uid2 < updates[idx].uid2 &&
- (updates[idx].add_flags != u.add_flags ||
- updates[idx].remove_flags != u.remove_flags)) {
- /* split existing update from end */
- tmp_update = updates[idx];
- tmp_update.uid2 = u.uid2;
- updates[idx].uid1 = u.uid2 + 1;
-
- i_assert(tmp_update.uid1 <= tmp_update.uid2);
- i_assert(updates[idx].uid1 <= updates[idx].uid2);
-
- array_insert(&t->updates, idx, &tmp_update, 1);
- updates = array_get_modifiable(&t->updates, &count);
- }
-
- updates[idx].add_flags =
- (updates[idx].add_flags | u.add_flags) &
- ~u.remove_flags;
- updates[idx].remove_flags =
- (updates[idx].remove_flags | u.remove_flags) &
- ~u.add_flags;
- u.uid1 = updates[idx].uid2 + 1;
-
- if (updates[idx].add_flags == 0 &&
- updates[idx].remove_flags == 0) {
- /* we can remove this update completely */
- array_delete(&t->updates, idx, 1);
- updates = array_get_modifiable(&t->updates, &count);
- }
-
- if (u.uid1 > u.uid2) {
- /* break here before idx++ so last_update_idx is set
- correctly */
- break;
- }
- }
- i_assert(idx <= count);
-
- if (u.uid1 <= u.uid2) {
- i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1);
- i_assert(idx == count || updates[idx].uid1 > u.uid2);
- array_insert(&t->updates, idx, &u, 1);
- }
- updates = array_get_modifiable(&t->updates, &count);
- t->last_update_idx = idx == count ? count-1 : idx;
-
- /* merge everything */
- idx = first_idx == 0 ? 0 : first_idx - 1;
- max = I_MIN(t->last_update_idx + 1, count);
- for (; idx < max; ) {
- if (updates[idx].uid2 + 1 == updates[idx+1].uid1 &&
- updates[idx].add_flags == updates[idx+1].add_flags &&
- updates[idx].remove_flags == updates[idx+1].remove_flags) {
- /* merge */
- updates[idx].uid2 = updates[idx+1].uid2;
- array_delete(&t->updates, idx + 1, 1);
- max--;
- if (t->last_update_idx > idx)
- t->last_update_idx--;
- updates = array_get_modifiable(&t->updates, &count);
- } else {
- idx++;
- }
- }
-}
-
-static void mail_index_record_modify_flags(struct mail_index_record *rec,
- enum modify_type modify_type,
- enum mail_flags flags)
-{
- switch (modify_type) {
- case MODIFY_REPLACE:
- rec->flags = flags;
- break;
- case MODIFY_ADD:
- rec->flags |= flags;
- break;
- case MODIFY_REMOVE:
- rec->flags &= ~flags;
- break;
- }
-}
-
-void mail_index_update_flags_range(struct mail_index_transaction *t,
- uint32_t seq1, uint32_t seq2,
- enum modify_type modify_type,
- enum mail_flags flags)
-{
- struct mail_index_record *rec;
- struct mail_transaction_flag_update u, *last_update;
- unsigned int idx, first_idx, count;
-
- update_minmax_flagupdate_seq(t, seq1, seq2);
- if (seq2 >= t->first_new_seq) {
- /* updates for appended messages, modify them directly */
- uint32_t seq;
-
- for (seq = I_MAX(t->first_new_seq, seq1); seq <= seq2; seq++) {
- rec = mail_index_transaction_lookup(t, seq);
- mail_index_record_modify_flags(rec, modify_type, flags);
- }
- if (seq1 >= t->first_new_seq)
- return;
-
- /* range contains also existing messages. update them next. */
- seq2 = t->first_new_seq - 1;
- }
-
- i_assert(seq1 <= seq2 && seq1 > 0);
- i_assert(seq2 <= mail_index_view_get_messages_count(t->view));
-
- if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0)
- t->drop_unnecessary_flag_updates = TRUE;
-
- memset(&u, 0, sizeof(u));
- u.uid1 = seq1;
- u.uid2 = seq2;
-
- switch (modify_type) {
- case MODIFY_REPLACE:
- u.add_flags = flags;
- u.remove_flags = ~flags & MAIL_INDEX_FLAGS_MASK;
- break;
- case MODIFY_ADD:
- if (flags == 0)
- return;
- u.add_flags = flags;
- break;
- case MODIFY_REMOVE:
- if (flags == 0)
- return;
- u.remove_flags = flags;
- break;
- }
-
- if (!array_is_created(&t->updates)) {
- i_array_init(&t->updates, 256);
- array_append(&t->updates, &u, 1);
- return;
- }
-
- last_update = array_get_modifiable(&t->updates, &count);
- if (t->last_update_idx < count) {
- /* fast path - hopefully we're updating the next message,
- or a message that is to be appended as last update */
- last_update += t->last_update_idx;
- if (seq1 - 1 == last_update->uid2) {
- if (u.add_flags == last_update->add_flags &&
- u.remove_flags == last_update->remove_flags &&
- (t->last_update_idx + 1 == count ||
- last_update[1].uid1 > seq2)) {
- /* we can just update the UID range */
- last_update->uid2 = seq2;
- return;
- }
- } else if (seq1 > last_update->uid2) {
- /* hopefully we can just append it */
- t->last_update_idx++;
- last_update++;
- }
- }
-
- if (t->last_update_idx == count)
- array_append(&t->updates, &u, 1);
- else {
- i_assert(t->last_update_idx < count);
-
- /* slow path */
- if (seq1 > last_update->uid2) {
- /* added after this */
- first_idx = t->last_update_idx + 1;
- } else {
- /* added before this or on top of this */
- first_idx = 0;
- count = t->last_update_idx + 1;
- }
- idx = mail_index_transaction_get_flag_update_pos(t, first_idx,
- count, u.uid1);
- mail_index_insert_flag_update(t, u, idx);
- }
-}
-
-void mail_index_update_flags(struct mail_index_transaction *t, uint32_t seq,
- enum modify_type modify_type,
- enum mail_flags flags)
-{
- mail_index_update_flags_range(t, seq, seq, modify_type, flags);
-}
-
-void mail_index_update_header(struct mail_index_transaction *t,
- size_t offset, const void *data, size_t size,
- bool prepend)
-{
- i_assert(offset < sizeof(t->pre_hdr_change));
- i_assert(size <= sizeof(t->pre_hdr_change) - offset);
-
- t->log_updates = TRUE;
-
- if (prepend) {
- t->pre_hdr_changed = TRUE;
- memcpy(t->pre_hdr_change + offset, data, size);
- for (; size > 0; size--)
- t->pre_hdr_mask[offset++] = 1;
- } else {
- t->post_hdr_changed = TRUE;
- memcpy(t->post_hdr_change + offset, data, size);
- for (; size > 0; size--)
- t->post_hdr_mask[offset++] = 1;
- }
-}
-
-void mail_index_ext_resize(struct mail_index_transaction *t, uint32_t ext_id,
- uint32_t hdr_size, uint16_t record_size,
- uint16_t record_align)
-{
- struct mail_transaction_ext_intro intro;
- uint32_t old_record_size, old_record_align;
-
- memset(&intro, 0, sizeof(intro));
-
- /* get ext_id from transaction's map if it's there */
- if (!mail_index_map_get_ext_idx(t->view->map, ext_id, &intro.ext_id)) {
- /* have to create it */
- const struct mail_index_registered_ext *rext;
-
- intro.ext_id = (uint32_t)-1;
- rext = array_idx(&t->view->index->extensions, ext_id);
- old_record_size = rext->record_size;
- old_record_align = rext->record_align;
- } else {
- const struct mail_index_ext *ext;
-
- ext = array_idx(&t->view->map->extensions, intro.ext_id);
- old_record_size = ext->record_size;
- old_record_align = ext->record_align;
- }
-
- /* allow only header size changes if extension records have already
- been changed in transaction */
- i_assert(!array_is_created(&t->ext_rec_updates) ||
- (old_record_size == record_size &&
- old_record_align == record_align));
-
- t->log_ext_updates = TRUE;
-
- if (!array_is_created(&t->ext_resizes))
- i_array_init(&t->ext_resizes, ext_id + 2);
-
- intro.hdr_size = hdr_size;
- intro.record_size = record_size;
- intro.record_align = record_align;
- intro.name_size = 1;
- array_idx_set(&t->ext_resizes, ext_id, &intro);
-}
-
-void mail_index_ext_reset(struct mail_index_transaction *t, uint32_t ext_id,
- uint32_t reset_id, bool clear_data)
-{
- struct mail_transaction_ext_reset reset;
-
- i_assert(reset_id != 0);
-
- memset(&reset, 0, sizeof(reset));
- reset.new_reset_id = reset_id;
- reset.preserve_data = !clear_data;
-
- mail_index_ext_set_reset_id(t, ext_id, reset_id);
-
- if (!array_is_created(&t->ext_resets))
- i_array_init(&t->ext_resets, ext_id + 2);
- array_idx_set(&t->ext_resets, ext_id, &reset);
- t->log_ext_updates = TRUE;
-}
-
-void mail_index_ext_reset_inc(struct mail_index_transaction *t, uint32_t ext_id,
- uint32_t prev_reset_id, bool clear_data)
-{
- uint32_t expected_reset_id = prev_reset_id + 1;
-
- mail_index_ext_reset(t, ext_id, (uint32_t)-1, clear_data);
-
- if (!array_is_created(&t->ext_reset_atomic))
- i_array_init(&t->ext_reset_atomic, ext_id + 2);
- array_idx_set(&t->ext_reset_atomic, ext_id, &expected_reset_id);
-}
-
-static bool
-mail_index_transaction_has_ext_updates(const ARRAY_TYPE(seq_array_array) *arr)
-{
- const ARRAY_TYPE(seq_array) *array;
- unsigned int i, count;
-
- if (array_is_created(arr)) {
- array = array_get(arr, &count);
- for (i = 0; i < count; i++) {
- if (array_is_created(&array[i]))
- return TRUE;
- }
- }
- return FALSE;
-}
-
-static bool
-mail_index_transaction_has_ext_changes(struct mail_index_transaction *t)
-{
- unsigned int i, count;
-
- if (mail_index_transaction_has_ext_updates(&t->ext_rec_updates))
- return TRUE;
- if (mail_index_transaction_has_ext_updates(&t->ext_rec_atomics))
- return TRUE;
-
- if (array_is_created(&t->ext_hdr_updates)) {
- const struct mail_index_transaction_ext_hdr_update *hdr;
-
- hdr = array_get(&t->ext_hdr_updates, &count);
- for (i = 0; i < count; i++) {
- if (hdr[i].alloc_size > 0)
- return TRUE;
- }
- }
- if (array_is_created(&t->ext_resets)) {
- const struct mail_transaction_ext_reset *resets;
-
- resets = array_get(&t->ext_resets, &count);
- for (i = 0; i < count; i++) {
- if (resets[i].new_reset_id != 0)
- return TRUE;
- }
- }
- if (array_is_created(&t->ext_resizes)) {
- const struct mail_transaction_ext_intro *resizes;
-
- resizes = array_get(&t->ext_resizes, &count);
- for (i = 0; i < count; i++) {
- if (resizes[i].name_size > 0)
- return TRUE;
- }
- }
- return FALSE;
-}
-
-static void
-mail_index_ext_update_reset(ARRAY_TYPE(seq_array_array) *arr, uint32_t ext_id)
-{
- if (array_is_created(arr) && ext_id < array_count(arr)) {
- /* if extension records have been updated, clear them */
- ARRAY_TYPE(seq_array) *array;
-
- array = array_idx_modifiable(arr, ext_id);
- if (array_is_created(array))
- array_clear(array);
- }
-}
-
-void mail_index_ext_set_reset_id(struct mail_index_transaction *t,
- uint32_t ext_id, uint32_t reset_id)
-{
- mail_index_ext_update_reset(&t->ext_rec_updates, ext_id);
- mail_index_ext_update_reset(&t->ext_rec_atomics, ext_id);
- if (array_is_created(&t->ext_hdr_updates) &&
- ext_id < array_count(&t->ext_hdr_updates)) {
- /* if extension headers have been updated, clear them */
- struct mail_index_transaction_ext_hdr_update *hdr;
-
- hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id);
- if (hdr->alloc_size > 0) {
- i_free_and_null(hdr->mask);
- i_free_and_null(hdr->data);
- }
- hdr->alloc_size = 0;
- }
- if (array_is_created(&t->ext_resets) &&
- ext_id < array_count(&t->ext_resets)) {
- /* clear resets */
- array_idx_clear(&t->ext_resets, ext_id);
- }
- if (array_is_created(&t->ext_resizes) &&
- ext_id < array_count(&t->ext_resizes)) {
- /* clear resizes */
- array_idx_clear(&t->ext_resizes, ext_id);
- }
-
- if (!array_is_created(&t->ext_reset_ids))
- i_array_init(&t->ext_reset_ids, ext_id + 2);
- array_idx_set(&t->ext_reset_ids, ext_id, &reset_id);
-
- t->log_ext_updates = mail_index_transaction_has_ext_changes(t);
-}
-
-void mail_index_update_header_ext(struct mail_index_transaction *t,
- uint32_t ext_id, size_t offset,
- const void *data, size_t size)
-{
- struct mail_index_transaction_ext_hdr_update *hdr;
- size_t new_size;
-
- i_assert(offset <= (uint16_t)-1 && size <= (uint16_t)-1 &&
- offset + size <= (uint16_t)-1);
-
- if (!array_is_created(&t->ext_hdr_updates))
- i_array_init(&t->ext_hdr_updates, ext_id + 2);
-
- hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id);
- if (hdr->alloc_size < offset || hdr->alloc_size - offset < size) {
- i_assert(size < (size_t)-1 - offset);
- new_size = nearest_power(offset + size);
- hdr->mask = i_realloc(hdr->mask, hdr->alloc_size, new_size);
- hdr->data = i_realloc(hdr->data, hdr->alloc_size, new_size);
- hdr->alloc_size = new_size;
- }
- memset(hdr->mask + offset, 1, size);
- memcpy(hdr->data + offset, data, size);
-
- t->log_ext_updates = TRUE;
-}
-
-void mail_index_update_ext(struct mail_index_transaction *t, uint32_t seq,
- uint32_t ext_id, const void *data, void *old_data_r)
-{
- struct mail_index *index = t->view->index;
- const struct mail_index_registered_ext *rext;
- const struct mail_transaction_ext_intro *intro;
- uint16_t record_size;
- ARRAY_TYPE(seq_array) *array;
- unsigned int count;
-
- i_assert(seq > 0 &&
- (seq <= mail_index_view_get_messages_count(t->view) ||
- seq <= t->last_new_seq));
- i_assert(ext_id < array_count(&index->extensions));
-
- t->log_ext_updates = TRUE;
-
- if (!array_is_created(&t->ext_resizes)) {
- intro = NULL;
- count = 0;
- } else {
- intro = array_get(&t->ext_resizes, &count);
- }
- if (ext_id < count && intro[ext_id].name_size != 0) {
- /* resized record */
- record_size = intro[ext_id].record_size;
- } else {
- rext = array_idx(&index->extensions, ext_id);
- record_size = rext->record_size;
- }
-
- if (!array_is_created(&t->ext_rec_updates))
- i_array_init(&t->ext_rec_updates, ext_id + 2);
- array = array_idx_modifiable(&t->ext_rec_updates, ext_id);
-
- /* @UNSAFE */
- if (!mail_index_seq_array_add(array, seq, data, record_size,
- old_data_r)) {
- /* not found, clear old_data if it was given */
- if (old_data_r != NULL)
- memset(old_data_r, 0, record_size);
- }
-}
-
-int mail_index_atomic_inc_ext(struct mail_index_transaction *t,
- uint32_t seq, uint32_t ext_id, int diff)
-{
- ARRAY_TYPE(seq_array) *array;
- int32_t old_diff32, diff32 = diff;
-
- i_assert(seq > 0 &&
- (seq <= mail_index_view_get_messages_count(t->view) ||
- seq <= t->last_new_seq));
- i_assert(ext_id < array_count(&t->view->index->extensions));
- /* currently non-external transactions can be applied multiple times,
- causing multiple increments. */
- //FIXME:i_assert((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0);
-
- t->log_ext_updates = TRUE;
- if (!array_is_created(&t->ext_rec_atomics))
- i_array_init(&t->ext_rec_atomics, ext_id + 2);
- array = array_idx_modifiable(&t->ext_rec_atomics, ext_id);
- if (mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32),
- &old_diff32)) {
- /* already incremented this sequence in this transaction */
- diff32 += old_diff32;
- mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32),
- NULL);
- }
- return diff32;
-}
-
-static bool
-keyword_update_has_changes(struct mail_index_transaction *t, uint32_t seq,
- enum modify_type modify_type,
- struct mail_keywords *keywords)
-{
- struct mail_index_transaction_keyword_update *u;
- ARRAY_TYPE(keyword_indexes) existing;
- const unsigned int *existing_idx;
- unsigned int i, j, existing_count;
- bool found;
-
- t_array_init(&existing, 32);
- if (seq < t->first_new_seq)
- mail_index_lookup_keywords(t->view, seq, &existing);
- existing_idx = array_get(&existing, &existing_count);
-
- if (modify_type == MODIFY_REPLACE && existing_count != keywords->count)
- return TRUE;
-
- for (i = 0; i < keywords->count; i++) {
- u = array_idx_modifiable(&t->keyword_updates,
- keywords->idx[i]);
- if (array_is_created(&u->add_seq) ||
- array_is_created(&u->remove_seq))
- return TRUE;
-
- found = FALSE;
- for (j = 0; j < existing_count; j++) {
- if (existing_idx[j] == keywords->idx[i]) {
- found = TRUE;
- break;
- }
- }
- switch (modify_type) {
- case MODIFY_ADD:
- case MODIFY_REPLACE:
- if (!found)
- return TRUE;
- break;
- case MODIFY_REMOVE:
- if (found)
- return TRUE;
- break;
- }
- }
- return FALSE;
-}
-
-void mail_index_update_keywords(struct mail_index_transaction *t, uint32_t seq,
- enum modify_type modify_type,
- struct mail_keywords *keywords)
-{
- struct mail_index_transaction_keyword_update *u;
- unsigned int i, ku_count;
- bool changed;
-
- i_assert(seq > 0 &&
- (seq <= mail_index_view_get_messages_count(t->view) ||
- seq <= t->last_new_seq));
- i_assert(keywords->count > 0 || modify_type == MODIFY_REPLACE);
- i_assert(keywords->index == t->view->index);
-
- update_minmax_flagupdate_seq(t, seq, seq);
-
- if (!array_is_created(&t->keyword_updates) && keywords->count > 0) {
- uint32_t max_idx = keywords->idx[keywords->count-1];
-
- i_array_init(&t->keyword_updates, max_idx + 1);
- }
-
- if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) {
- T_BEGIN {
- changed = keyword_update_has_changes(t, seq,
- modify_type,
- keywords);
- } T_END;
- if (!changed)
- return;
- }
-
- /* Update add_seq and remove_seq arrays which describe the keyword
- changes. Don't bother updating remove_seq or keyword resets for
- newly added messages since they default to not having any
- keywords anyway. */
- switch (modify_type) {
- case MODIFY_ADD:
- for (i = 0; i < keywords->count; i++) {
- u = array_idx_modifiable(&t->keyword_updates,
- keywords->idx[i]);
- seq_range_array_add(&u->add_seq, 16, seq);
- seq_range_array_remove(&u->remove_seq, seq);
- }
- break;
- case MODIFY_REMOVE:
- for (i = 0; i < keywords->count; i++) {
- u = array_idx_modifiable(&t->keyword_updates,
- keywords->idx[i]);
- seq_range_array_remove(&u->add_seq, seq);
- if (seq < t->first_new_seq)
- seq_range_array_add(&u->remove_seq, 16, seq);
- }
- break;
- case MODIFY_REPLACE:
- /* Remove sequence from all add/remove arrays */
- if (array_is_created(&t->keyword_updates)) {
- u = array_get_modifiable(&t->keyword_updates,
- &ku_count);
- for (i = 0; i < ku_count; i++) {
- seq_range_array_remove(&u[i].add_seq, seq);
- seq_range_array_remove(&u[i].remove_seq, seq);
- }
- }
- /* Add the wanted keyword back */
- for (i = 0; i < keywords->count; i++) {
- u = array_idx_modifiable(&t->keyword_updates,
- keywords->idx[i]);
- seq_range_array_add(&u->add_seq, 16, seq);
- }
-
- if (seq < t->first_new_seq)
- seq_range_array_add(&t->keyword_resets, 16, seq);
- break;
- }
-
- t->log_updates = TRUE;
-}
-
-void mail_index_reset(struct mail_index_transaction *t)
-{
- mail_index_transaction_reset(t);
-
- t->reset = TRUE;
-}
-
-void mail_index_transaction_set_max_modseq(struct mail_index_transaction *t,
- uint64_t max_modseq,
- ARRAY_TYPE(seq_range) *seqs)
-{
- i_assert(array_is_created(seqs));
-
- t->max_modseq = max_modseq;
- t->conflict_seqs = seqs;
-}
-
static struct mail_index_transaction_vfuncs trans_vfuncs = {
mail_index_transaction_reset_v,
mail_index_transaction_commit_v,
--- /dev/null
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "test-common.h"
+#include "mail-index-private.h"
+#include "mail-index-transaction-private.h"
+
+#include <stdlib.h>
+
+static struct mail_index_header hdr;
+static struct mail_index_record rec;
+
+const struct mail_index_header *
+mail_index_get_header(struct mail_index_view *view ATTR_UNUSED)
+{
+ return &hdr;
+}
+
+const struct mail_index_record *
+mail_index_lookup(struct mail_index_view *view ATTR_UNUSED,
+ uint32_t seq ATTR_UNUSED)
+{
+ return &rec;
+}
+
+void mail_index_lookup_keywords(struct mail_index_view *view ATTR_UNUSED,
+ uint32_t seq ATTR_UNUSED,
+ ARRAY_TYPE(keyword_indexes) *keyword_idx ATTR_UNUSED)
+{
+ array_clear(keyword_idx);
+}
+
+bool mail_index_map_get_ext_idx(struct mail_index_map *map ATTR_UNUSED,
+ uint32_t ext_id ATTR_UNUSED,
+ uint32_t *idx_r ATTR_UNUSED)
+{
+ return FALSE;
+}
+
+uint32_t mail_index_view_get_messages_count(struct mail_index_view *view ATTR_UNUSED)
+{
+ return hdr.messages_count;
+}
+
+static struct mail_index_transaction *
+mail_index_transaction_new(void)
+{
+ struct mail_index_transaction *t;
+
+ t = t_new(struct mail_index_transaction, 1);
+ t->first_new_seq = hdr.messages_count + 1;
+ return t;
+}
+
+static void test_mail_index_append(void)
+{
+ struct mail_index_transaction *t;
+ const struct mail_index_record *appends;
+ unsigned int count;
+ uint32_t seq, next_uid;
+
+ hdr.messages_count = 4;
+ t = mail_index_transaction_new();
+
+ test_begin("mail index append");
+ mail_index_append(t, 0, &seq);
+ test_assert(t->log_updates);
+ test_assert(seq == 5);
+ mail_index_append(t, 0, &seq);
+ test_assert(seq == 6);
+ test_assert(!t->appends_nonsorted);
+
+ mail_index_append_assign_uids(t, 123, &next_uid);
+ test_assert(next_uid == 125);
+
+ appends = array_get(&t->appends, &count);
+ test_assert(appends[0].uid == 123);
+ test_assert(appends[0].flags == 0);
+ test_assert(appends[1].uid == 124);
+ test_assert(appends[1].flags == 0);
+ test_end();
+
+ /* test with some uids */
+ t = mail_index_transaction_new();
+
+ test_begin("mail index append with uids");
+ mail_index_append(t, 0, &seq);
+ test_assert(seq == 5);
+ mail_index_append(t, 126, &seq);
+ test_assert(seq == 6);
+ test_assert(!t->appends_nonsorted);
+ mail_index_append(t, 124, &seq);
+ test_assert(seq == 7);
+ test_assert(t->appends_nonsorted);
+ mail_index_append(t, 0, &seq);
+ test_assert(seq == 8);
+ mail_index_append(t, 128, &seq);
+ test_assert(seq == 9);
+ test_assert(t->highest_append_uid == 128);
+
+ mail_index_append_assign_uids(t, 129, &next_uid);
+ test_assert(next_uid == 131);
+
+ appends = array_get(&t->appends, &count);
+ test_assert(count == 5);
+ test_assert(appends[0].uid == 129);
+ test_assert(appends[1].uid == 126);
+ test_assert(appends[2].uid == 124);
+ test_assert(appends[3].uid == 130);
+ test_assert(appends[4].uid == 128);
+ test_end();
+}
+
+static void test_mail_index_flag_update_fastpath(void)
+{
+ struct mail_index_transaction *t;
+ const struct mail_transaction_flag_update *updates;
+ unsigned int count;
+
+ hdr.messages_count = 20;
+ t = mail_index_transaction_new();
+
+ test_begin("mail index flag update fast paths");
+
+ mail_index_update_flags_range(t, 13, 14, MODIFY_REPLACE,
+ MAIL_DELETED);
+ test_assert(t->last_update_idx == 0);
+ test_assert(array_count(&t->updates) == 1);
+
+ mail_index_update_flags_range(t, 15, 15, MODIFY_REPLACE,
+ MAIL_DELETED);
+ test_assert(t->last_update_idx == 0);
+ test_assert(array_count(&t->updates) == 1);
+
+ mail_index_update_flags_range(t, 16, 16, MODIFY_ADD,
+ MAIL_DELETED);
+ test_assert(t->last_update_idx == 1);
+ test_assert(array_count(&t->updates) == 2);
+
+ updates = array_get(&t->updates, &count);
+ test_assert(updates[0].uid1 == 13);
+ test_assert(updates[0].uid2 == 15);
+ test_assert(updates[0].add_flags == MAIL_DELETED);
+ test_assert(updates[0].remove_flags ==
+ (MAIL_ANSWERED | MAIL_FLAGGED | MAIL_SEEN | MAIL_DRAFT));
+ test_assert(updates[0].padding == 0);
+ test_assert(updates[1].uid1 == 16);
+ test_assert(updates[1].uid2 == 16);
+ test_assert(updates[1].add_flags == MAIL_DELETED);
+ test_assert(updates[1].remove_flags == 0);
+ test_assert(updates[1].padding == 0);
+ test_assert(!t->log_updates);
+ test_end();
+}
+
+static void test_mail_index_flag_update_simple_merges(void)
+{
+ struct mail_index_transaction *t;
+ const struct mail_transaction_flag_update *updates;
+ unsigned int count;
+
+ hdr.messages_count = 20;
+ t = mail_index_transaction_new();
+
+ test_begin("mail index flag update simple merges");
+
+ mail_index_update_flags_range(t, 6, 8, MODIFY_ADD,
+ MAIL_FLAGGED);
+ test_assert(t->last_update_idx == 0);
+ mail_index_update_flags_range(t, 5, 6, MODIFY_ADD,
+ MAIL_FLAGGED);
+ test_assert(t->last_update_idx == 0);
+ mail_index_update_flags_range(t, 4, 4, MODIFY_ADD,
+ MAIL_FLAGGED);
+ test_assert(t->last_update_idx == 0);
+ mail_index_update_flags_range(t, 7, 9, MODIFY_ADD,
+ MAIL_FLAGGED);
+ test_assert(t->last_update_idx == 0);
+ mail_index_update_flags_range(t, 10, 10, MODIFY_ADD,
+ MAIL_FLAGGED);
+ updates = array_get(&t->updates, &count);
+ test_assert(count == 1);
+ test_assert(updates[0].uid1 == 4);
+ test_assert(updates[0].uid2 == 10);
+ test_assert(updates[0].add_flags == MAIL_FLAGGED);
+ test_assert(updates[0].remove_flags == 0);
+
+ mail_index_update_flags_range(t, 12, 12, MODIFY_ADD,
+ MAIL_FLAGGED);
+ mail_index_update_flags_range(t, 11, 11, MODIFY_ADD,
+ MAIL_FLAGGED);
+ test_assert(count == 1);
+ test_assert(updates[0].uid1 == 4);
+ test_assert(updates[0].uid2 == 12);
+ test_end();
+}
+
+static void test_mail_index_flag_update_complex_merges(void)
+{
+ struct mail_index_transaction *t;
+ const struct mail_transaction_flag_update *updates;
+ unsigned int count;
+
+ hdr.messages_count = 20;
+ t = mail_index_transaction_new();
+
+ test_begin("mail index flag update complex merges");
+
+ mail_index_update_flags_range(t, 6, 8, MODIFY_REPLACE,
+ MAIL_SEEN);
+ mail_index_update_flags_range(t, 3, 6, MODIFY_ADD,
+ MAIL_FLAGGED);
+ mail_index_update_flags_range(t, 5, 7, MODIFY_ADD,
+ MAIL_DRAFT);
+ mail_index_update_flags_range(t, 6, 6, MODIFY_REPLACE,
+ MAIL_SEEN | MAIL_ANSWERED);
+ mail_index_update_flags_range(t, 5, 10, MODIFY_REMOVE,
+ MAIL_ANSWERED);
+ mail_index_update_flags_range(t, 7, 12, MODIFY_ADD,
+ MAIL_DELETED);
+
+ updates = array_get(&t->updates, &count);
+ test_assert(count == 7);
+ test_assert(updates[0].uid1 == 3);
+ test_assert(updates[0].uid2 == 4);
+ test_assert(updates[0].add_flags == MAIL_FLAGGED);
+ test_assert(updates[0].remove_flags == 0);
+ test_assert(updates[1].uid1 == 5);
+ test_assert(updates[1].uid2 == 5);
+ test_assert(updates[1].add_flags == (MAIL_DRAFT | MAIL_FLAGGED));
+ test_assert(updates[1].remove_flags == MAIL_ANSWERED);
+ test_assert(updates[2].uid1 == 6);
+ test_assert(updates[2].uid2 == 6);
+ test_assert(updates[2].add_flags == MAIL_SEEN);
+ test_assert(updates[2].remove_flags == (MAIL_ANSWERED | MAIL_FLAGGED | MAIL_DELETED | MAIL_DRAFT));
+ test_assert(updates[3].uid1 == 7);
+ test_assert(updates[3].uid2 == 7);
+ test_assert(updates[3].add_flags == (MAIL_SEEN | MAIL_DRAFT | MAIL_DELETED));
+ test_assert(updates[3].remove_flags == (MAIL_ANSWERED | MAIL_FLAGGED));
+ test_assert(updates[4].uid1 == 8);
+ test_assert(updates[4].uid2 == 8);
+ test_assert(updates[4].add_flags == (MAIL_SEEN | MAIL_DELETED));
+ test_assert(updates[4].remove_flags == (MAIL_ANSWERED | MAIL_FLAGGED | MAIL_DRAFT));
+ test_assert(updates[5].uid1 == 9);
+ test_assert(updates[5].uid2 == 10);
+ test_assert(updates[5].add_flags == MAIL_DELETED);
+ test_assert(updates[5].remove_flags == MAIL_ANSWERED);
+ test_assert(updates[6].uid1 == 11);
+ test_assert(updates[6].uid2 == 12);
+ test_assert(updates[6].add_flags == MAIL_DELETED);
+ test_assert(updates[6].remove_flags == 0);
+
+ test_end();
+}
+
+static bool
+flags_array_check(struct mail_index_transaction *t,
+ const enum mail_flags *flags, unsigned int msg_count)
+{
+ const struct mail_transaction_flag_update *updates;
+ unsigned int i, count, seq;
+
+ if (array_is_created(&t->updates))
+ updates = array_get(&t->updates, &count);
+ else {
+ updates = NULL;
+ count = 0;
+ }
+ for (seq = 1, i = 0; i < count; i++) {
+ if (i > 0) {
+ test_assert(updates[i-1].uid2 < updates[i].uid1);
+ test_assert(updates[i-1].uid2 + 1 != updates[i].uid1 ||
+ updates[i-1].add_flags != updates[i].add_flags ||
+ updates[i-1].remove_flags != updates[i].remove_flags);
+ }
+ for (; seq != updates[i].uid1; seq++)
+ test_assert(flags[seq] == 0);
+ for (; seq <= updates[i].uid2; seq++)
+ test_assert(flags[seq] == updates[i].add_flags);
+ }
+ for (; seq <= msg_count; seq++)
+ test_assert(flags[seq] == 0);
+ return TRUE;
+}
+
+static void test_mail_index_flag_update_random(void)
+{
+ struct mail_index_transaction *t;
+ unsigned int r, seq1, seq2, seq;
+ enum mail_flags *flags, change;
+ enum modify_type modify_type;
+
+ hdr.messages_count = 20;
+ t = mail_index_transaction_new();
+
+ test_begin("mail index flag update random");
+
+ flags = t_new(enum mail_flags, hdr.messages_count + 1);
+ for (r = 0; r < 1000; r++) {
+ change = rand() % (MAIL_FLAGS_NONRECENT+1);
+ seq1 = (rand() % hdr.messages_count) + 1;
+ seq2 = seq1 == hdr.messages_count ? seq1 :
+ (rand() % (hdr.messages_count - seq1)) + seq1;
+
+ switch (rand() % 3) {
+ case 0:
+ modify_type = MODIFY_ADD;
+ for (seq = seq1; seq <= seq2; seq++)
+ flags[seq] |= change;
+ break;
+ case 1:
+ modify_type = MODIFY_REMOVE;
+ for (seq = seq1; seq <= seq2; seq++)
+ flags[seq] &= ~change;
+ break;
+ case 2:
+ modify_type = MODIFY_REPLACE;
+ for (seq = seq1; seq <= seq2; seq++)
+ flags[seq] = change;
+ break;
+ default:
+ i_unreached();
+ }
+ mail_index_update_flags_range(t, seq1, seq2, modify_type,
+ change);
+ flags_array_check(t, flags, hdr.messages_count);
+ }
+ test_end();
+}
+
+static void test_mail_index_flag_update_appends(void)
+{
+ struct mail_index_transaction *t;
+ const struct mail_index_record *appends;
+ const struct mail_transaction_flag_update *updates;
+ unsigned int count;
+ uint32_t seq;
+
+ hdr.messages_count = 4;
+ t = mail_index_transaction_new();
+
+ test_begin("mail index flag update appends");
+ mail_index_append(t, 0, &seq);
+ test_assert(seq == 5);
+ mail_index_append(t, 0, &seq);
+ test_assert(seq == 6);
+ mail_index_append(t, 0, &seq);
+ test_assert(seq == 7);
+
+ mail_index_update_flags_range(t, 5, 6, MODIFY_REPLACE,
+ MAIL_SEEN | MAIL_FLAGGED);
+ mail_index_update_flags_range(t, 6, 7, MODIFY_ADD,
+ MAIL_DRAFT | MAIL_FLAGGED);
+ mail_index_update_flags_range(t, 5, 7, MODIFY_REMOVE,
+ MAIL_FLAGGED);
+
+ appends = array_get(&t->appends, &count);
+ test_assert(count == 3);
+ test_assert(appends[0].flags == MAIL_SEEN);
+ test_assert(appends[1].flags == (MAIL_SEEN | MAIL_DRAFT));
+ test_assert(appends[2].flags == MAIL_DRAFT);
+
+ /* mixed existing/appends */
+ mail_index_update_flags_range(t, 4, 5, MODIFY_ADD,
+ MAIL_ANSWERED);
+ test_assert(appends[0].flags == (MAIL_SEEN | MAIL_ANSWERED));
+
+ updates = array_get(&t->updates, &count);
+ test_assert(count == 1);
+ test_assert(updates[0].uid1 == 4);
+ test_assert(updates[0].uid2 == 4);
+ test_assert(updates[0].add_flags == MAIL_ANSWERED);
+ test_end();
+}
+
+static bool test_flag_update_pos(struct mail_index_transaction *t,
+ uint32_t seq, unsigned int idx)
+{
+ unsigned int i, j, count;
+
+ count = array_count(&t->updates);
+ for (i = 0; i < idx; i++) {
+ for (j = idx + 1; j <= count; j++) {
+ if (!mail_index_transaction_get_flag_update_pos(t, i, j, seq) == idx) {
+ test_assert(FALSE);
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static void test_mail_index_transaction_get_flag_update_pos(void)
+{
+ struct mail_index_transaction *t;
+
+ test_begin("mail index transaction get flag update pos");
+
+ hdr.messages_count = 10;
+ t = mail_index_transaction_new();
+ mail_index_update_flags_range(t, 1, 1, MODIFY_REPLACE, 0);
+ mail_index_update_flags_range(t, 3, 4, MODIFY_REPLACE, 0);
+ mail_index_update_flags_range(t, 6, 7, MODIFY_REPLACE, 0);
+ mail_index_update_flags_range(t, 9, 10, MODIFY_REPLACE, 0);
+
+ test_assert(test_flag_update_pos(t, 1, 0));
+ test_assert(test_flag_update_pos(t, 2, 1));
+ test_assert(test_flag_update_pos(t, 3, 1));
+ test_assert(test_flag_update_pos(t, 4, 1));
+ test_assert(test_flag_update_pos(t, 5, 2));
+ test_assert(test_flag_update_pos(t, 6, 2));
+ test_assert(test_flag_update_pos(t, 7, 2));
+ test_assert(test_flag_update_pos(t, 8, 3));
+ test_assert(test_flag_update_pos(t, 9, 3));
+ test_assert(test_flag_update_pos(t, 10, 3));
+ test_assert(test_flag_update_pos(t, 11, 4));
+ test_assert(test_flag_update_pos(t, 12, 4));
+ test_end();
+}
+
+int main(void)
+{
+ static void (*test_functions[])(void) = {
+ test_mail_index_append,
+ test_mail_index_flag_update_fastpath,
+ test_mail_index_flag_update_simple_merges,
+ test_mail_index_flag_update_complex_merges,
+ test_mail_index_flag_update_random,
+ test_mail_index_flag_update_appends,
+ test_mail_index_transaction_get_flag_update_pos,
+ NULL
+ };
+ return test_run(test_functions);
+}