1 /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "array.h"
6 #include "maildir-storage.h"
7 #include "index-sync-changes.h"
8 #include "maildir-uidlist.h"
9 #include "maildir-keywords.h"
10 #include "maildir-filename-flags.h"
11 #include "maildir-sync.h"
12 #include "mailbox-recent-flags.h"
13
14 #include <stdio.h>
15 #include <unistd.h>
16
17 struct maildir_index_sync_context {
18 struct maildir_mailbox *mbox;
19 struct maildir_sync_context *maildir_sync_ctx;
20
21 struct mail_index_view *view;
22 struct mail_index_sync_ctx *sync_ctx;
23 struct maildir_keywords_sync_ctx *keywords_sync_ctx;
24 struct mail_index_transaction *trans;
25
26 struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
27 struct index_sync_changes_context *sync_changes;
28 enum mail_flags flags;
29 ARRAY_TYPE(keyword_indexes) keywords, idx_keywords;
30
31 uint32_t uid;
32 bool update_maildir_hdr_cur;
33
34 time_t start_time;
35 unsigned int flag_change_count, expunge_count, new_msgs_count;
36 };
37
38 struct maildir_keywords_sync_ctx *
maildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context * ctx)39 maildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context *ctx)
40 {
41 return ctx->keywords_sync_ctx;
42 }
43
maildir_sync_set_new_msgs_count(struct maildir_index_sync_context * ctx,unsigned int count)44 void maildir_sync_set_new_msgs_count(struct maildir_index_sync_context *ctx,
45 unsigned int count)
46 {
47 ctx->new_msgs_count = count;
48 }
49
50 static bool
maildir_expunge_is_valid_guid(struct maildir_index_sync_context * ctx,uint32_t uid,const char * filename,guid_128_t expunged_guid_128)51 maildir_expunge_is_valid_guid(struct maildir_index_sync_context *ctx,
52 uint32_t uid, const char *filename,
53 guid_128_t expunged_guid_128)
54 {
55 guid_128_t guid_128;
56 const char *guid;
57
58 if (guid_128_is_empty(expunged_guid_128)) {
59 /* no GUID associated with expunge */
60 return TRUE;
61 }
62
63 T_BEGIN {
64 guid = maildir_uidlist_lookup_ext(ctx->mbox->uidlist, uid,
65 MAILDIR_UIDLIST_REC_EXT_GUID);
66 if (guid == NULL)
67 guid = t_strcut(filename, *MAILDIR_INFO_SEP_S);
68 mail_generate_guid_128_hash(guid, guid_128);
69 } T_END;
70
71 if (memcmp(guid_128, expunged_guid_128, sizeof(guid_128)) == 0)
72 return TRUE;
73
74 mailbox_set_critical(&ctx->mbox->box,
75 "Expunged GUID mismatch for UID %u: %s vs %s",
76 ctx->uid, guid_128_to_string(guid_128),
77 guid_128_to_string(expunged_guid_128));
78 return FALSE;
79 }
80
maildir_expunge(struct maildir_mailbox * mbox,const char * path,struct maildir_index_sync_context * ctx)81 static int maildir_expunge(struct maildir_mailbox *mbox, const char *path,
82 struct maildir_index_sync_context *ctx)
83 {
84 struct mailbox *box = &mbox->box;
85
86 ctx->expunge_count++;
87
88 if (unlink(path) == 0) {
89 mailbox_sync_notify(box, ctx->uid, MAILBOX_SYNC_TYPE_EXPUNGE);
90 return 1;
91 }
92 if (errno == ENOENT)
93 return 0;
94 if (UNLINK_EISDIR(errno))
95 return maildir_lose_unexpected_dir(box->storage, path);
96
97 mailbox_set_critical(&mbox->box, "unlink(%s) failed: %m", path);
98 return -1;
99 }
100
maildir_sync_flags(struct maildir_mailbox * mbox,const char * path,struct maildir_index_sync_context * ctx)101 static int maildir_sync_flags(struct maildir_mailbox *mbox, const char *path,
102 struct maildir_index_sync_context *ctx)
103 {
104 struct mailbox *box = &mbox->box;
105 struct stat st;
106 const char *dir, *fname, *newfname, *newpath;
107 enum mail_index_sync_type sync_type;
108 uint8_t flags8;
109
110 ctx->flag_change_count++;
111
112 fname = strrchr(path, '/');
113 i_assert(fname != NULL);
114 fname++;
115 dir = t_strdup_until(path, fname);
116
117 i_assert(*fname != '\0');
118
119 /* get the current flags and keywords */
120 maildir_filename_flags_get(ctx->keywords_sync_ctx,
121 fname, &ctx->flags, &ctx->keywords);
122
123 /* apply changes */
124 flags8 = ctx->flags;
125 index_sync_changes_apply(ctx->sync_changes, NULL,
126 &flags8, &ctx->keywords, &sync_type);
127 ctx->flags = flags8;
128
129 /* and try renaming with the new name */
130 newfname = maildir_filename_flags_kw_set(ctx->keywords_sync_ctx, fname,
131 ctx->flags, &ctx->keywords);
132 newpath = t_strconcat(dir, newfname, NULL);
133 if (strcmp(path, newpath) == 0) {
134 /* just make sure that the file still exists. avoid rename()
135 here because it's slow on HFS. */
136 if (stat(path, &st) < 0) {
137 if (errno == ENOENT)
138 return 0;
139 mailbox_set_critical(box, "stat(%s) failed: %m", path);
140 return -1;
141 }
142 } else {
143 if (rename(path, newpath) < 0) {
144 if (errno == ENOENT)
145 return 0;
146 if (!ENOSPACE(errno) && errno != EACCES) {
147 mailbox_set_critical(box,
148 "rename(%s, %s) failed: %m",
149 path, newpath);
150 }
151 return -1;
152 }
153 }
154 mailbox_sync_notify(box, ctx->uid, index_sync_type_convert(sync_type));
155 return 1;
156 }
157
maildir_handle_uid_insertion(struct maildir_index_sync_context * ctx,enum maildir_uidlist_rec_flag uflags,const char * filename,uint32_t uid)158 static int maildir_handle_uid_insertion(struct maildir_index_sync_context *ctx,
159 enum maildir_uidlist_rec_flag uflags,
160 const char *filename, uint32_t uid)
161 {
162 int ret;
163
164 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
165 /* partial syncing */
166 return 0;
167 }
168
169 /* most likely a race condition: we read the maildir, then someone else
170 expunged messages and committed changes to index. so, this message
171 shouldn't actually exist. */
172 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) == 0) {
173 /* mark it racy and check in next sync */
174 ctx->mbox->maildir_hdr.cur_check_time = 0;
175 maildir_sync_set_racing(ctx->maildir_sync_ctx);
176 maildir_uidlist_add_flags(ctx->mbox->uidlist, filename,
177 MAILDIR_UIDLIST_REC_FLAG_RACING);
178 return 0;
179 }
180
181 if (ctx->uidlist_sync_ctx == NULL) {
182 ret = maildir_uidlist_sync_init(ctx->mbox->uidlist,
183 MAILDIR_UIDLIST_SYNC_PARTIAL |
184 MAILDIR_UIDLIST_SYNC_KEEP_STATE,
185 &ctx->uidlist_sync_ctx);
186 if (ret <= 0)
187 return -1;
188 }
189
190 uflags &= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR;
191 maildir_uidlist_sync_remove(ctx->uidlist_sync_ctx, filename);
192 ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
193 filename, uflags);
194 i_assert(ret > 0);
195
196 /* give the new UID to it immediately */
197 maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);
198
199 i_warning("Maildir %s: Expunged message reappeared, giving a new UID "
200 "(old uid=%u, file=%s)%s", mailbox_get_path(&ctx->mbox->box),
201 uid, filename, !str_begins(filename, "msg.") ? "" :
202 " (Your MDA is saving MH files into Maildir?)");
203 return 0;
204 }
205
maildir_sync_index_begin(struct maildir_mailbox * mbox,struct maildir_sync_context * maildir_sync_ctx,struct maildir_index_sync_context ** ctx_r)206 int maildir_sync_index_begin(struct maildir_mailbox *mbox,
207 struct maildir_sync_context *maildir_sync_ctx,
208 struct maildir_index_sync_context **ctx_r)
209 {
210 struct mailbox *_box = &mbox->box;
211 struct maildir_index_sync_context *ctx;
212 struct mail_index_sync_ctx *sync_ctx;
213 struct mail_index_view *view;
214 struct mail_index_transaction *trans;
215 enum mail_index_sync_flags sync_flags;
216
217 sync_flags = index_storage_get_sync_flags(&mbox->box);
218 /* don't drop recent messages if we're saving messages */
219 if (maildir_sync_ctx == NULL)
220 sync_flags &= ENUM_NEGATE(MAIL_INDEX_SYNC_FLAG_DROP_RECENT);
221
222 if (index_storage_expunged_sync_begin(_box, &sync_ctx, &view,
223 &trans, sync_flags) < 0)
224 return -1;
225
226 ctx = i_new(struct maildir_index_sync_context, 1);
227 ctx->mbox = mbox;
228 ctx->maildir_sync_ctx = maildir_sync_ctx;
229 ctx->sync_ctx = sync_ctx;
230 ctx->view = view;
231 ctx->trans = trans;
232 ctx->keywords_sync_ctx =
233 maildir_keywords_sync_init(mbox->keywords, _box->index);
234 ctx->sync_changes =
235 index_sync_changes_init(ctx->sync_ctx, ctx->view, ctx->trans,
236 maildir_is_backend_readonly(mbox));
237 ctx->start_time = time(NULL);
238
239 *ctx_r = ctx;
240 return 0;
241 }
242
243 static bool
maildir_index_header_has_changed(const struct maildir_index_header * old_hdr,const struct maildir_index_header * new_hdr)244 maildir_index_header_has_changed(const struct maildir_index_header *old_hdr,
245 const struct maildir_index_header *new_hdr)
246 {
247 #define DIR_DELAYED_REFRESH(hdr, name) \
248 ((hdr)->name ## _check_time <= \
249 (hdr)->name ## _mtime + MAILDIR_SYNC_SECS)
250
251 if (old_hdr->new_mtime != new_hdr->new_mtime ||
252 old_hdr->new_mtime_nsecs != new_hdr->new_mtime_nsecs ||
253 old_hdr->cur_mtime != new_hdr->cur_mtime ||
254 old_hdr->cur_mtime_nsecs != new_hdr->cur_mtime_nsecs ||
255 old_hdr->uidlist_mtime != new_hdr->uidlist_mtime ||
256 old_hdr->uidlist_mtime_nsecs != new_hdr->uidlist_mtime_nsecs ||
257 old_hdr->uidlist_size != new_hdr->uidlist_size)
258 return TRUE;
259
260 return DIR_DELAYED_REFRESH(old_hdr, new) !=
261 DIR_DELAYED_REFRESH(new_hdr, new) ||
262 DIR_DELAYED_REFRESH(old_hdr, cur) !=
263 DIR_DELAYED_REFRESH(new_hdr, cur);
264 }
265
266 static void
maildir_sync_index_update_ext_header(struct maildir_index_sync_context * ctx)267 maildir_sync_index_update_ext_header(struct maildir_index_sync_context *ctx)
268 {
269 struct maildir_mailbox *mbox = ctx->mbox;
270 const char *cur_path;
271 const void *data;
272 size_t data_size;
273 struct stat st;
274
275 cur_path = t_strconcat(mailbox_get_path(&mbox->box), "/cur", NULL);
276 if (ctx->update_maildir_hdr_cur && stat(cur_path, &st) == 0) {
277 if ((time_t)mbox->maildir_hdr.cur_check_time < st.st_mtime)
278 mbox->maildir_hdr.cur_check_time = st.st_mtime;
279 mbox->maildir_hdr.cur_mtime = st.st_mtime;
280 mbox->maildir_hdr.cur_mtime_nsecs = ST_MTIME_NSEC(st);
281 }
282
283 mail_index_get_header_ext(mbox->box.view, mbox->maildir_ext_id,
284 &data, &data_size);
285 if (data_size != sizeof(mbox->maildir_hdr) ||
286 maildir_index_header_has_changed(data, &mbox->maildir_hdr)) {
287 mail_index_update_header_ext(ctx->trans, mbox->maildir_ext_id,
288 0, &mbox->maildir_hdr,
289 sizeof(mbox->maildir_hdr));
290 }
291 }
292
maildir_sync_index_finish(struct maildir_index_sync_context * ctx,bool success)293 static int maildir_sync_index_finish(struct maildir_index_sync_context *ctx,
294 bool success)
295 {
296 struct maildir_mailbox *mbox = ctx->mbox;
297 unsigned int time_diff;
298 int ret = success ? 0 : -1;
299
300 time_diff = time(NULL) - ctx->start_time;
301 if (time_diff >= MAILDIR_SYNC_TIME_WARN_SECS) {
302 i_warning("Maildir %s: Synchronization took %u seconds "
303 "(%u new msgs, %u flag change attempts, "
304 "%u expunge attempts)",
305 mailbox_get_path(&ctx->mbox->box), time_diff,
306 ctx->new_msgs_count, ctx->flag_change_count,
307 ctx->expunge_count);
308 mail_index_sync_no_warning(ctx->sync_ctx);
309 }
310
311 if (ret < 0)
312 mail_index_sync_rollback(&ctx->sync_ctx);
313 else {
314 maildir_sync_index_update_ext_header(ctx);
315
316 /* Set syncing_commit=TRUE so that if any sync callbacks try
317 to access mails which got lost (eg. expunge callback trying
318 to open the file which was just unlinked) we don't try to
319 start a second index sync and crash. */
320 mbox->syncing_commit = TRUE;
321 if (mail_index_sync_commit(&ctx->sync_ctx) < 0) {
322 mailbox_set_index_error(&mbox->box);
323 ret = -1;
324 }
325 mbox->syncing_commit = FALSE;
326 }
327
328 index_storage_expunging_deinit(&mbox->box);
329 maildir_keywords_sync_deinit(&ctx->keywords_sync_ctx);
330 index_sync_changes_deinit(&ctx->sync_changes);
331 i_free(ctx);
332 return ret;
333 }
334
maildir_sync_index_commit(struct maildir_index_sync_context ** _ctx)335 int maildir_sync_index_commit(struct maildir_index_sync_context **_ctx)
336 {
337 struct maildir_index_sync_context *ctx = *_ctx;
338
339 *_ctx = NULL;
340 return maildir_sync_index_finish(ctx, TRUE);
341 }
342
maildir_sync_index_rollback(struct maildir_index_sync_context ** _ctx)343 void maildir_sync_index_rollback(struct maildir_index_sync_context **_ctx)
344 {
345 struct maildir_index_sync_context *ctx = *_ctx;
346
347 *_ctx = NULL;
348 (void)maildir_sync_index_finish(ctx, FALSE);
349 }
350
uint_cmp(const unsigned int * i1,const unsigned int * i2)351 static int uint_cmp(const unsigned int *i1, const unsigned int *i2)
352 {
353 if (*i1 < *i2)
354 return -1;
355 else if (*i1 > *i2)
356 return 1;
357 else
358 return 0;
359 }
360
361 static void
maildir_sync_mail_keywords(struct maildir_index_sync_context * ctx,uint32_t seq)362 maildir_sync_mail_keywords(struct maildir_index_sync_context *ctx, uint32_t seq)
363 {
364 struct mailbox *box = &ctx->mbox->box;
365 struct mail_keywords *kw;
366 unsigned int i, j, old_count, new_count;
367 const unsigned int *old_indexes, *new_indexes;
368 bool have_indexonly_keywords;
369 int diff;
370
371 mail_index_lookup_keywords(ctx->view, seq, &ctx->idx_keywords);
372 if (index_keyword_array_cmp(&ctx->keywords, &ctx->idx_keywords)) {
373 /* no changes - we should get here usually */
374 return;
375 }
376
377 /* sort the keywords */
378 array_sort(&ctx->idx_keywords, uint_cmp);
379 array_sort(&ctx->keywords, uint_cmp);
380
381 /* drop keywords that are in index-only. we don't want to touch them. */
382 old_indexes = array_get(&ctx->idx_keywords, &old_count);
383 have_indexonly_keywords = FALSE;
384 for (i = old_count; i > 0; i--) {
385 if (maildir_keywords_idx_char(ctx->keywords_sync_ctx,
386 old_indexes[i-1]) == '\0') {
387 have_indexonly_keywords = TRUE;
388 array_delete(&ctx->idx_keywords, i-1, 1);
389 }
390 }
391
392 if (!have_indexonly_keywords) {
393 /* no index-only keywords found, so something changed.
394 just replace them all. */
395 kw = mail_index_keywords_create_from_indexes(box->index,
396 &ctx->keywords);
397 mail_index_update_keywords(ctx->trans, seq, MODIFY_REPLACE, kw);
398 mail_index_keywords_unref(&kw);
399 return;
400 }
401
402 /* check again if non-index-only keywords changed */
403 if (index_keyword_array_cmp(&ctx->keywords, &ctx->idx_keywords))
404 return;
405
406 /* we can't reset all the keywords or we'd drop indexonly keywords too.
407 so first remove the unwanted keywords and then add back the wanted
408 ones. we can get these lists easily by removing common elements
409 from old and new keywords. */
410 new_indexes = array_get(&ctx->keywords, &new_count);
411 for (i = j = 0; i < old_count && j < new_count; ) {
412 diff = (int)old_indexes[i] - (int)new_indexes[j];
413 if (diff == 0) {
414 array_delete(&ctx->keywords, j, 1);
415 array_delete(&ctx->idx_keywords, i, 1);
416 old_indexes = array_get(&ctx->idx_keywords, &old_count);
417 new_indexes = array_get(&ctx->keywords, &new_count);
418 } else if (diff < 0) {
419 i++;
420 } else {
421 j++;
422 }
423 }
424
425 if (array_count(&ctx->idx_keywords) > 0) {
426 kw = mail_index_keywords_create_from_indexes(box->index,
427 &ctx->idx_keywords);
428 mail_index_update_keywords(ctx->trans, seq, MODIFY_REMOVE, kw);
429 mail_index_keywords_unref(&kw);
430 }
431
432 if (array_count(&ctx->keywords) > 0) {
433 kw = mail_index_keywords_create_from_indexes(box->index,
434 &ctx->keywords);
435 mail_index_update_keywords(ctx->trans, seq, MODIFY_ADD, kw);
436 mail_index_keywords_unref(&kw);
437 }
438 }
439
maildir_sync_index(struct maildir_index_sync_context * ctx,bool partial)440 int maildir_sync_index(struct maildir_index_sync_context *ctx,
441 bool partial)
442 {
443 struct maildir_mailbox *mbox = ctx->mbox;
444 struct mail_index_view *view = ctx->view;
445 struct mail_index_view *view2;
446 struct maildir_uidlist_iter_ctx *iter;
447 struct mail_index_transaction *trans = ctx->trans;
448 const struct mail_index_header *hdr;
449 struct mail_index_header empty_hdr;
450 const struct mail_index_record *rec;
451 uint32_t seq, seq2, uid, prev_uid;
452 enum maildir_uidlist_rec_flag uflags;
453 const char *filename;
454 uint32_t uid_validity, next_uid, hdr_next_uid, first_recent_uid;
455 uint32_t first_uid;
456 unsigned int changes = 0;
457 int ret = 0;
458 time_t time_before_sync;
459 guid_128_t expunged_guid_128;
460 enum mail_flags private_flags_mask;
461 bool expunged, full_rescan = FALSE;
462
463 i_assert(!mbox->syncing_commit);
464
465 first_uid = 1;
466 hdr = mail_index_get_header(view);
467 uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
468 if (uid_validity != hdr->uid_validity &&
469 uid_validity != 0 && hdr->uid_validity != 0) {
470 /* uidvalidity changed and index isn't being synced for the
471 first time, reset the index so we can add all messages as
472 new */
473 i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
474 mailbox_get_path(&ctx->mbox->box),
475 hdr->uid_validity, uid_validity);
476 mail_index_reset(trans);
477 mailbox_recent_flags_reset(&mbox->box);
478
479 first_uid = hdr->messages_count + 1;
480 i_zero(&empty_hdr);
481 empty_hdr.next_uid = 1;
482 hdr = &empty_hdr;
483 }
484 hdr_next_uid = hdr->next_uid;
485
486 ctx->mbox->box.tmp_sync_view = view;
487 private_flags_mask = mailbox_get_private_flags_mask(&mbox->box);
488 time_before_sync = time(NULL);
489 mbox->syncing_commit = TRUE;
490 seq = prev_uid = 0; first_recent_uid = I_MAX(hdr->first_recent_uid, 1);
491 i_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS);
492 i_array_init(&ctx->idx_keywords, MAILDIR_MAX_KEYWORDS);
493 iter = maildir_uidlist_iter_init(mbox->uidlist);
494 while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
495 maildir_filename_flags_get(ctx->keywords_sync_ctx, filename,
496 &ctx->flags, &ctx->keywords);
497
498 i_assert(uid > prev_uid);
499 prev_uid = uid;
500
501 /* the private flags are kept only in indexes. don't use them
502 at all even for newly seen mails */
503 ctx->flags &= ENUM_NEGATE(private_flags_mask);
504
505 again:
506 seq++;
507 ctx->uid = uid;
508
509 if (seq > hdr->messages_count) {
510 if (uid < hdr_next_uid) {
511 if (maildir_handle_uid_insertion(ctx, uflags,
512 filename,
513 uid) < 0)
514 ret = -1;
515 seq--;
516 continue;
517 }
518
519 /* Trust uidlist recent flags only for newly added
520 messages. When saving/copying messages with flags
521 they're stored to cur/ and uidlist treats them
522 as non-recent. */
523 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) == 0) {
524 if (uid >= first_recent_uid)
525 first_recent_uid = uid + 1;
526 }
527
528 hdr_next_uid = uid + 1;
529 mail_index_append(trans, uid, &seq);
530 mail_index_update_flags(trans, seq, MODIFY_REPLACE,
531 ctx->flags);
532 if (array_count(&ctx->keywords) > 0) {
533 struct mail_keywords *kw;
534
535 kw = mail_index_keywords_create_from_indexes(
536 mbox->box.index, &ctx->keywords);
537 mail_index_update_keywords(trans, seq,
538 MODIFY_REPLACE, kw);
539 mail_index_keywords_unref(&kw);
540 }
541 continue;
542 }
543
544 rec = mail_index_lookup(view, seq);
545 if (uid > rec->uid) {
546 /* already expunged (no point in showing guid in the
547 expunge record anymore) */
548 mail_index_expunge(ctx->trans, seq);
549 goto again;
550 }
551
552 if (uid < rec->uid) {
553 if (maildir_handle_uid_insertion(ctx, uflags,
554 filename, uid) < 0)
555 ret = -1;
556 seq--;
557 continue;
558 }
559
560 index_sync_changes_read(ctx->sync_changes, ctx->uid, &expunged,
561 expunged_guid_128);
562 if (expunged) {
563 if (!maildir_expunge_is_valid_guid(ctx, ctx->uid,
564 filename,
565 expunged_guid_128))
566 continue;
567 if (maildir_file_do(mbox, ctx->uid,
568 maildir_expunge, ctx) >= 0) {
569 /* successful expunge */
570 mail_index_expunge(ctx->trans, seq);
571 }
572 if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
573 maildir_sync_notify(ctx->maildir_sync_ctx);
574 continue;
575 }
576
577 /* the private flags are stored only in indexes, keep them */
578 ctx->flags |= rec->flags & private_flags_mask;
579
580 if (index_sync_changes_have(ctx->sync_changes)) {
581 /* apply flag changes to maildir */
582 if (maildir_file_do(mbox, ctx->uid,
583 maildir_sync_flags, ctx) < 0)
584 ctx->flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
585 if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
586 maildir_sync_notify(ctx->maildir_sync_ctx);
587 }
588
589 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
590 /* partial syncing */
591 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) {
592 /* we last saw this mail in new/, but it's
593 not there anymore. possibly expunged,
594 make sure. */
595 full_rescan = TRUE;
596 }
597 continue;
598 }
599
600 if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
601 /* we haven't been able to update maildir with this
602 record's flag changes. don't sync them. */
603 continue;
604 }
605
606 if (ctx->flags != (rec->flags & MAIL_FLAGS_NONRECENT)) {
607 mail_index_update_flags(trans, seq, MODIFY_REPLACE,
608 ctx->flags);
609 }
610
611 maildir_sync_mail_keywords(ctx, seq);
612 }
613 maildir_uidlist_iter_deinit(&iter);
614
615 if (!partial) {
616 /* expunge the rest */
617 for (seq++; seq <= hdr->messages_count; seq++)
618 mail_index_expunge(ctx->trans, seq);
619 }
620
621 /* add \Recent flags. use updated view so it contains newly
622 appended messages. */
623 view2 = mail_index_transaction_open_updated_view(trans);
624 if (mail_index_lookup_seq_range(view2, first_recent_uid, (uint32_t)-1,
625 &seq, &seq2) && seq2 >= first_uid) {
626 if (seq < first_uid) {
627 /* UIDVALIDITY changed, skip over the old messages */
628 seq = first_uid;
629 }
630 mailbox_recent_flags_set_seqs(&mbox->box, view2, seq, seq2);
631 }
632 mail_index_view_close(&view2);
633
634 if (ctx->uidlist_sync_ctx != NULL) {
635 if (maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx,
636 TRUE) < 0)
637 ret = -1;
638 }
639
640 mailbox_sync_notify(&mbox->box, 0, 0);
641 ctx->mbox->box.tmp_sync_view = NULL;
642
643 /* check cur/ mtime later. if we came here from saving messages they
644 could still be moved to cur/ directory. */
645 ctx->update_maildir_hdr_cur = TRUE;
646 mbox->maildir_hdr.cur_check_time = time_before_sync;
647
648 if (uid_validity == 0) {
649 uid_validity = hdr->uid_validity != 0 ? hdr->uid_validity :
650 maildir_get_uidvalidity_next(mbox->box.list);
651 maildir_uidlist_set_uid_validity(mbox->uidlist, uid_validity);
652 }
653 maildir_uidlist_set_next_uid(mbox->uidlist, hdr_next_uid, FALSE);
654
655 if (uid_validity != hdr->uid_validity) {
656 mail_index_update_header(trans,
657 offsetof(struct mail_index_header, uid_validity),
658 &uid_validity, sizeof(uid_validity), TRUE);
659 }
660
661 next_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
662 if (hdr_next_uid < next_uid) {
663 mail_index_update_header(trans,
664 offsetof(struct mail_index_header, next_uid),
665 &next_uid, sizeof(next_uid), FALSE);
666 }
667
668 i_assert(hdr->first_recent_uid <= first_recent_uid);
669 if (hdr->first_recent_uid < first_recent_uid) {
670 mail_index_update_header(ctx->trans,
671 offsetof(struct mail_index_header, first_recent_uid),
672 &first_recent_uid, sizeof(first_recent_uid), FALSE);
673 }
674 array_free(&ctx->keywords);
675 array_free(&ctx->idx_keywords);
676 mbox->syncing_commit = FALSE;
677 return ret < 0 ? -1 : (full_rescan ? 0 : 1);
678 }
679
680 static unsigned int
maildir_list_get_ext_id(struct maildir_mailbox * mbox,struct mail_index_view * view)681 maildir_list_get_ext_id(struct maildir_mailbox *mbox,
682 struct mail_index_view *view)
683 {
684 if (mbox->maildir_list_index_ext_id == (uint32_t)-1) {
685 mbox->maildir_list_index_ext_id =
686 mail_index_ext_register(mail_index_view_get_index(view),
687 "maildir", 0,
688 sizeof(struct maildir_list_index_record),
689 sizeof(uint32_t));
690 }
691 return mbox->maildir_list_index_ext_id;
692 }
693
maildir_list_index_has_changed(struct mailbox * box,struct mail_index_view * list_view,uint32_t seq,bool quick)694 int maildir_list_index_has_changed(struct mailbox *box,
695 struct mail_index_view *list_view,
696 uint32_t seq, bool quick)
697 {
698 struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
699 const struct maildir_list_index_record *rec;
700 const void *data;
701 const char *root_dir, *new_dir, *cur_dir;
702 struct stat st;
703 uint32_t ext_id;
704 bool expunged;
705 int ret;
706
707 ret = index_storage_list_index_has_changed(box, list_view, seq, quick);
708 if (ret != 0 || box->storage->set->mailbox_list_index_very_dirty_syncs)
709 return ret;
710 if (mbox->storage->set->maildir_very_dirty_syncs) {
711 /* we don't track cur/new directories with dirty syncs */
712 return 0;
713 }
714
715 ext_id = maildir_list_get_ext_id(mbox, list_view);
716 mail_index_lookup_ext(list_view, seq, ext_id, &data, &expunged);
717 rec = data;
718
719 if (rec == NULL || expunged ||
720 rec->new_mtime == 0 || rec->cur_mtime == 0) {
721 /* doesn't exist, not synced or dirty-synced */
722 return 1;
723 }
724
725 ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
726 &root_dir);
727 if (ret < 0)
728 return ret;
729 i_assert(ret > 0);
730
731 /* check if new/ changed */
732 new_dir = t_strconcat(root_dir, "/new", NULL);
733 if (stat(new_dir, &st) < 0) {
734 mailbox_set_critical(box, "stat(%s) failed: %m", new_dir);
735 return -1;
736 }
737 if ((time_t)rec->new_mtime != st.st_mtime)
738 return 1;
739
740 /* check if cur/ changed */
741 cur_dir = t_strconcat(root_dir, "/cur", NULL);
742 if (stat(cur_dir, &st) < 0) {
743 mailbox_set_critical(box, "stat(%s) failed: %m", cur_dir);
744 return -1;
745 }
746 if ((time_t)rec->cur_mtime != st.st_mtime)
747 return 1;
748 return 0;
749 }
750
maildir_list_index_update_sync(struct mailbox * box,struct mail_index_transaction * trans,uint32_t seq)751 void maildir_list_index_update_sync(struct mailbox *box,
752 struct mail_index_transaction *trans,
753 uint32_t seq)
754 {
755 struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
756 struct mail_index_view *list_view;
757 const struct maildir_index_header *mhdr = &mbox->maildir_hdr;
758 const struct maildir_list_index_record *old_rec;
759 struct maildir_list_index_record new_rec;
760 const void *data;
761 uint32_t ext_id;
762 bool expunged;
763
764 index_storage_list_index_update_sync(box, trans, seq);
765 if (mbox->storage->set->maildir_very_dirty_syncs) {
766 /* we don't track cur/new directories with dirty syncs */
767 return;
768 }
769
770 /* get the current record */
771 list_view = mail_index_transaction_get_view(trans);
772 ext_id = maildir_list_get_ext_id(mbox, list_view);
773 mail_index_lookup_ext(list_view, seq, ext_id, &data, &expunged);
774 if (expunged)
775 return;
776 old_rec = data;
777
778 i_zero(&new_rec);
779 if (mhdr->new_check_time <= mhdr->new_mtime + MAILDIR_SYNC_SECS ||
780 mhdr->cur_check_time <= mhdr->cur_mtime + MAILDIR_SYNC_SECS) {
781 /* dirty, we need a refresh next time */
782 } else {
783 new_rec.new_mtime = mhdr->new_mtime;
784 new_rec.cur_mtime = mhdr->cur_mtime;
785 }
786
787 if (old_rec == NULL ||
788 memcmp(old_rec, &new_rec, sizeof(*old_rec)) != 0)
789 mail_index_update_ext(trans, seq, ext_id, &new_rec, NULL);
790 }
791