summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mail-index-transaction-update.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-index/mail-index-transaction-update.c')
-rw-r--r--src/lib-index/mail-index-transaction-update.c1367
1 files changed, 1367 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-transaction-update.c b/src/lib-index/mail-index-transaction-update.c
new file mode 100644
index 0000000..c7bcbd9
--- /dev/null
+++ b/src/lib-index/mail-index-transaction-update.c
@@ -0,0 +1,1367 @@
+/* Copyright (c) 2003-2018 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 "array.h"
+#include "time-util.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) *rec;
+ struct mail_index_transaction_ext_hdr_update *ext_hdr;
+
+ if (array_is_created(&t->ext_rec_updates)) {
+ array_foreach_modifiable(&t->ext_rec_updates, rec) {
+ if (array_is_created(rec))
+ array_free(rec);
+ }
+ array_free(&t->ext_rec_updates);
+ }
+ if (array_is_created(&t->ext_rec_atomics)) {
+ array_foreach_modifiable(&t->ext_rec_atomics, rec) {
+ if (array_is_created(rec))
+ array_free(rec);
+ }
+ array_free(&t->ext_rec_atomics);
+ }
+ if (array_is_created(&t->ext_hdr_updates)) {
+ array_foreach_modifiable(&t->ext_hdr_updates, ext_hdr) {
+ i_free(ext_hdr->data);
+ i_free(ext_hdr->mask);
+ }
+ array_free(&t->ext_hdr_updates);
+ }
+
+ if (array_is_created(&t->keyword_updates)) {
+ struct mail_index_transaction_keyword_update *u;
+
+ array_foreach_modifiable(&t->keyword_updates, u) {
+ if (array_is_created(&u->add_seq))
+ array_free(&u->add_seq);
+ if (array_is_created(&u->remove_seq))
+ array_free(&u->remove_seq);
+ }
+ array_free(&t->keyword_updates);
+ }
+
+ if (array_is_created(&t->appends))
+ array_free(&t->appends);
+ if (array_is_created(&t->modseq_updates))
+ array_free(&t->modseq_updates);
+ 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);
+ buffer_free(&t->attribute_updates);
+ buffer_free(&t->attribute_updates_suffix);
+
+ 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;
+ t->min_highest_modseq = 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->expunges_nonsorted = FALSE;
+ t->drop_unnecessary_flag_updates = FALSE;
+ t->pre_hdr_changed = FALSE;
+ t->post_hdr_changed = FALSE;
+ t->reset = FALSE;
+ t->index_deleted = FALSE;
+ t->index_undeleted = FALSE;
+ t->log_updates = FALSE;
+ t->log_ext_updates = FALSE;
+ t->tail_offset_changed = 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->modseq_updates) ||
+ array_is_created(&t->expunges) ||
+ array_is_created(&t->keyword_updates) ||
+ t->attribute_updates != NULL ||
+ t->pre_hdr_changed || t->post_hdr_changed ||
+ t->min_highest_modseq != 0;
+}
+
+void mail_index_update_day_headers(struct mail_index_transaction *t,
+ time_t day_stamp)
+{
+ struct mail_index_header hdr;
+ const struct mail_index_record *rec;
+ const int max_days = N_ELEMENTS(hdr.day_first_uid);
+ time_t stamp;
+ int i, days;
+
+ hdr = *mail_index_get_header(t->view);
+ rec = array_front(&t->appends);
+
+ stamp = time_to_local_day_start(day_stamp);
+ 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]. */
+ if (days > 0 && days < max_days)
+ memmove(hdr.day_first_uid + days, hdr.day_first_uid,
+ (max_days - days) * sizeof(hdr.day_first_uid[0]));
+ 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_finish_uids(struct mail_index_transaction *t,
+ uint32_t first_uid,
+ ARRAY_TYPE(seq_range) *uids_r)
+{
+ return mail_index_append_finish_uids_full(t, first_uid, first_uid, uids_r);
+}
+
+void mail_index_append_finish_uids_full(struct mail_index_transaction *t,
+ uint32_t min_allowed_uid,
+ uint32_t first_new_uid,
+ ARRAY_TYPE(seq_range) *uids_r)
+{
+ struct mail_index_record *recs;
+ unsigned int i, count;
+ struct seq_range *range;
+ uint32_t next_uid;
+
+ if (!array_is_created(&t->appends))
+ return;
+
+ i_assert(min_allowed_uid <= first_new_uid);
+ i_assert(first_new_uid < (uint32_t)-1);
+
+ /* first find the highest assigned uid */
+ recs = array_get_modifiable(&t->appends, &count);
+ i_assert(count > 0);
+
+ next_uid = first_new_uid;
+ for (i = 0; i < count; i++) {
+ if (next_uid <= recs[i].uid)
+ next_uid = recs[i].uid + 1;
+ }
+ i_assert(next_uid > 0 && next_uid < (uint32_t)-1);
+
+ /* assign missing uids */
+ for (i = 0; i < count; i++) {
+ if (recs[i].uid == 0 || recs[i].uid < min_allowed_uid) {
+ i_assert(next_uid < (uint32_t)-1);
+ recs[i].uid = next_uid++;
+ if (t->highest_append_uid < recs[i].uid)
+ t->highest_append_uid = recs[i].uid;
+ } else {
+ t->appends_nonsorted = TRUE;
+ }
+ }
+
+ /* write the saved uids range */
+ array_clear(uids_r);
+ range = array_append_space(uids_r);
+ range->seq1 = range->seq2 = recs[0].uid;
+ for (i = 1; i < count; i++) {
+ if (range->seq2 + 1 == recs[i].uid)
+ range->seq2++;
+ else {
+ range = array_append_space(uids_r);
+ range->seq1 = range->seq2 = recs[i].uid;
+ }
+ }
+}
+
+void mail_index_update_modseq(struct mail_index_transaction *t, uint32_t seq,
+ uint64_t min_modseq)
+{
+ struct mail_transaction_modseq_update *u;
+
+ /* modseq=1 is the minimum always and it's only for mails that were
+ created/modified before modseqs were enabled. */
+ if (min_modseq <= 1)
+ return;
+
+ if (!array_is_created(&t->modseq_updates))
+ i_array_init(&t->modseq_updates, 32);
+
+ u = array_append_space(&t->modseq_updates);
+ u->uid = seq;
+ u->modseq_low32 = min_modseq & 0xffffffff;
+ u->modseq_high32 = min_modseq >> 32;
+
+ t->log_updates = TRUE;
+}
+
+void mail_index_update_highest_modseq(struct mail_index_transaction *t,
+ uint64_t min_modseq)
+{
+ /* modseq=1 is the minimum always and it's only for mails that were
+ created/modified before modseqs were enabled. */
+ if (min_modseq <= 1)
+ return;
+
+ if (t->min_highest_modseq < min_modseq)
+ t->min_highest_modseq = min_modseq;
+
+ t->log_updates = TRUE;
+}
+
+static void
+mail_index_revert_ext(ARRAY_TYPE(seq_array_array) *ext_updates,
+ uint32_t seq)
+{
+ ARRAY_TYPE(seq_array) *seqs;
+ unsigned int idx;
+
+ if (!array_is_created(ext_updates))
+ return;
+
+ array_foreach_modifiable(ext_updates, seqs) {
+ if (array_is_created(seqs) &&
+ mail_index_seq_array_lookup(seqs, seq, &idx))
+ array_delete(seqs, idx, 1);
+ }
+}
+
+static void
+mail_index_revert_changes_common(struct mail_index_transaction *t, uint32_t seq)
+{
+ struct mail_index_transaction_keyword_update *kw_update;
+ unsigned int i;
+
+ /* remove extension updates */
+ mail_index_revert_ext(&t->ext_rec_updates, seq);
+ mail_index_revert_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_updates)) {
+ array_foreach_modifiable(&t->keyword_updates, kw_update) {
+ if (array_is_created(&kw_update->add_seq)) {
+ seq_range_array_remove(&kw_update->add_seq,
+ seq);
+ }
+ if (array_is_created(&kw_update->remove_seq)) {
+ seq_range_array_remove(&kw_update->remove_seq,
+ seq);
+ }
+ }
+ }
+ /* remove modseqs */
+ if (array_is_created(&t->modseq_updates) &&
+ mail_index_seq_array_lookup((void *)&t->modseq_updates, seq, &i))
+ array_delete(&t->modseq_updates, i, 1);
+}
+
+void mail_index_revert_changes(struct mail_index_transaction *t, uint32_t seq)
+{
+ mail_index_revert_changes_common(t, seq);
+ (void)mail_index_cancel_flag_updates(t, seq);
+}
+
+static void
+mail_index_expunge_last_append(struct mail_index_transaction *t, uint32_t seq)
+{
+ i_assert(seq == t->last_new_seq);
+
+ mail_index_revert_changes_common(t, 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)
+{
+ static guid_128_t null_guid =
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ mail_index_expunge_guid(t, seq, null_guid);
+}
+
+void mail_index_expunge_guid(struct mail_index_transaction *t, uint32_t seq,
+ const guid_128_t guid_128)
+{
+ const struct mail_transaction_expunge_guid *expunges;
+ struct mail_transaction_expunge_guid *expunge;
+ unsigned int count;
+
+ 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;
+
+ /* ignore duplicates here. drop them when committing. */
+ if (!array_is_created(&t->expunges))
+ i_array_init(&t->expunges, 64);
+ else if (!t->expunges_nonsorted) {
+ /* usually expunges are added in increasing order. */
+ expunges = array_get(&t->expunges, &count);
+ if (count > 0 && seq < expunges[count-1].uid)
+ t->expunges_nonsorted = TRUE;
+ }
+ expunge = array_append_space(&t->expunges);
+ expunge->uid = seq;
+ memcpy(expunge->guid_128, guid_128, sizeof(expunge->guid_128));
+ }
+}
+
+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_index_flag_update *updates;
+ unsigned int idx, count;
+
+ updates = array_get(&t->updates, &count);
+ i_assert(left_idx <= right_idx && right_idx <= count);
+ i_assert(count < INT_MAX);
+
+ /* 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_index_flag_update u,
+ unsigned int idx)
+{
+ struct mail_index_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) &
+ ENUM_NEGATE(u.remove_flags);
+ updates[idx].remove_flags =
+ (updates[idx].remove_flags | u.remove_flags) &
+ ENUM_NEGATE(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 = count == 0 ? 0 : I_MIN(t->last_update_idx + 1, count-1);
+ 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 &= ENUM_NEGATE(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_index_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;
+
+ i_zero(&u);
+ u.uid1 = seq1;
+ u.uid2 = seq2;
+
+ switch (modify_type) {
+ case MODIFY_REPLACE:
+ u.add_flags = flags;
+ u.remove_flags = ENUM_NEGATE(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_push_back(&t->updates, &u);
+ 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_push_back(&t->updates, &u);
+ 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);
+}
+
+static void
+mail_index_attribute_set_full(struct mail_index_transaction *t,
+ const char *key, bool pvt, char prefix,
+ time_t timestamp, uint32_t value_len)
+{
+ uint32_t ts = timestamp;
+
+ if (t->attribute_updates == NULL) {
+ t->attribute_updates = buffer_create_dynamic(default_pool, 64);
+ t->attribute_updates_suffix = buffer_create_dynamic(default_pool, 64);
+ }
+ buffer_append_c(t->attribute_updates, prefix);
+ buffer_append_c(t->attribute_updates, pvt ? 'p' : 's');
+ buffer_append(t->attribute_updates, key, strlen(key)+1);
+
+ buffer_append(t->attribute_updates_suffix, &ts, sizeof(ts));
+ if (prefix == '+') {
+ buffer_append(t->attribute_updates_suffix,
+ &value_len, sizeof(value_len));
+ }
+ t->log_updates = TRUE;
+}
+
+void mail_index_attribute_set(struct mail_index_transaction *t,
+ bool pvt, const char *key,
+ time_t timestamp, uint32_t value_len)
+{
+ mail_index_attribute_set_full(t, key, pvt, '+', timestamp, value_len);
+}
+
+void mail_index_attribute_unset(struct mail_index_transaction *t,
+ bool pvt, const char *key,
+ time_t timestamp)
+{
+ mail_index_attribute_set_full(t, key, pvt, '-', timestamp, 0);
+}
+
+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;
+ }
+}
+
+static void
+mail_index_ext_rec_updates_resize(struct mail_index_transaction *t,
+ uint32_t ext_id, uint16_t new_record_size)
+{
+ ARRAY_TYPE(seq_array) *array, old_array;
+ unsigned int i;
+
+ if (!array_is_created(&t->ext_rec_updates))
+ return;
+ array = array_idx_modifiable(&t->ext_rec_updates, ext_id);
+ if (!array_is_created(array))
+ return;
+
+ old_array = *array;
+ i_zero(array);
+ mail_index_seq_array_alloc(array, new_record_size);
+
+ /* copy the records' beginnings. leave the end zero-filled. */
+ for (i = 0; i < array_count(&old_array); i++) {
+ const void *old_record = array_idx(&old_array, i);
+
+ memcpy(array_append_space(array), old_record,
+ old_array.arr.element_size);
+ }
+ array_free(&old_array);
+}
+
+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)
+{
+ const struct mail_index_registered_ext *rext;
+ const struct mail_transaction_ext_intro *resizes;
+ unsigned int resizes_count;
+ struct mail_transaction_ext_intro intro;
+ uint32_t old_record_size = 0, old_record_align, old_header_size;
+
+ i_zero(&intro);
+ rext = array_idx(&t->view->index->extensions, ext_id);
+
+ /* 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 */
+ intro.ext_id = (uint32_t)-1;
+ old_record_align = rext->record_align;
+ old_header_size = rext->hdr_size;
+ } else {
+ const struct mail_index_ext *ext;
+
+ ext = array_idx(&t->view->map->extensions, intro.ext_id);
+ old_record_align = ext->record_align;
+ old_header_size = ext->hdr_size;
+ }
+
+ /* get the record size. if there are any existing record updates,
+ they're using the registered size, not the map's existing
+ record_size. */
+ if (array_is_created(&t->ext_resizes))
+ resizes = array_get(&t->ext_resizes, &resizes_count);
+ else {
+ resizes = NULL;
+ resizes_count = 0;
+ }
+ if (ext_id < resizes_count && resizes[ext_id].name_size != 0) {
+ /* already resized once. use the resized value. */
+ old_record_size = resizes[ext_id].record_size;
+ } else {
+ /* use the registered values. */
+ old_record_size = rext->record_size;
+ }
+
+ if (record_size != old_record_size && record_size != (uint16_t)-1) {
+ /* if record_size grows, we'll just resize the existing
+ ext_rec_updates array. it's not possible to shrink
+ record_size without data loss. */
+ i_assert(record_size > old_record_size);
+ mail_index_ext_rec_updates_resize(t, ext_id, record_size);
+ }
+
+ 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 != (uint32_t)-1 ? hdr_size : old_header_size;
+ if (record_size != (uint16_t)-1) {
+ i_assert(record_align != (uint16_t)-1);
+ intro.record_size = record_size;
+ intro.record_align = record_align;
+ } else {
+ i_assert(record_align == (uint16_t)-1);
+ intro.record_size = old_record_size;
+ intro.record_align = old_record_align;
+ }
+ intro.name_size = 1;
+ array_idx_set(&t->ext_resizes, ext_id, &intro);
+}
+
+void mail_index_ext_resize_hdr(struct mail_index_transaction *t,
+ uint32_t ext_id, uint32_t hdr_size)
+{
+ mail_index_ext_resize(t, ext_id, hdr_size, (uint16_t)-1, (uint16_t)-1);
+}
+
+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);
+
+ i_zero(&reset);
+ reset.new_reset_id = reset_id;
+ reset.preserve_data = clear_data ? 0 : 1;
+
+ 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;
+
+ if (array_is_created(arr)) {
+ array_foreach(arr, array) {
+ if (array_is_created(array))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool
+mail_index_transaction_has_ext_changes(struct mail_index_transaction *t)
+{
+ 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;
+
+ array_foreach(&t->ext_hdr_updates, hdr) {
+ if (hdr->alloc_size > 0)
+ return TRUE;
+ }
+ }
+ if (array_is_created(&t->ext_resets)) {
+ const struct mail_transaction_ext_reset *reset;
+
+ array_foreach(&t->ext_resets, reset) {
+ if (reset->new_reset_id != 0)
+ return TRUE;
+ }
+ }
+ if (array_is_created(&t->ext_resizes)) {
+ const struct mail_transaction_ext_intro *resize;
+
+ array_foreach(&t->ext_resizes, resize) {
+ if (resize->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);
+ }
+}
+
+static void
+mail_index_ext_reset_changes(struct mail_index_transaction *t, uint32_t ext_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);
+ }
+
+ t->log_ext_updates = mail_index_transaction_has_ext_changes(t);
+}
+
+void mail_index_ext_using_reset_id(struct mail_index_transaction *t,
+ uint32_t ext_id, uint32_t reset_id)
+{
+ uint32_t *reset_id_p;
+ bool changed;
+
+ if (!array_is_created(&t->ext_reset_ids))
+ i_array_init(&t->ext_reset_ids, ext_id + 2);
+ reset_id_p = array_idx_get_space(&t->ext_reset_ids, ext_id);
+ changed = *reset_id_p != reset_id && *reset_id_p != 0;
+ *reset_id_p = reset_id;
+ if (changed) {
+ /* reset_id changed, clear existing changes */
+ mail_index_ext_reset_changes(t, ext_id);
+ }
+}
+
+void mail_index_ext_set_reset_id(struct mail_index_transaction *t,
+ uint32_t ext_id, uint32_t reset_id)
+{
+ mail_index_ext_using_reset_id(t, ext_id, reset_id);
+ /* make sure the changes get reset, even if reset_id doesn't change */
+ mail_index_ext_reset_changes(t, ext_id);
+}
+
+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 <= (uint32_t)-1 && size <= (uint32_t)-1 &&
+ offset + size <= (uint32_t)-1);
+
+ if (!array_is_created(&t->ext_hdr_updates))
+ i_array_init(&t->ext_hdr_updates, ext_id + 2);
+
+ hdr = array_idx_get_space(&t->ext_hdr_updates, ext_id);
+ if (hdr->alloc_size < offset || hdr->alloc_size - offset < size) {
+ i_assert(size < SIZE_MAX - 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;
+ }
+ i_assert(record_size > 0);
+
+ if (!array_is_created(&t->ext_rec_updates))
+ i_array_init(&t->ext_rec_updates, ext_id + 2);
+ array = array_idx_get_space(&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: we need this now and it doesn't
+ actually seem to be a real problem at least right now - why? */
+ /*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_get_space(&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;
+ (void)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)
+{
+ 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_transaction_lookup_latest_keywords(t, 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++) {
+ 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;
+}
+
+static struct mail_keywords *
+keyword_update_remove_existing(struct mail_index_transaction *t, uint32_t seq)
+{
+ ARRAY_TYPE(keyword_indexes) keywords;
+ uint32_t i, keywords_count;
+
+ t_array_init(&keywords, 32);
+ if (t->view->v.lookup_full == NULL) {
+ /* syncing is saving a list of changes into this transaction.
+ the seq is actual an uid, so we can't lookup the existing
+ keywords. we shouldn't get here unless we're reading
+ pre-v2.2 keyword-reset records from .log files. so we don't
+ really care about performance that much here, */
+ keywords_count = array_count(&t->view->index->keywords);
+ for (i = 0; i < keywords_count; i++)
+ array_push_back(&keywords, &i);
+ } else {
+ mail_index_transaction_lookup_latest_keywords(t, seq, &keywords);
+ }
+ if (array_count(&keywords) == 0)
+ return NULL;
+ return mail_index_keywords_create_from_indexes(t->view->index,
+ &keywords);
+}
+
+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;
+ struct mail_keywords *add_keywords = NULL, *remove_keywords = NULL;
+ struct mail_keywords *unref_keywords = NULL;
+ unsigned int i;
+ bool changed;
+
+ i_assert(seq > 0 &&
+ (seq <= mail_index_view_get_messages_count(t->view) ||
+ seq <= t->last_new_seq));
+ i_assert(keywords->index == t->view->index);
+
+ if (keywords->count == 0 && modify_type != MODIFY_REPLACE)
+ return;
+
+ update_minmax_flagupdate_seq(t, seq, seq);
+
+ if (!array_is_created(&t->keyword_updates)) {
+ uint32_t max_idx = keywords->count == 0 ? 3 :
+ 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;
+ }
+
+ switch (modify_type) {
+ case MODIFY_REPLACE:
+ /* split this into add+remove. remove all existing keywords not
+ included in the keywords list */
+ if (seq < t->first_new_seq) {
+ /* remove the ones currently in index */
+ remove_keywords = keyword_update_remove_existing(t, seq);
+ unref_keywords = remove_keywords;
+ }
+ /* remove from all changes we've done in this transaction */
+ array_foreach_modifiable(&t->keyword_updates, u)
+ seq_range_array_remove(&u->add_seq, seq);
+ add_keywords = keywords;
+ break;
+ case MODIFY_ADD:
+ add_keywords = keywords;
+ break;
+ case MODIFY_REMOVE:
+ remove_keywords = keywords;
+ break;
+ }
+
+ /* Update add_seq and remove_seq arrays which describe the keyword
+ changes. First do the removes, since replace removes everything
+ first. */
+ if (remove_keywords != NULL) {
+ for (i = 0; i < remove_keywords->count; i++) {
+ u = array_idx_get_space(&t->keyword_updates,
+ remove_keywords->idx[i]);
+ seq_range_array_remove(&u->add_seq, seq);
+ /* Don't bother updating remove_seq for new messages,
+ since their initial state is "no keyword" anyway */
+ if (seq < t->first_new_seq) {
+ seq_range_array_add_with_init(&u->remove_seq,
+ 16, seq);
+ }
+ }
+ }
+ if (add_keywords != NULL) {
+ for (i = 0; i < add_keywords->count; i++) {
+ u = array_idx_get_space(&t->keyword_updates,
+ add_keywords->idx[i]);
+ seq_range_array_add_with_init(&u->add_seq, 16, seq);
+ seq_range_array_remove(&u->remove_seq, seq);
+ }
+ }
+ if (unref_keywords != NULL)
+ mail_index_keywords_unref(&unref_keywords);
+
+ t->log_updates = TRUE;
+}
+
+bool mail_index_cancel_flag_updates(struct mail_index_transaction *t,
+ uint32_t seq)
+{
+ struct mail_index_flag_update *updates, tmp_update;
+ unsigned int i, count;
+
+ if (!array_is_created(&t->updates))
+ return FALSE;
+
+ updates = array_get_modifiable(&t->updates, &count);
+ i = mail_index_transaction_get_flag_update_pos(t, 0, count, seq);
+ if (i == count)
+ return FALSE;
+ else {
+ i_assert(seq <= updates[i].uid2);
+ if (seq < updates[i].uid1)
+ return FALSE;
+ }
+
+ /* exists */
+ if (updates[i].uid1 == seq) {
+ if (updates[i].uid2 != seq)
+ updates[i].uid1++;
+ else if (count > 1)
+ array_delete(&t->updates, i, 1);
+ else
+ array_free(&t->updates);
+ } else if (updates[i].uid2 == seq) {
+ updates[i].uid2--;
+ } else {
+ /* need to split it in two */
+ tmp_update = updates[i];
+ tmp_update.uid1 = seq+1;
+ updates[i].uid2 = seq-1;
+ array_insert(&t->updates, i + 1, &tmp_update, 1);
+ }
+ return TRUE;
+}
+
+static bool mail_index_cancel_array(ARRAY_TYPE(seq_range) *array, uint32_t seq)
+{
+ if (array_is_created(array)) {
+ if (seq_range_array_remove(array, seq)) {
+ if (array_count(array) == 0)
+ array_free(array);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool mail_index_cancel_keyword_updates(struct mail_index_transaction *t,
+ uint32_t seq)
+{
+ struct mail_index_transaction_keyword_update *kw;
+ bool ret = FALSE, have_kw_changes = FALSE;
+
+ if (!array_is_created(&t->keyword_updates))
+ return FALSE;
+
+ array_foreach_modifiable(&t->keyword_updates, kw) {
+ if (mail_index_cancel_array(&kw->add_seq, seq))
+ ret = TRUE;
+ if (mail_index_cancel_array(&kw->remove_seq, seq))
+ ret = TRUE;
+ if (array_is_created(&kw->add_seq) ||
+ array_is_created(&kw->remove_seq))
+ have_kw_changes = TRUE;
+ }
+ if (!have_kw_changes)
+ array_free(&t->keyword_updates);
+ return ret;
+}
+
+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_unset_fscked(struct mail_index_transaction *t)
+{
+ struct mail_index_header new_hdr =
+ *mail_index_get_header(t->view);
+
+ i_assert(t->view->index->log_sync_locked);
+
+ /* remove fsck'd-flag if it exists. */
+ if ((new_hdr.flags & MAIL_INDEX_HDR_FLAG_FSCKD) != 0) {
+ new_hdr.flags &= ENUM_NEGATE(MAIL_INDEX_HDR_FLAG_FSCKD);
+ mail_index_update_header(t,
+ offsetof(struct mail_index_header, flags),
+ &new_hdr.flags, sizeof(new_hdr.flags), FALSE);
+ }
+}
+
+void mail_index_set_deleted(struct mail_index_transaction *t)
+{
+ i_assert(!t->index_undeleted);
+
+ t->index_deleted = TRUE;
+}
+
+void mail_index_set_undeleted(struct mail_index_transaction *t)
+{
+ i_assert(!t->index_deleted);
+
+ t->index_undeleted = 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;
+}