1
/* Copyright (c) 2008-2009 Dovecot authors, see the included COPYING file */
5
#include "mail-transaction-log-private.h"
6
#include "mail-index-private.h"
7
#include "mail-index-sync-private.h"
8
#include "mail-index-modseq.h"
10
ARRAY_DEFINE_TYPE(modseqs, uint64_t);
12
enum modseq_metadata_idx {
13
/* must be in the same order as enum mail_flags */
14
METADATA_MODSEQ_IDX_ANSWERED = 0,
15
METADATA_MODSEQ_IDX_FLAGGED,
16
METADATA_MODSEQ_IDX_DELETED,
17
METADATA_MODSEQ_IDX_SEEN,
18
METADATA_MODSEQ_IDX_DRAFT,
20
METADATA_MODSEQ_IDX_KEYWORD_START
23
struct metadata_modseqs {
24
ARRAY_TYPE(modseqs) modseqs;
27
struct mail_index_map_modseq {
28
/* indexes use enum modseq_metadata_idx */
29
ARRAY_DEFINE(metadata_modseqs, struct metadata_modseqs);
32
struct mail_index_modseq_sync {
33
struct mail_index_sync_map_ctx *sync_map_ctx;
34
struct mail_index_view *view;
35
struct mail_transaction_log_view *log_view;
36
struct mail_index_map_modseq *mmap;
38
uint64_t highest_modseq;
41
void mail_index_modseq_init(struct mail_index *index)
43
index->modseq_ext_id =
44
mail_index_ext_register(index, MAIL_INDEX_MODSEQ_EXT_NAME,
45
sizeof(struct mail_index_modseq_header),
46
sizeof(uint64_t), sizeof(uint64_t));
49
static uint64_t mail_index_modseq_get_head(struct mail_index *index)
51
return index->log->head == NULL ? 1 :
52
I_MAX(index->log->head->sync_highest_modseq, 1);
55
void mail_index_modseq_enable(struct mail_index *index)
57
struct mail_index_transaction *trans;
58
struct mail_index_view *view;
59
struct mail_index_modseq_header hdr;
60
uint32_t ext_map_idx, log_seq;
63
if (index->modseqs_enabled)
66
if (!mail_index_map_get_ext_idx(index->map, index->modseq_ext_id,
68
/* modseqs not enabled to the index yet, add them. */
69
view = mail_index_view_open(index);
70
trans = mail_index_transaction_begin(view, 0);
72
memset(&hdr, 0, sizeof(hdr));
73
hdr.highest_modseq = mail_index_modseq_get_head(index);
74
mail_index_update_header_ext(trans, index->modseq_ext_id,
75
0, &hdr, sizeof(hdr));
77
/* commit also refreshes the index, which syncs the modseqs */
78
(void)mail_index_transaction_commit(&trans,
79
&log_seq, &log_offset);
80
mail_index_view_close(&view);
82
/* get the modseq extension to index map */
83
if (!mail_index_map_get_ext_idx(index->map,
86
/* didn't work for some reason */
90
index->modseqs_enabled = TRUE;
93
const struct mail_index_modseq_header *
94
mail_index_map_get_modseq_header(struct mail_index_map *map)
96
const struct mail_index_ext *ext;
99
if (!mail_index_map_get_ext_idx(map, map->index->modseq_ext_id, &idx))
102
ext = array_idx(&map->extensions, idx);
103
if (ext->hdr_size != sizeof(struct mail_index_modseq_header))
106
return CONST_PTR_OFFSET(map->hdr_base, ext->hdr_offset);
109
uint64_t mail_index_map_modseq_get_highest(struct mail_index_map *map)
111
const struct mail_index_modseq_header *modseq_hdr;
113
modseq_hdr = mail_index_map_get_modseq_header(map);
114
if (modseq_hdr != NULL && modseq_hdr->highest_modseq != 0)
115
return modseq_hdr->highest_modseq;
117
/* fallback to returning the log head. if modseqs aren't
118
enabled, we return 0. */
119
return map->index->log->head == NULL ? 0 :
120
map->index->log->head->sync_highest_modseq;
124
uint64_t mail_index_modseq_get_highest(struct mail_index_view *view)
126
return mail_index_map_modseq_get_highest(view->map);
129
static struct mail_index_map_modseq *
130
mail_index_map_modseq(struct mail_index_view *view)
132
struct mail_index_map_modseq *mmap = view->map->rec_map->modseq;
133
uint32_t ext_map_idx;
138
/* don't start tracking until we've seen modseq extension intro */
139
if (!mail_index_map_get_ext_idx(view->map, view->index->modseq_ext_id,
143
mmap = i_new(struct mail_index_map_modseq, 1);
144
i_array_init(&mmap->metadata_modseqs,
145
METADATA_MODSEQ_IDX_KEYWORD_START +
146
array_count(&view->index->keywords));
147
view->map->rec_map->modseq = mmap;
151
uint64_t mail_index_modseq_lookup(struct mail_index_view *view, uint32_t seq)
153
struct mail_index_map_modseq *mmap = mail_index_map_modseq(view);
154
struct mail_index_map *map;
155
const struct mail_index_ext *ext;
156
const struct mail_index_record *rec;
157
const uint64_t *modseqp;
158
uint32_t ext_map_idx;
161
return mail_index_modseq_get_head(view->index);
163
rec = mail_index_lookup_full(view, seq, &map);
164
if (!mail_index_map_get_ext_idx(map, view->index->modseq_ext_id,
166
/* not enabled yet */
167
return mail_index_modseq_get_head(view->index);
170
ext = array_idx(&map->extensions, ext_map_idx);
171
modseqp = CONST_PTR_OFFSET(rec, ext->record_offset);
173
/* If we're here because we just enabled modseqs, we'll return
174
the same modseq (initial highestmodseq) for all messages.
175
The next sync will change these zeros to initial
176
highestmodseq or higher.
178
If we're here because a message got appended but modseq
179
wasn't set (older Dovecot?), we'll again use the current
180
highest modseq. This isn't exactly correct, but it gets
181
fixed after the next sync and this situation shouldn't
182
normally happen anyway. */
183
return mail_index_modseq_get_highest(view);
189
modseq_idx_lookup(struct mail_index_map_modseq *mmap,
190
unsigned int idx, uint32_t seq)
192
const struct metadata_modseqs *metadata;
193
const uint64_t *modseqs;
196
metadata = array_get(&mmap->metadata_modseqs, &count);
197
if (idx >= count || !array_is_created(&metadata[idx].modseqs))
200
modseqs = array_get(&metadata[idx].modseqs, &count);
201
return seq > count ? 0 : modseqs[seq-1];
204
uint64_t mail_index_modseq_lookup_flags(struct mail_index_view *view,
205
enum mail_flags flags_mask,
208
struct mail_index_map_modseq *mmap = mail_index_map_modseq(view);
210
uint64_t modseq, highest_modseq = 0;
213
/* first try to find a specific match */
214
for (i = 0; i < METADATA_MODSEQ_IDX_KEYWORD_START; i++) {
215
if ((flags_mask & (1 << i)) != 0) {
216
modseq = modseq_idx_lookup(mmap, i, seq);
217
if (highest_modseq < modseq)
218
highest_modseq = modseq;
223
if (highest_modseq == 0) {
224
/* no specific matches, fallback to using the highest */
225
highest_modseq = mail_index_modseq_lookup(view, seq);
227
return highest_modseq;
230
uint64_t mail_index_modseq_lookup_keywords(struct mail_index_view *view,
231
const struct mail_keywords *keywords,
234
struct mail_index_map_modseq *mmap = mail_index_map_modseq(view);
235
unsigned int i, metadata_idx;
236
uint64_t modseq, highest_modseq = 0;
239
/* first try to find a specific match */
240
for (i = 0; i < keywords->count; i++) {
241
metadata_idx = METADATA_MODSEQ_IDX_KEYWORD_START +
244
modseq = modseq_idx_lookup(mmap, metadata_idx, seq);
245
if (highest_modseq < modseq)
246
highest_modseq = modseq;
250
if (highest_modseq == 0) {
251
/* no specific matches, fallback to using the highest */
252
highest_modseq = mail_index_modseq_lookup(view, seq);
254
return highest_modseq;
258
mail_index_modseq_update(struct mail_index_modseq_sync *ctx,
259
uint64_t modseq, bool nonzeros,
260
uint32_t seq1, uint32_t seq2)
262
const struct mail_index_ext *ext;
263
const struct mail_index_record *rec;
264
uint32_t ext_map_idx;
267
if (!mail_index_map_get_ext_idx(ctx->view->map,
268
ctx->view->index->modseq_ext_id,
272
if (modseq > ctx->highest_modseq)
273
ctx->highest_modseq = modseq;
275
ext = array_idx(&ctx->view->map->extensions, ext_map_idx);
276
for (; seq1 <= seq2; seq1++) {
277
rec = MAIL_INDEX_MAP_IDX(ctx->view->map, seq1-1);
278
modseqp = PTR_OFFSET(rec, ext->record_offset);
279
if (*modseqp == 0 || (nonzeros && *modseqp < modseq))
285
mail_index_modseq_update_highest(struct mail_index_modseq_sync *ctx,
286
uint32_t seq1, uint32_t seq2)
290
if (ctx->mmap == NULL)
293
modseq = mail_transaction_log_view_get_prev_modseq(ctx->log_view);
294
mail_index_modseq_update(ctx, modseq, TRUE, seq1, seq2);
299
mail_index_modseq_update_old_rec(struct mail_index_modseq_sync *ctx,
300
const struct mail_transaction_header *thdr,
303
ARRAY_TYPE(seq_range) uids = ARRAY_INIT;
304
const struct seq_range *rec;
306
unsigned int i, count;
309
switch (thdr->type & MAIL_TRANSACTION_TYPE_MASK) {
310
case MAIL_TRANSACTION_APPEND: {
311
const struct mail_index_record *appends = tdata;
313
count = thdr->size / sizeof(*appends);
314
for (i = 0; i < count; i++) {
315
if (mail_index_lookup_seq(ctx->view,
316
appends[i].uid, &seq1)) {
317
mail_index_modseq_update_highest(ctx, seq1,
323
case MAIL_TRANSACTION_FLAG_UPDATE: {
324
uid_buf = buffer_create_const_data(pool_datastack_create(),
326
array_create_from_buffer(&uids, uid_buf,
327
sizeof(struct mail_transaction_flag_update));
330
case MAIL_TRANSACTION_KEYWORD_UPDATE: {
331
const struct mail_transaction_keyword_update *rec = tdata;
332
unsigned int seqset_offset;
334
seqset_offset = sizeof(*rec) + rec->name_size;
335
if ((seqset_offset % 4) != 0)
336
seqset_offset += 4 - (seqset_offset % 4);
338
uid_buf = buffer_create_const_data(pool_datastack_create(),
339
CONST_PTR_OFFSET(tdata, seqset_offset),
340
thdr->size - seqset_offset);
341
array_create_from_buffer(&uids, uid_buf, sizeof(uint32_t)*2);
344
case MAIL_TRANSACTION_KEYWORD_RESET:
345
uid_buf = buffer_create_const_data(pool_datastack_create(),
347
array_create_from_buffer(&uids, uid_buf,
348
sizeof(struct mail_transaction_keyword_reset));
355
count = array_count(&uids);
356
for (i = 0; i < count; i++) {
357
rec = array_idx(&uids, i);
358
if (mail_index_lookup_seq_range(ctx->view, rec->seq1, rec->seq2,
360
mail_index_modseq_update_highest(ctx, seq1, seq2);
364
static void mail_index_modseq_sync_init(struct mail_index_modseq_sync *ctx)
366
struct mail_index_map *map = ctx->view->map;
367
const struct mail_index_ext *ext;
368
const struct mail_index_modseq_header *hdr;
369
const struct mail_transaction_header *thdr;
371
uint32_t ext_map_idx;
378
if (!mail_index_map_get_ext_idx(map, ctx->view->index->modseq_ext_id,
381
ext = array_idx(&map->extensions, ext_map_idx);
383
/* get the current highest_modseq. don't change any modseq below it. */
384
hdr = CONST_PTR_OFFSET(map->hdr_base, ext->hdr_offset);
385
ctx->highest_modseq = hdr->highest_modseq;
387
/* Scan logs for updates between ext_hdr.log_* .. view position.
388
There are two reasons why there could be any:
390
1) We just enabled modseqs and we're filling the initial values.
391
2) A non-modseq-aware Dovecot version added new messages and wrote
392
dovecot.index file. */
393
mail_transaction_log_view_get_prev_pos(ctx->view->log_view,
394
&end_seq, &end_offset);
395
if (end_seq <= hdr->log_seq ||
396
(end_seq == hdr->log_seq && end_offset <= hdr->log_offset)) {
397
/* modseqs are up to date */
401
ctx->log_view = mail_transaction_log_view_open(ctx->view->index->log);
402
ret = mail_transaction_log_view_set(ctx->log_view,
403
I_MAX(1, hdr->log_seq),
405
end_seq, end_offset, &reset);
407
/* missing files - try with only the last file */
408
ret = mail_transaction_log_view_set(ctx->log_view, end_seq, 0,
411
/* since we don't know if we skipped some changes, set all
412
modseqs to beginning of the latest file. */
413
cur_modseq = mail_transaction_log_view_get_prev_modseq(
415
if (cur_modseq < hdr->highest_modseq) {
416
/* should happen only when setting initial modseqs.
417
we may already have returned highest_modseq as
418
some messages' modseq value. don't shrink it. */
419
cur_modseq = hdr->highest_modseq;
421
mail_index_modseq_update(ctx, cur_modseq, TRUE, 1,
422
map->hdr.messages_count);
424
/* we have all the logs. replace zero modseqs with the current
425
highest modseq (we may have already returned it for them). */
426
mail_index_modseq_update(ctx, hdr->highest_modseq, FALSE, 1,
427
map->hdr.messages_count);
430
while (mail_transaction_log_view_next(ctx->log_view,
431
&thdr, &tdata) > 0) {
433
mail_index_modseq_update_old_rec(ctx, thdr,
438
mail_index_sync_write_seq_update(ctx->sync_map_ctx, 1,
439
map->hdr.messages_count);
440
mail_transaction_log_view_close(&ctx->log_view);
443
struct mail_index_modseq_sync *
444
mail_index_modseq_sync_begin(struct mail_index_sync_map_ctx *sync_map_ctx)
446
struct mail_index_modseq_sync *ctx;
448
ctx = i_new(struct mail_index_modseq_sync, 1);
449
ctx->sync_map_ctx = sync_map_ctx;
450
ctx->view = sync_map_ctx->view;
451
ctx->mmap = mail_index_map_modseq(ctx->view);
452
if (ctx->mmap != NULL) {
453
mail_index_modseq_sync_init(ctx);
454
ctx->log_view = ctx->view->log_view;
459
static void mail_index_modseq_update_header(struct mail_index_modseq_sync *ctx)
461
struct mail_index_map *map = ctx->view->map;
462
const struct mail_index_ext *ext;
463
const struct mail_index_modseq_header *old_modseq_hdr;
464
struct mail_index_modseq_header new_modseq_hdr;
465
uint32_t ext_map_idx, log_seq;
468
if (!mail_index_map_get_ext_idx(map, ctx->view->index->modseq_ext_id,
472
mail_transaction_log_view_get_prev_pos(ctx->view->log_view,
473
&log_seq, &log_offset);
475
ext = array_idx(&map->extensions, ext_map_idx);
476
old_modseq_hdr = CONST_PTR_OFFSET(map->hdr_base, ext->hdr_offset);
478
if (old_modseq_hdr->log_seq < log_seq ||
479
(old_modseq_hdr->log_seq == log_seq &&
480
old_modseq_hdr->log_offset < log_offset)) {
481
new_modseq_hdr.highest_modseq = ctx->highest_modseq;
482
new_modseq_hdr.log_seq = log_seq;
483
new_modseq_hdr.log_offset = log_offset;
485
buffer_write(map->hdr_copy_buf, ext->hdr_offset,
486
&new_modseq_hdr, sizeof(new_modseq_hdr));
487
map->hdr_base = map->hdr_copy_buf->data;
488
map->write_ext_header = TRUE;
492
void mail_index_modseq_sync_end(struct mail_index_modseq_sync **_ctx)
494
struct mail_index_modseq_sync *ctx = *_ctx;
497
if (ctx->mmap != NULL) {
498
i_assert(ctx->mmap == ctx->view->map->rec_map->modseq);
499
mail_index_modseq_update_header(ctx);
504
void mail_index_modseq_sync_map_replaced(struct mail_index_modseq_sync *ctx)
506
ctx->mmap = mail_index_map_modseq(ctx->view);
509
void mail_index_modseq_hdr_update(struct mail_index_modseq_sync *ctx)
511
if (ctx->mmap == NULL) {
512
ctx->mmap = mail_index_map_modseq(ctx->view);
513
i_assert(ctx->mmap != NULL);
514
mail_index_modseq_sync_init(ctx);
515
ctx->log_view = ctx->view->log_view;
519
void mail_index_modseq_append(struct mail_index_modseq_sync *ctx, uint32_t seq)
521
mail_index_modseq_update_highest(ctx, seq, seq);
524
void mail_index_modseq_expunge(struct mail_index_modseq_sync *ctx,
525
uint32_t seq1, uint32_t seq2)
527
struct metadata_modseqs *metadata;
528
unsigned int i, count;
531
if (ctx->mmap == NULL)
535
metadata = array_get_modifiable(&ctx->mmap->metadata_modseqs, &count);
536
for (i = 0; i < count; i++) {
537
if (array_is_created(&metadata->modseqs))
538
array_delete(&metadata->modseqs, seq1, seq2-seq1);
541
modseq = mail_transaction_log_view_get_prev_modseq(ctx->log_view);
542
if (ctx->highest_modseq < modseq)
543
ctx->highest_modseq = modseq;
547
modseqs_update(ARRAY_TYPE(modseqs) *array, uint32_t seq1, uint32_t seq2,
550
for (; seq1 <= seq2; seq1++)
551
array_idx_set(array, seq1-1, &value);
555
modseqs_idx_update(struct mail_index_modseq_sync *ctx, unsigned int idx,
556
uint32_t seq1, uint32_t seq2)
558
struct metadata_modseqs *metadata;
560
if (!ctx->view->index->modseqs_enabled) {
561
/* we want to keep permanent modseqs updated, but don't bother
562
updating in-memory per-flag updates */
566
metadata = array_idx_modifiable(&ctx->mmap->metadata_modseqs, idx);
567
if (!array_is_created(&metadata->modseqs))
568
i_array_init(&metadata->modseqs, seq2 + 16);
569
modseqs_update(&metadata->modseqs, seq1, seq2, ctx->highest_modseq);
572
void mail_index_modseq_update_flags(struct mail_index_modseq_sync *ctx,
573
enum mail_flags flags_mask,
574
uint32_t seq1, uint32_t seq2)
578
if (!mail_index_modseq_update_highest(ctx, seq1, seq2))
581
for (i = 0; i < METADATA_MODSEQ_IDX_KEYWORD_START; i++) {
582
if ((flags_mask & (1 << i)) != 0)
583
modseqs_idx_update(ctx, i, seq1, seq2);
587
void mail_index_modseq_update_keyword(struct mail_index_modseq_sync *ctx,
588
unsigned int keyword_idx,
589
uint32_t seq1, uint32_t seq2)
591
if (!mail_index_modseq_update_highest(ctx, seq1, seq2))
594
modseqs_idx_update(ctx, METADATA_MODSEQ_IDX_KEYWORD_START + keyword_idx,
598
void mail_index_modseq_reset_keywords(struct mail_index_modseq_sync *ctx,
599
uint32_t seq1, uint32_t seq2)
601
unsigned int i, count;
603
if (!mail_index_modseq_update_highest(ctx, seq1, seq2))
606
count = array_count(&ctx->mmap->metadata_modseqs);
607
for (i = METADATA_MODSEQ_IDX_KEYWORD_START; i < count; i++)
608
modseqs_idx_update(ctx, i, seq1, seq2);
611
struct mail_index_map_modseq *
612
mail_index_map_modseq_clone(const struct mail_index_map_modseq *mmap)
614
struct mail_index_map_modseq *new_mmap;
615
const struct metadata_modseqs *src_metadata;
616
struct metadata_modseqs *dest_metadata;
617
unsigned int i, count;
619
src_metadata = array_get(&mmap->metadata_modseqs, &count);
621
new_mmap = i_new(struct mail_index_map_modseq, 1);
622
i_array_init(&new_mmap->metadata_modseqs, count + 16);
624
for (i = 0; i < count; i++) {
625
dest_metadata = array_append_space(&new_mmap->metadata_modseqs);
626
if (array_is_created(&src_metadata[i].modseqs)) {
627
i_array_init(&dest_metadata->modseqs,
628
array_count(&src_metadata[i].modseqs));
629
array_append_array(&dest_metadata->modseqs,
630
&src_metadata[i].modseqs);
636
void mail_index_map_modseq_free(struct mail_index_map_modseq **_mmap)
638
struct mail_index_map_modseq *mmap = *_mmap;
639
struct metadata_modseqs *metadata;
640
unsigned int i, count;
644
metadata = array_get_modifiable(&mmap->metadata_modseqs, &count);
645
for (i = 0; i < count; i++) {
646
if (array_is_created(&metadata[i].modseqs))
647
array_free(&metadata[i].modseqs);
649
array_free(&mmap->metadata_modseqs);
653
bool mail_index_modseq_get_next_log_offset(struct mail_index_view *view,
654
uint64_t modseq, uint32_t *log_seq_r,
655
uoff_t *log_offset_r)
657
struct mail_transaction_log_file *file, *prev_file = NULL;
659
for (file = view->index->log->files; file != NULL; file = file->next) {
660
if (modseq < file->hdr.initial_modseq)
665
if (prev_file == NULL) {
666
/* the log file has been deleted already */
670
*log_seq_r = prev_file->hdr.file_seq;
671
return mail_transaction_log_file_get_modseq_next_offset(
672
prev_file, modseq, log_offset_r) == 0;