1
/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
5
#include "mail-index-modseq.h"
6
#include "mail-storage-private.h"
7
#include "dsync-mail.h"
8
#include "dsync-mailbox.h"
9
#include "dsync-transaction-log-scan.h"
11
struct dsync_transaction_log_scan {
13
HASH_TABLE_TYPE(dsync_uid_mail_change) changes;
14
HASH_TABLE_TYPE(dsync_attr_change) attr_changes;
15
struct mail_index_view *view;
16
uint32_t highest_wanted_uid;
18
uint32_t last_log_seq;
19
uoff_t last_log_offset;
21
bool returned_all_changes;
24
static bool ATTR_NOWARN_UNUSED_RESULT
25
export_change_get(struct dsync_transaction_log_scan *ctx, uint32_t uid,
26
enum dsync_mail_change_type type,
27
struct dsync_mail_change **change_r)
29
struct dsync_mail_change *change;
30
const char *orig_guid;
33
i_assert(type != DSYNC_MAIL_CHANGE_TYPE_SAVE);
37
if (uid > ctx->highest_wanted_uid)
40
change = hash_table_lookup(ctx->changes, POINTER_CAST(uid));
42
/* first change for this UID */
43
change = p_new(ctx->pool, struct dsync_mail_change, 1);
46
hash_table_insert(ctx->changes, POINTER_CAST(uid), change);
47
} else if (type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) {
48
/* expunge overrides flag changes */
49
orig_guid = change->guid;
50
memset(change, 0, sizeof(*change));
53
change->guid = orig_guid;
54
} else if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) {
55
/* already expunged, this change doesn't matter */
58
/* another flag update */
65
log_add_expunge(struct dsync_transaction_log_scan *ctx, const void *data,
66
const struct mail_transaction_header *hdr)
68
const struct mail_transaction_expunge *rec = data, *end;
69
struct dsync_mail_change *change;
72
if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
73
/* this is simply a request for expunge */
76
end = CONST_PTR_OFFSET(data, hdr->size);
77
for (; rec != end; rec++) {
78
for (uid = rec->uid1; uid <= rec->uid2; uid++) {
79
export_change_get(ctx, uid,
80
DSYNC_MAIL_CHANGE_TYPE_EXPUNGE,
87
log_add_expunge_uid(struct dsync_transaction_log_scan *ctx, const void *data,
88
const struct mail_transaction_header *hdr, uint32_t uid)
90
const struct mail_transaction_expunge *rec = data, *end;
91
struct dsync_mail_change *change;
93
if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
94
/* this is simply a request for expunge */
97
end = CONST_PTR_OFFSET(data, hdr->size);
98
for (; rec != end; rec++) {
99
if (uid >= rec->uid1 && uid <= rec->uid2) {
100
export_change_get(ctx, uid,
101
DSYNC_MAIL_CHANGE_TYPE_EXPUNGE,
110
log_add_expunge_guid(struct dsync_transaction_log_scan *ctx,
111
struct mail_index_view *view, const void *data,
112
const struct mail_transaction_header *hdr)
114
const struct mail_transaction_expunge_guid *rec = data, *end;
115
struct dsync_mail_change *change;
119
external = (hdr->type & MAIL_TRANSACTION_EXTERNAL) != 0;
121
end = CONST_PTR_OFFSET(data, hdr->size);
122
for (; rec != end; rec++) {
123
if (!external && mail_index_lookup_seq(view, rec->uid, &seq)) {
124
/* expunge request that hasn't been actually done yet.
125
we check non-external ones because they might have
126
the GUID while external ones don't. */
129
if (export_change_get(ctx, rec->uid,
130
DSYNC_MAIL_CHANGE_TYPE_EXPUNGE,
132
!guid_128_is_empty(rec->guid_128)) T_BEGIN {
133
change->guid = p_strdup(ctx->pool,
134
guid_128_to_string(rec->guid_128));
140
log_add_expunge_guid_uid(struct dsync_transaction_log_scan *ctx, const void *data,
141
const struct mail_transaction_header *hdr, uint32_t uid)
143
const struct mail_transaction_expunge_guid *rec = data, *end;
144
struct dsync_mail_change *change;
146
/* we're assuming UID is already known to be expunged */
147
end = CONST_PTR_OFFSET(data, hdr->size);
148
for (; rec != end; rec++) {
152
if (!export_change_get(ctx, rec->uid,
153
DSYNC_MAIL_CHANGE_TYPE_EXPUNGE,
156
if (!guid_128_is_empty(rec->guid_128)) T_BEGIN {
157
change->guid = p_strdup(ctx->pool,
158
guid_128_to_string(rec->guid_128));
166
log_add_flag_update(struct dsync_transaction_log_scan *ctx, const void *data,
167
const struct mail_transaction_header *hdr)
169
const struct mail_transaction_flag_update *rec = data, *end;
170
struct dsync_mail_change *change;
173
end = CONST_PTR_OFFSET(data, hdr->size);
174
for (; rec != end; rec++) {
175
for (uid = rec->uid1; uid <= rec->uid2; uid++) {
176
if (export_change_get(ctx, uid,
177
DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE,
179
change->add_flags |= rec->add_flags;
180
change->remove_flags &= ~rec->add_flags;
181
change->remove_flags |= rec->remove_flags;
182
change->add_flags &= ~rec->remove_flags;
189
log_add_keyword_reset(struct dsync_transaction_log_scan *ctx, const void *data,
190
const struct mail_transaction_header *hdr)
192
const struct mail_transaction_keyword_reset *rec = data, *end;
193
struct dsync_mail_change *change;
196
end = CONST_PTR_OFFSET(data, hdr->size);
197
for (; rec != end; rec++) {
198
for (uid = rec->uid1; uid <= rec->uid2; uid++) {
199
if (!export_change_get(ctx, uid,
200
DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE,
204
change->keywords_reset = TRUE;
205
if (array_is_created(&change->keyword_changes))
206
array_clear(&change->keyword_changes);
212
keywords_change_remove(struct dsync_mail_change *change, const char *name)
214
const char *const *changes;
215
unsigned int i, count;
217
changes = array_get(&change->keyword_changes, &count);
218
for (i = 0; i < count; i++) {
219
if (strcmp(changes[i]+1, name) == 0) {
220
array_delete(&change->keyword_changes, i, 1);
227
log_add_keyword_update(struct dsync_transaction_log_scan *ctx, const void *data,
228
const struct mail_transaction_header *hdr)
230
const struct mail_transaction_keyword_update *rec = data;
231
struct dsync_mail_change *change;
232
const char *kw_name, *change_str;
233
const uint32_t *uids, *end;
234
unsigned int uids_offset;
237
uids_offset = sizeof(*rec) + rec->name_size;
238
if ((uids_offset % 4) != 0)
239
uids_offset += 4 - (uids_offset % 4);
241
kw_name = t_strndup((const void *)(rec+1), rec->name_size);
242
switch (rec->modify_type) {
244
change_str = p_strdup_printf(ctx->pool, "%c%s",
245
KEYWORD_CHANGE_ADD, kw_name);
248
change_str = p_strdup_printf(ctx->pool, "%c%s",
249
KEYWORD_CHANGE_REMOVE, kw_name);
255
uids = CONST_PTR_OFFSET(rec, uids_offset);
256
end = CONST_PTR_OFFSET(rec, hdr->size);
258
for (; uids < end; uids += 2) {
259
for (uid = uids[0]; uid <= uids[1]; uid++) {
260
if (!export_change_get(ctx, uid,
261
DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE,
264
if (!array_is_created(&change->keyword_changes)) {
265
p_array_init(&change->keyword_changes,
268
keywords_change_remove(change, kw_name);
270
array_append(&change->keyword_changes, &change_str, 1);
276
log_add_modseq_update(struct dsync_transaction_log_scan *ctx, const void *data,
277
const struct mail_transaction_header *hdr, bool pvt_scan)
279
const struct mail_transaction_modseq_update *rec = data, *end;
280
struct dsync_mail_change *change;
283
/* update message's modseq, possibly by creating an empty flag change */
284
end = CONST_PTR_OFFSET(rec, hdr->size);
285
for (; rec != end; rec++) {
287
/* highestmodseq update */
291
if (!export_change_get(ctx, rec->uid,
292
DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE,
296
modseq = rec->modseq_low32 |
297
((uint64_t)rec->modseq_high32 << 32);
299
if (change->modseq < modseq)
300
change->modseq = modseq;
302
if (change->pvt_modseq < modseq)
303
change->pvt_modseq = modseq;
309
log_add_attribute_update_key(struct dsync_transaction_log_scan *ctx,
310
const char *attr_change, uint64_t modseq)
312
struct dsync_mailbox_attribute lookup_attr, *attr;
314
i_assert(strlen(attr_change) > 2); /* checked by lib-index */
316
lookup_attr.type = attr_change[1] == 'p' ?
317
MAIL_ATTRIBUTE_TYPE_PRIVATE : MAIL_ATTRIBUTE_TYPE_SHARED;
318
lookup_attr.key = attr_change+2;
320
attr = hash_table_lookup(ctx->attr_changes, &lookup_attr);
322
attr = p_new(ctx->pool, struct dsync_mailbox_attribute, 1);
323
attr->type = lookup_attr.type;
324
attr->key = p_strdup(ctx->pool, lookup_attr.key);
325
hash_table_insert(ctx->attr_changes, attr, attr);
327
attr->deleted = attr_change[0] == '-';
328
attr->modseq = modseq;
332
log_add_attribute_update(struct dsync_transaction_log_scan *ctx,
334
const struct mail_transaction_header *hdr,
337
const char *attr_changes = data;
340
for (i = 0; i < hdr->size && attr_changes[i] != '\0'; ) {
341
log_add_attribute_update_key(ctx, attr_changes+i, modseq);
342
i += strlen(attr_changes+i) + 1;
347
dsync_log_set(struct dsync_transaction_log_scan *ctx,
348
struct mail_index_view *view, bool pvt_scan,
349
struct mail_transaction_log_view *log_view, uint64_t modseq)
351
uint32_t log_seq, end_seq;
352
uoff_t log_offset, end_offset;
356
end_seq = view->log_file_head_seq;
357
end_offset = view->log_file_head_offset;
360
mail_index_modseq_get_next_log_offset(view, modseq,
361
&log_seq, &log_offset)) {
362
/* scan the view only up to end of the current view.
363
if there are more changes, we don't care about them until
364
the next sync. the modseq may however already point to
365
beyond the current view's end (FIXME: why?) */
366
if (log_seq > end_seq ||
367
(log_seq == end_seq && log_offset > end_offset)) {
369
end_offset = log_offset;
371
ret = mail_transaction_log_view_set(log_view,
379
/* return everything we've got (until the end of the view) */
381
ctx->returned_all_changes = TRUE;
382
if (mail_transaction_log_view_set_all(log_view) < 0)
385
mail_transaction_log_view_get_prev_pos(log_view, &log_seq, &log_offset);
386
if (log_seq > end_seq ||
387
(log_seq == end_seq && log_offset > end_offset)) {
389
end_offset = log_offset;
391
ret = mail_transaction_log_view_set(log_view,
393
end_seq, end_offset, &reset);
395
/* we shouldn't get here. _view_set_all() already
396
reserved all the log files, the _view_set() only
397
removed unwanted ones. */
398
i_error("%s: Couldn't set transaction log view (seq %u..%u)",
399
view->index->filepath, log_seq, end_seq);
402
return ret < 0 ? -1 : 0;
406
dsync_log_scan(struct dsync_transaction_log_scan *ctx,
407
struct mail_index_view *view, uint64_t modseq, bool pvt_scan)
409
struct mail_transaction_log_view *log_view;
410
const struct mail_transaction_header *hdr;
412
uint32_t file_seq, max_seq;
413
uoff_t file_offset, max_offset;
416
log_view = mail_transaction_log_view_open(view->index->log);
417
if (dsync_log_set(ctx, view, pvt_scan, log_view, modseq) < 0) {
418
mail_transaction_log_view_close(&log_view);
422
/* read the log only up to current position in view */
423
max_seq = view->log_file_expunge_seq;
424
max_offset = view->log_file_expunge_offset;
426
mail_transaction_log_view_get_prev_pos(log_view, &file_seq,
429
while (mail_transaction_log_view_next(log_view, &hdr, &data) > 0) {
430
mail_transaction_log_view_get_prev_pos(log_view, &file_seq,
432
if (file_offset >= max_offset && file_seq == max_seq)
435
if ((hdr->type & MAIL_TRANSACTION_SYNC) != 0) {
436
/* ignore changes done by dsync, unless we can get
437
expunged message's GUID from it */
438
if ((hdr->type & MAIL_TRANSACTION_TYPE_MASK) !=
439
MAIL_TRANSACTION_EXPUNGE_GUID)
443
switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
444
case MAIL_TRANSACTION_EXPUNGE:
446
log_add_expunge(ctx, data, hdr);
448
case MAIL_TRANSACTION_EXPUNGE_GUID:
450
log_add_expunge_guid(ctx, view, data, hdr);
452
case MAIL_TRANSACTION_FLAG_UPDATE:
453
log_add_flag_update(ctx, data, hdr);
455
case MAIL_TRANSACTION_KEYWORD_RESET:
456
log_add_keyword_reset(ctx, data, hdr);
458
case MAIL_TRANSACTION_KEYWORD_UPDATE:
460
log_add_keyword_update(ctx, data, hdr);
463
case MAIL_TRANSACTION_MODSEQ_UPDATE:
464
log_add_modseq_update(ctx, data, hdr, pvt_scan);
466
case MAIL_TRANSACTION_ATTRIBUTE_UPDATE:
467
cur_modseq = mail_transaction_log_view_get_prev_modseq(log_view);
468
log_add_attribute_update(ctx, data, hdr, cur_modseq);
474
ctx->last_log_seq = file_seq;
475
ctx->last_log_offset = file_offset;
477
mail_transaction_log_view_close(&log_view);
482
dsync_mailbox_attribute_cmp(const struct dsync_mailbox_attribute *attr1,
483
const struct dsync_mailbox_attribute *attr2)
485
if (attr1->type < attr2->type)
487
if (attr1->type > attr2->type)
489
return strcmp(attr1->key, attr2->key);
493
dsync_mailbox_attribute_hash(const struct dsync_mailbox_attribute *attr)
495
return str_hash(attr->key) ^ attr->type;
498
int dsync_transaction_log_scan_init(struct mail_index_view *view,
499
struct mail_index_view *pvt_view,
500
uint32_t highest_wanted_uid,
501
uint64_t modseq, uint64_t pvt_modseq,
502
struct dsync_transaction_log_scan **scan_r)
504
struct dsync_transaction_log_scan *ctx;
507
pool = pool_alloconly_create(MEMPOOL_GROWING"dsync transaction log scan",
509
ctx = p_new(pool, struct dsync_transaction_log_scan, 1);
511
hash_table_create_direct(&ctx->changes, pool, 0);
512
hash_table_create(&ctx->attr_changes, pool, 0,
513
dsync_mailbox_attribute_hash,
514
dsync_mailbox_attribute_cmp);
516
ctx->highest_wanted_uid = highest_wanted_uid;
518
if (dsync_log_scan(ctx, view, modseq, FALSE) < 0)
520
if (pvt_view != NULL) {
521
if (dsync_log_scan(ctx, pvt_view, pvt_modseq, TRUE) < 0)
529
HASH_TABLE_TYPE(dsync_uid_mail_change)
530
dsync_transaction_log_scan_get_hash(struct dsync_transaction_log_scan *scan)
532
return scan->changes;
535
HASH_TABLE_TYPE(dsync_attr_change)
536
dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan *scan)
538
return scan->attr_changes;
542
dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan *scan)
544
return scan->returned_all_changes;
547
struct dsync_mail_change *
548
dsync_transaction_log_scan_find_new_expunge(struct dsync_transaction_log_scan *scan,
551
struct mail_transaction_log_view *log_view;
552
const struct mail_transaction_header *hdr;
554
bool reset, found = FALSE;
558
if (scan->highest_wanted_uid < uid)
559
scan->highest_wanted_uid = uid;
561
log_view = mail_transaction_log_view_open(scan->view->index->log);
562
if (mail_transaction_log_view_set(log_view,
564
scan->last_log_offset,
565
(uint32_t)-1, (uoff_t)-1,
568
mail_transaction_log_view_next(log_view, &hdr, &data) > 0) {
569
switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
570
case MAIL_TRANSACTION_EXPUNGE:
571
if (log_add_expunge_uid(scan, data, hdr, uid))
574
case MAIL_TRANSACTION_EXPUNGE_GUID:
575
if (log_add_expunge_guid_uid(scan, data, hdr, uid))
581
mail_transaction_log_view_close(&log_view);
583
return !found ? NULL :
584
hash_table_lookup(scan->changes, POINTER_CAST(uid));
587
void dsync_transaction_log_scan_deinit(struct dsync_transaction_log_scan **_scan)
589
struct dsync_transaction_log_scan *scan = *_scan;
593
hash_table_destroy(&scan->changes);
594
hash_table_destroy(&scan->attr_changes);
595
pool_unref(&scan->pool);