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