]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Left out from initial CONDSTORE commit.
authorTimo Sirainen <tss@iki.fi>
Sat, 15 Mar 2008 13:23:35 +0000 (15:23 +0200)
committerTimo Sirainen <tss@iki.fi>
Sat, 15 Mar 2008 13:23:35 +0000 (15:23 +0200)
--HG--
branch : HEAD

src/lib-index/mail-index-modseq.c [new file with mode: 0644]
src/lib-index/mail-index-modseq.h [new file with mode: 0644]

diff --git a/src/lib-index/mail-index-modseq.c b/src/lib-index/mail-index-modseq.c
new file mode 100644 (file)
index 0000000..9f6cc0d
--- /dev/null
@@ -0,0 +1,610 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-index-private.h"
+#include "mail-index-modseq.h"
+#include "mail-index-sync-private.h"
+
+#define MAIL_INDEX_MODSEQ_EXT_NAME "modseq"
+
+ARRAY_DEFINE_TYPE(modseqs, uint64_t);
+
+enum modseq_metadata_idx {
+       /* must be in the same order as enum mail_flags */
+       METADATA_MODSEQ_IDX_ANSWERED = 0,
+       METADATA_MODSEQ_IDX_FLAGGED,
+       METADATA_MODSEQ_IDX_DELETED,
+       METADATA_MODSEQ_IDX_SEEN,
+       METADATA_MODSEQ_IDX_DRAFT,
+
+       METADATA_MODSEQ_IDX_KEYWORD_START
+};
+
+struct mail_index_modseq_header {
+       /* highest used modseq */
+       uint64_t highest_modseq;
+       /* last tracked log file position */
+       uint32_t log_seq;
+       uint32_t log_offset;
+};
+
+struct metadata_modseqs {
+       ARRAY_TYPE(modseqs) modseqs;
+};
+
+struct mail_index_map_modseq {
+       /* indexes use enum modseq_metadata_idx */
+       ARRAY_DEFINE(metadata_modseqs, struct metadata_modseqs);
+};
+
+struct mail_index_modseq_sync {
+       struct mail_index_sync_map_ctx *sync_map_ctx;
+       struct mail_index_view *view;
+       struct mail_transaction_log_view *log_view;
+       struct mail_index_map_modseq *mmap;
+
+       uint64_t highest_modseq;
+       uint32_t log_seq;
+       uoff_t log_offset;
+};
+
+void mail_index_modseq_init(struct mail_index *index)
+{
+       index->modseq_ext_id =
+               mail_index_ext_register(index, MAIL_INDEX_MODSEQ_EXT_NAME,
+                                       sizeof(struct mail_index_modseq_header),
+                                       sizeof(uint64_t), sizeof(uint64_t));
+}
+
+static uint64_t mail_index_modseq_get_head(struct mail_index_map *map)
+{
+       return map->hdr.log_file_head_offset |
+               ((uint64_t)(map->hdr.indexid + map->hdr.log_file_seq) << 32);
+}
+
+void mail_index_modseq_enable(struct mail_index *index)
+{
+       struct mail_index_transaction *trans;
+       struct mail_index_view *view;
+       struct mail_index_modseq_header hdr;
+       uint32_t ext_map_idx, log_seq;
+       uoff_t log_offset;
+
+       if (index->modseqs_enabled)
+               return;
+
+       if (!mail_index_map_get_ext_idx(index->map, index->modseq_ext_id,
+                                       &ext_map_idx)) {
+               /* modseqs not enabled to the index yet, add them. */
+               view = mail_index_view_open(index);
+               trans = mail_index_transaction_begin(view, 0);
+
+               memset(&hdr, 0, sizeof(hdr));
+               hdr.highest_modseq = mail_index_modseq_get_head(index->map);
+               mail_index_update_header_ext(trans, index->modseq_ext_id,
+                                            0, &hdr, sizeof(hdr));
+
+               /* commit also refreshes the index, which syncs the modseqs */
+               (void)mail_index_transaction_commit(&trans,
+                                                   &log_seq, &log_offset);
+               mail_index_view_close(&view);
+
+               /* get the modseq extension to index map */
+               if (!mail_index_map_get_ext_idx(index->map,
+                                               index->modseq_ext_id,
+                                               &ext_map_idx)) {
+                       /* didn't work for some reason */
+                       return;
+               }
+       }
+       index->modseqs_enabled = TRUE;
+}
+
+uint64_t mail_index_modseq_get_highest(struct mail_index_view *view)
+{
+       const struct mail_index_modseq_header *modseq_hdr;
+       const void *data;
+       size_t size;
+
+       mail_index_get_header_ext(view, view->index->modseq_ext_id,
+                                 &data, &size);
+       if (size == sizeof(*modseq_hdr)) {
+               modseq_hdr = data;
+               if (modseq_hdr->highest_modseq != 0)
+                       return modseq_hdr->highest_modseq;
+       }
+       /* fallback to returning the log head */
+       return mail_index_modseq_get_head(view->map);
+}
+
+static struct mail_index_map_modseq *
+mail_index_map_modseq(struct mail_index_view *view)
+{
+       struct mail_index_map_modseq *mmap = view->map->rec_map->modseq;
+       uint32_t ext_map_idx;
+
+       if (mmap != NULL)
+               return mmap;
+
+       /* don't start tracking until we've seen modseq extension intro */
+       if (!mail_index_map_get_ext_idx(view->map, view->index->modseq_ext_id,
+                                       &ext_map_idx))
+               return NULL;
+
+       mmap = i_new(struct mail_index_map_modseq, 1);
+       i_array_init(&mmap->metadata_modseqs,
+                    METADATA_MODSEQ_IDX_KEYWORD_START +
+                    array_count(&view->index->keywords));
+       view->map->rec_map->modseq = mmap;
+       return mmap;
+}
+
+uint64_t mail_index_modseq_lookup(struct mail_index_view *view, uint32_t seq)
+{
+       struct mail_index_map_modseq *mmap = mail_index_map_modseq(view);
+       struct mail_index_map *map;
+       const struct mail_index_ext *ext;
+       const struct mail_index_record *rec;
+       const uint64_t *modseqp;
+       uint32_t ext_map_idx;
+
+       if (mmap == NULL)
+               return mail_index_modseq_get_head(view->map);
+
+       rec = mail_index_lookup_full(view, seq, &map);
+       if (!mail_index_map_get_ext_idx(map, view->index->modseq_ext_id,
+                                       &ext_map_idx)) {
+               /* not enabled yet */
+               return mail_index_modseq_get_head(view->map);
+       }
+
+       ext = array_idx(&map->extensions, ext_map_idx);
+       modseqp = CONST_PTR_OFFSET(rec, ext->record_offset);
+       if (*modseqp == 0) {
+               /* If we're here because we just enabled modseqs, we'll return
+                  the same modseq (initial highestmodseq) for all messages.
+                  The next sync will change these zeros to initial
+                  highestmodseq or higher.
+
+                  If we're here because a message got appended but modseq
+                  wasn't set (older Dovecot?), we'll again use the current
+                  highest modseq. This isn't exactly correct, but it gets
+                  fixed after the next sync and this situation shouldn't
+                  normally happen anyway. */
+               return mail_index_modseq_get_highest(view);
+       }
+       return *modseqp;
+}
+
+static uint64_t
+modseq_idx_lookup(struct mail_index_map_modseq *mmap,
+                 unsigned int idx, uint32_t seq)
+{
+       const struct metadata_modseqs *metadata;
+       const uint64_t *modseqs;
+       unsigned int count;
+
+       metadata = array_get(&mmap->metadata_modseqs, &count);
+       if (idx >= count || !array_is_created(&metadata[idx].modseqs))
+               return 0;
+
+       modseqs = array_get(&metadata[idx].modseqs, &count);
+       return seq > count ? 0 : modseqs[seq-1];
+}
+
+uint64_t mail_index_modseq_lookup_flags(struct mail_index_view *view,
+                                       enum mail_flags flags_mask,
+                                       uint32_t seq)
+{
+       struct mail_index_map_modseq *mmap = mail_index_map_modseq(view);
+       unsigned int i;
+       uint64_t modseq, highest_modseq = 0;
+
+       if (mmap != NULL) {
+               /* first try to find a specific match */
+               for (i = 0; i < METADATA_MODSEQ_IDX_KEYWORD_START; i++) {
+                       if ((flags_mask & (1 << i)) != 0) {
+                               modseq = modseq_idx_lookup(mmap, i, seq);
+                               if (highest_modseq < modseq)
+                                       highest_modseq = modseq;
+                       }
+               }
+       }
+
+       if (highest_modseq == 0) {
+               /* no specific matches, fallback to using the highest */
+               highest_modseq = mail_index_modseq_lookup(view, seq);
+       }
+       return highest_modseq;
+}
+
+uint64_t mail_index_modseq_lookup_keywords(struct mail_index_view *view,
+                                          const struct mail_keywords *keywords,
+                                          uint32_t seq)
+{
+       struct mail_index_map_modseq *mmap = mail_index_map_modseq(view);
+       unsigned int i, metadata_idx;
+       uint64_t modseq, highest_modseq = 0;
+
+       if (mmap != NULL) {
+               /* first try to find a specific match */
+               for (i = 0; i < keywords->count; i++) {
+                       metadata_idx = METADATA_MODSEQ_IDX_KEYWORD_START +
+                               keywords->idx[i];
+
+                       modseq = modseq_idx_lookup(mmap, metadata_idx, seq);
+                       if (highest_modseq < modseq)
+                               highest_modseq = modseq;
+               }
+       }
+
+       if (highest_modseq == 0) {
+               /* no specific matches, fallback to using the highest */
+               highest_modseq = mail_index_modseq_lookup(view, seq);
+       }
+       return highest_modseq;
+}
+
+static uint64_t get_cur_modseq(struct mail_index_modseq_sync *ctx)
+{
+       mail_transaction_log_view_get_prev_pos(ctx->log_view,
+                                              &ctx->log_seq, &ctx->log_offset);
+       i_assert(ctx->log_offset <= (uint32_t)-1);
+
+       return ctx->log_offset |
+               ((uint64_t)(ctx->view->map->hdr.indexid + ctx->log_seq) << 32);
+}
+
+static void
+mail_index_modseq_update(struct mail_index_modseq_sync *ctx,
+                        uint64_t modseq, bool nonzeros,
+                        uint32_t seq1, uint32_t seq2)
+{
+       const struct mail_index_ext *ext;
+       const struct mail_index_record *rec;
+       uint32_t ext_map_idx;
+       uint64_t *modseqp;
+
+       if (!mail_index_map_get_ext_idx(ctx->view->map,
+                                       ctx->view->index->modseq_ext_id,
+                                       &ext_map_idx))
+               return;
+
+       if (modseq > ctx->highest_modseq)
+               ctx->highest_modseq = modseq;
+
+       ext = array_idx(&ctx->view->map->extensions, ext_map_idx);
+       for (; seq1 <= seq2; seq1++) {
+               rec = MAIL_INDEX_MAP_IDX(ctx->view->map, seq1-1);
+               modseqp = PTR_OFFSET(rec, ext->record_offset);
+               if (*modseqp == 0 || (nonzeros && *modseqp < modseq))
+                       *modseqp = modseq;
+       }
+}
+
+static bool
+mail_index_modseq_update_highest(struct mail_index_modseq_sync *ctx,
+                                uint32_t seq1, uint32_t seq2)
+{
+       if (ctx->mmap == NULL)
+               return FALSE;
+
+       mail_index_modseq_update(ctx, get_cur_modseq(ctx), TRUE, seq1, seq2);
+       return TRUE;
+}
+
+static void
+mail_index_modseq_update_old_rec(struct mail_index_modseq_sync *ctx,
+                                const struct mail_transaction_header *thdr,
+                                const void *tdata)
+{
+       ARRAY_TYPE(seq_range) uids = ARRAY_INIT;
+       const struct seq_range *rec;
+       buffer_t *uid_buf;
+       unsigned int i, count;
+       uint32_t seq1, seq2;
+
+       switch (thdr->type & MAIL_TRANSACTION_TYPE_MASK) {
+       case MAIL_TRANSACTION_APPEND: {
+               const struct mail_index_record *appends = tdata;
+
+               count = thdr->size / sizeof(*appends);
+               for (i = 0; i < count; i++) {
+                       if (mail_index_lookup_seq(ctx->view,
+                                                 appends[i].uid, &seq1)) {
+                               mail_index_modseq_update_highest(ctx, seq1,
+                                                                seq1);
+                       }
+               }
+               return;
+       }
+       case MAIL_TRANSACTION_FLAG_UPDATE: {
+               uid_buf = buffer_create_const_data(pool_datastack_create(),
+                                                  tdata, thdr->size);
+               array_create_from_buffer(&uids, uid_buf,
+                       sizeof(struct mail_transaction_flag_update));
+               break;
+       }
+       case MAIL_TRANSACTION_KEYWORD_UPDATE: {
+               const struct mail_transaction_keyword_update *rec = tdata;
+               unsigned int seqset_offset;
+
+               seqset_offset = sizeof(*rec) + rec->name_size;
+               if ((seqset_offset % 4) != 0)
+                       seqset_offset += 4 - (seqset_offset % 4);
+
+               uid_buf = buffer_create_const_data(pool_datastack_create(),
+                                       CONST_PTR_OFFSET(tdata, seqset_offset),
+                                       thdr->size - seqset_offset);
+               array_create_from_buffer(&uids, uid_buf, sizeof(uint32_t)*2);
+               break;
+       }
+       case MAIL_TRANSACTION_KEYWORD_RESET:
+               uid_buf = buffer_create_const_data(pool_datastack_create(),
+                                                  tdata, thdr->size);
+               array_create_from_buffer(&uids, uid_buf,
+                       sizeof(struct mail_transaction_keyword_reset));
+               break;
+       default:
+               return;
+       }
+
+       /* update modseqs */
+       count = array_count(&uids);
+       for (i = 0; i < count; i++) {
+               rec = array_idx(&uids, i);
+               if (mail_index_lookup_seq_range(ctx->view, rec->seq1, rec->seq2,
+                                               &seq1, &seq2))
+                       mail_index_modseq_update_highest(ctx, seq1, seq2);
+       }
+}
+
+static void mail_index_modseq_sync_init(struct mail_index_modseq_sync *ctx)
+{
+       struct mail_index_map *map = ctx->view->map;
+       const struct mail_index_ext *ext;
+       const struct mail_index_modseq_header *hdr;
+       const struct mail_transaction_header *thdr;
+       const void *tdata;
+       uint32_t ext_map_idx;
+       uint32_t end_seq;
+       uoff_t end_offset;
+       uint64_t cur_modseq;
+       bool reset;
+       int ret;
+
+       if (!mail_index_map_get_ext_idx(map, ctx->view->index->modseq_ext_id,
+                                       &ext_map_idx))
+               i_unreached();
+       ext = array_idx(&map->extensions, ext_map_idx);
+
+       /* get the current highest_modseq. don't change any modseq below it. */
+       hdr = CONST_PTR_OFFSET(map->hdr_base, ext->hdr_offset);
+       ctx->highest_modseq = hdr->highest_modseq;
+
+       /* Scan logs for updates between ext_hdr.log_* .. view position.
+          There are two reasons why there could be any:
+
+          1) We just enabled modseqs and we're filling the initial values.
+          2) A non-modseq-aware Dovecot version added new messages and wrote
+             dovecot.index file. */
+       mail_transaction_log_view_get_prev_pos(ctx->view->log_view,
+                                              &end_seq, &end_offset);
+       if (end_seq <= hdr->log_seq ||
+           (end_seq == hdr->log_seq && end_offset <= hdr->log_offset)) {
+               /* modseqs are up to date */
+               return;
+       }
+
+       ctx->log_view = mail_transaction_log_view_open(ctx->view->index->log);
+       ret = mail_transaction_log_view_set(ctx->log_view,
+                                           I_MAX(1, hdr->log_seq),
+                                           hdr->log_offset,
+                                           end_seq, end_offset, &reset);
+       if (ret == 0) {
+               /* missing files - try with only the last file */
+               ret = mail_transaction_log_view_set(ctx->log_view, end_seq, 0,
+                                                   end_seq, end_offset,
+                                                   &reset);
+               /* since we don't know if we skipped some changes, set all
+                  modseqs to beginning of the latest file. */
+               cur_modseq = get_cur_modseq(ctx);
+               if (cur_modseq < hdr->highest_modseq) {
+                       /* should happen only when setting initial modseqs.
+                          we may already have returned highest_modseq as
+                          some messages' modseq value. don't shrink it. */
+                       cur_modseq = hdr->highest_modseq;
+               }
+               mail_index_modseq_update(ctx, cur_modseq, TRUE, 1,
+                                        map->hdr.messages_count);
+       } else {
+               /* we have all the logs. replace zero modseqs with the current
+                  highest modseq (we may have already returned it for them). */
+               mail_index_modseq_update(ctx, hdr->highest_modseq, FALSE, 1,
+                                        map->hdr.messages_count);
+       }
+       if (ret > 0) {
+               while (mail_transaction_log_view_next(ctx->log_view,
+                                                     &thdr, &tdata) > 0) {
+                       T_BEGIN {
+                               mail_index_modseq_update_old_rec(ctx, thdr,
+                                                                tdata);
+                       } T_END;
+               }
+       }
+       mail_index_sync_write_seq_update(ctx->sync_map_ctx, 1,
+                                        map->hdr.messages_count);
+       mail_transaction_log_view_close(&ctx->log_view);
+}
+
+struct mail_index_modseq_sync *
+mail_index_modseq_sync_begin(struct mail_index_sync_map_ctx *sync_map_ctx)
+{
+       struct mail_index_modseq_sync *ctx;
+
+       ctx = i_new(struct mail_index_modseq_sync, 1);
+       ctx->sync_map_ctx = sync_map_ctx;
+       ctx->view = sync_map_ctx->view;
+       ctx->mmap = mail_index_map_modseq(ctx->view);
+       if (ctx->mmap != NULL) {
+               mail_index_modseq_sync_init(ctx);
+               ctx->log_view = ctx->view->log_view;
+       }
+       return ctx;
+}
+
+static void mail_index_modseq_update_header(struct mail_index_modseq_sync *ctx)
+{
+       struct mail_index_map *map = ctx->view->map;
+       const struct mail_index_ext *ext;
+       const struct mail_index_modseq_header *old_modseq_hdr;
+       struct mail_index_modseq_header new_modseq_hdr;
+       uint32_t ext_map_idx;
+
+       if (!mail_index_map_get_ext_idx(map, ctx->view->index->modseq_ext_id,
+                                       &ext_map_idx))
+               return;
+
+       ext = array_idx(&map->extensions, ext_map_idx);
+       old_modseq_hdr = CONST_PTR_OFFSET(map->hdr_base, ext->hdr_offset);
+
+       if (old_modseq_hdr->log_seq < ctx->log_seq ||
+           (old_modseq_hdr->log_seq == ctx->log_seq &&
+            old_modseq_hdr->log_offset < ctx->log_offset)) {
+               new_modseq_hdr.highest_modseq = ctx->highest_modseq;
+               new_modseq_hdr.log_seq = ctx->log_seq;
+               new_modseq_hdr.log_offset = ctx->log_offset;
+
+               buffer_write(map->hdr_copy_buf, ext->hdr_offset,
+                            &new_modseq_hdr, sizeof(new_modseq_hdr));
+               map->hdr_base = map->hdr_copy_buf->data;
+               map->write_ext_header = TRUE;
+       }
+}
+
+void mail_index_modseq_sync_end(struct mail_index_modseq_sync **_ctx)
+{
+       struct mail_index_modseq_sync *ctx = *_ctx;
+
+       *_ctx = NULL;
+       if (ctx->mmap != NULL)
+               mail_index_modseq_update_header(ctx);
+       i_free(ctx);
+}
+
+void mail_index_modseq_hdr_update(struct mail_index_modseq_sync *ctx)
+{
+       if (ctx->mmap == NULL) {
+               ctx->mmap = mail_index_map_modseq(ctx->view);
+               i_assert(ctx->mmap != NULL);
+               mail_index_modseq_sync_init(ctx);
+               ctx->log_view = ctx->view->log_view;
+       }
+}
+
+void mail_index_modseq_append(struct mail_index_modseq_sync *ctx, uint32_t seq)
+{
+       mail_index_modseq_update_highest(ctx, seq, seq);
+}
+
+void mail_index_modseq_expunge(struct mail_index_modseq_sync *ctx,
+                              uint32_t seq1, uint32_t seq2)
+{
+       struct metadata_modseqs *metadata;
+       unsigned int i, count;
+       uint64_t modseq;
+
+       if (ctx->mmap == NULL)
+               return;
+
+       seq1--;
+       metadata = array_get_modifiable(&ctx->mmap->metadata_modseqs, &count);
+       for (i = 0; i < count; i++) {
+               if (array_is_created(&metadata->modseqs))
+                       array_delete(&metadata->modseqs, seq1, seq2-seq1);
+       }
+
+       modseq = get_cur_modseq(ctx);
+       if (ctx->highest_modseq < modseq)
+               ctx->highest_modseq = modseq;
+}
+
+static void
+modseqs_update(ARRAY_TYPE(modseqs) *array, uint32_t seq1, uint32_t seq2,
+              uint64_t value)
+{
+       for (; seq1 <= seq2; seq1++)
+               array_idx_set(array, seq1-1, &value);
+}
+
+static void
+modseqs_idx_update(struct mail_index_modseq_sync *ctx, unsigned int idx,
+                  uint32_t seq1, uint32_t seq2)
+{
+       struct metadata_modseqs *metadata;
+
+       if (!ctx->view->index->modseqs_enabled) {
+               /* we want to keep permanent modseqs updated, but don't bother
+                  updating in-memory per-flag updates */
+               return;
+       }
+
+       metadata = array_idx_modifiable(&ctx->mmap->metadata_modseqs, idx);
+       if (!array_is_created(&metadata->modseqs))
+               i_array_init(&metadata->modseqs, seq2 + 16);
+       modseqs_update(&metadata->modseqs, seq1, seq2, ctx->highest_modseq);
+}
+
+void mail_index_modseq_update_flags(struct mail_index_modseq_sync *ctx,
+                                   enum mail_flags flags_mask,
+                                   uint32_t seq1, uint32_t seq2)
+{
+       unsigned int i;
+
+       if (!mail_index_modseq_update_highest(ctx, seq1, seq2))
+               return;
+
+       for (i = 0; i < METADATA_MODSEQ_IDX_KEYWORD_START; i++) {
+               if ((flags_mask & (1 << i)) != 0)
+                       modseqs_idx_update(ctx, i, seq1, seq2);
+       }
+}
+
+void mail_index_modseq_update_keyword(struct mail_index_modseq_sync *ctx,
+                                     unsigned int keyword_idx,
+                                     uint32_t seq1, uint32_t seq2)
+{
+       if (!mail_index_modseq_update_highest(ctx, seq1, seq2))
+               return;
+
+       modseqs_idx_update(ctx, METADATA_MODSEQ_IDX_KEYWORD_START + keyword_idx,
+                          seq1, seq2);
+}
+
+void mail_index_modseq_reset_keywords(struct mail_index_modseq_sync *ctx,
+                                     uint32_t seq1, uint32_t seq2)
+{
+       unsigned int i, count;
+
+       if (!mail_index_modseq_update_highest(ctx, seq1, seq2))
+               return;
+
+       count = array_count(&ctx->mmap->metadata_modseqs);
+       for (i = METADATA_MODSEQ_IDX_KEYWORD_START; i < count; i++)
+               modseqs_idx_update(ctx, i, seq1, seq2);
+}
+
+void mail_index_map_modseq_free(struct mail_index_map_modseq *mmap)
+{
+       struct metadata_modseqs *metadata;
+       unsigned int i, count;
+
+       metadata = array_get_modifiable(&mmap->metadata_modseqs, &count);
+       for (i = 0; i < count; i++) {
+               if (array_is_created(&metadata->modseqs))
+                       array_free(&metadata->modseqs);
+       }
+       array_free(&mmap->metadata_modseqs);
+       i_free(mmap);
+}
diff --git a/src/lib-index/mail-index-modseq.h b/src/lib-index/mail-index-modseq.h
new file mode 100644 (file)
index 0000000..1a773b8
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef MAIL_INDEX_MODSEQ_H
+#define MAIL_INDEX_MODSEQ_H
+
+enum mail_flags;
+struct mail_keywords;
+struct mail_index;
+struct mail_index_view;
+struct mail_index_modseq;
+struct mail_index_map_modseq;
+struct mail_index_sync_map_ctx;
+
+void mail_index_modseq_init(struct mail_index *index);
+
+void mail_index_modseq_enable(struct mail_index *index);
+uint64_t mail_index_modseq_get_highest(struct mail_index_view *view);
+
+uint64_t mail_index_modseq_lookup(struct mail_index_view *view, uint32_t seq);
+uint64_t mail_index_modseq_lookup_flags(struct mail_index_view *view,
+                                       enum mail_flags flags_mask,
+                                       uint32_t seq);
+uint64_t mail_index_modseq_lookup_keywords(struct mail_index_view *view,
+                                          const struct mail_keywords *keywords,
+                                          uint32_t seq);
+
+struct mail_index_modseq_sync *
+mail_index_modseq_sync_begin(struct mail_index_sync_map_ctx *sync_map_ctx);
+void mail_index_modseq_sync_end(struct mail_index_modseq_sync **ctx);
+
+void mail_index_modseq_hdr_update(struct mail_index_modseq_sync *ctx);
+void mail_index_modseq_append(struct mail_index_modseq_sync *ctx, uint32_t seq);
+void mail_index_modseq_expunge(struct mail_index_modseq_sync *ctx,
+                              uint32_t seq1, uint32_t seq2);
+void mail_index_modseq_update_flags(struct mail_index_modseq_sync *ctx,
+                                   enum mail_flags flags_mask,
+                                   uint32_t seq1, uint32_t seq2);
+void mail_index_modseq_update_keyword(struct mail_index_modseq_sync *ctx,
+                                     unsigned int keyword_idx,
+                                     uint32_t seq1, uint32_t seq2);
+void mail_index_modseq_reset_keywords(struct mail_index_modseq_sync *ctx,
+                                     uint32_t seq1, uint32_t seq2);
+
+void mail_index_map_modseq_free(struct mail_index_map_modseq *mmap);
+
+#endif