1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "hash.h"
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"
10 
11 struct dsync_transaction_log_scan {
12 	pool_t pool;
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;
17 
18 	uint32_t last_log_seq;
19 	uoff_t last_log_offset;
20 
21 	bool returned_all_changes;
22 };
23 
24 static bool ATTR_NOWARN_UNUSED_RESULT
export_change_get(struct dsync_transaction_log_scan * ctx,uint32_t uid,enum dsync_mail_change_type type,struct dsync_mail_change ** change_r)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)
28 {
29 	struct dsync_mail_change *change;
30 	const char *orig_guid;
31 
32 	i_assert(uid > 0);
33 	i_assert(type != DSYNC_MAIL_CHANGE_TYPE_SAVE);
34 
35 	*change_r = NULL;
36 
37 	if (uid > ctx->highest_wanted_uid)
38 		return FALSE;
39 
40 	change = hash_table_lookup(ctx->changes, POINTER_CAST(uid));
41 	if (change == NULL) {
42 		/* first change for this UID */
43 		change = p_new(ctx->pool, struct dsync_mail_change, 1);
44 		change->uid = uid;
45 		change->type = type;
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 		i_zero(change);
51 		change->type = type;
52 		change->uid = uid;
53 		change->guid = orig_guid;
54 	} else if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) {
55 		/* already expunged, this change doesn't matter */
56 		return FALSE;
57 	} else {
58 		/* another flag update */
59 	}
60 	*change_r = change;
61 	return TRUE;
62 }
63 
64 static void
log_add_expunge(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr)65 log_add_expunge(struct dsync_transaction_log_scan *ctx, const void *data,
66 		const struct mail_transaction_header *hdr)
67 {
68 	const struct mail_transaction_expunge *rec = data, *end;
69 	struct dsync_mail_change *change;
70 	uint32_t uid;
71 
72 	if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
73 		/* this is simply a request for expunge */
74 		return;
75 	}
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,
81 					  &change);
82 		}
83 	}
84 }
85 
86 static bool
log_add_expunge_uid(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr,uint32_t uid)87 log_add_expunge_uid(struct dsync_transaction_log_scan *ctx, const void *data,
88 		    const struct mail_transaction_header *hdr, uint32_t uid)
89 {
90 	const struct mail_transaction_expunge *rec = data, *end;
91 	struct dsync_mail_change *change;
92 
93 	if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
94 		/* this is simply a request for expunge */
95 		return FALSE;
96 	}
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,
102 					  &change);
103 			return TRUE;
104 		}
105 	}
106 	return FALSE;
107 }
108 
109 static void
log_add_expunge_guid(struct dsync_transaction_log_scan * ctx,struct mail_index_view * view,const void * data,const struct mail_transaction_header * hdr)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)
113 {
114 	const struct mail_transaction_expunge_guid *rec = data, *end;
115 	struct dsync_mail_change *change;
116 	uint32_t seq;
117 	bool external;
118 
119 	external = (hdr->type & MAIL_TRANSACTION_EXTERNAL) != 0;
120 
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. */
127 			continue;
128 		}
129 		if (export_change_get(ctx, rec->uid,
130 				      DSYNC_MAIL_CHANGE_TYPE_EXPUNGE,
131 				      &change) &&
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));
135 		} T_END;
136 	}
137 }
138 
139 static bool
log_add_expunge_guid_uid(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr,uint32_t uid)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)
142 {
143 	const struct mail_transaction_expunge_guid *rec = data, *end;
144 	struct dsync_mail_change *change;
145 
146 	/* we're assuming UID is already known to be expunged */
147 	end = CONST_PTR_OFFSET(data, hdr->size);
148 	for (; rec != end; rec++) {
149 		if (rec->uid != uid)
150 			continue;
151 
152 		if (!export_change_get(ctx, rec->uid,
153 				       DSYNC_MAIL_CHANGE_TYPE_EXPUNGE,
154 				       &change))
155 			i_unreached();
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));
159 		} T_END;
160 		return TRUE;
161 	}
162 	return FALSE;
163 }
164 
165 static void
log_add_flag_update(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr)166 log_add_flag_update(struct dsync_transaction_log_scan *ctx, const void *data,
167 		    const struct mail_transaction_header *hdr)
168 {
169 	const struct mail_transaction_flag_update *rec = data, *end;
170 	struct dsync_mail_change *change;
171 	uint32_t uid;
172 
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,
178 					&change)) {
179 				change->add_flags |= rec->add_flags;
180 				change->remove_flags &= ENUM_NEGATE(rec->add_flags);
181 				change->remove_flags |= rec->remove_flags;
182 				change->add_flags &= ENUM_NEGATE(rec->remove_flags);
183 			}
184 		}
185 	}
186 }
187 
188 static void
log_add_keyword_reset(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr)189 log_add_keyword_reset(struct dsync_transaction_log_scan *ctx, const void *data,
190 		      const struct mail_transaction_header *hdr)
191 {
192 	const struct mail_transaction_keyword_reset *rec = data, *end;
193 	struct dsync_mail_change *change;
194 	uint32_t uid;
195 
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,
201 					&change))
202 				continue;
203 
204 			change->keywords_reset = TRUE;
205 			if (array_is_created(&change->keyword_changes))
206 				array_clear(&change->keyword_changes);
207 		}
208 	}
209 }
210 
211 static void
keywords_change_remove(struct dsync_mail_change * change,const char * name)212 keywords_change_remove(struct dsync_mail_change *change, const char *name)
213 {
214 	const char *const *changes;
215 	unsigned int i, count;
216 
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);
221 			break;
222 		}
223 	}
224 }
225 
226 static void
log_add_keyword_update(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr)227 log_add_keyword_update(struct dsync_transaction_log_scan *ctx, const void *data,
228 		       const struct mail_transaction_header *hdr)
229 {
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;
235 	uint32_t uid;
236 
237 	uids_offset = sizeof(*rec) + rec->name_size;
238 	if ((uids_offset % 4) != 0)
239 		uids_offset += 4 - (uids_offset % 4);
240 
241 	kw_name = t_strndup((const void *)(rec+1), rec->name_size);
242 	switch (rec->modify_type) {
243 	case MODIFY_ADD:
244 		change_str = p_strdup_printf(ctx->pool, "%c%s",
245 					     KEYWORD_CHANGE_ADD, kw_name);
246 		break;
247 	case MODIFY_REMOVE:
248 		change_str = p_strdup_printf(ctx->pool, "%c%s",
249 					     KEYWORD_CHANGE_REMOVE, kw_name);
250 		break;
251 	default:
252 		i_unreached();
253 	}
254 
255 	uids = CONST_PTR_OFFSET(rec, uids_offset);
256 	end = CONST_PTR_OFFSET(rec, hdr->size);
257 
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,
262 					&change))
263 				continue;
264 			if (!array_is_created(&change->keyword_changes)) {
265 				p_array_init(&change->keyword_changes,
266 					     ctx->pool, 4);
267 			} else {
268 				keywords_change_remove(change, kw_name);
269 			}
270 			array_push_back(&change->keyword_changes, &change_str);
271 		}
272 	}
273 }
274 
275 static void
log_add_modseq_update(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr,bool pvt_scan)276 log_add_modseq_update(struct dsync_transaction_log_scan *ctx, const void *data,
277 		      const struct mail_transaction_header *hdr, bool pvt_scan)
278 {
279 	const struct mail_transaction_modseq_update *rec = data, *end;
280 	struct dsync_mail_change *change;
281 	uint64_t modseq;
282 
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++) {
286 		if (rec->uid == 0) {
287 			/* highestmodseq update */
288 			continue;
289 		}
290 
291 		if (!export_change_get(ctx, rec->uid,
292 				       DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE,
293 				       &change))
294 			continue;
295 
296 		modseq = rec->modseq_low32 |
297 			((uint64_t)rec->modseq_high32 << 32);
298 		if (!pvt_scan) {
299 			if (change->modseq < modseq)
300 				change->modseq = modseq;
301 		} else {
302 			if (change->pvt_modseq < modseq)
303 				change->pvt_modseq = modseq;
304 		}
305 	}
306 }
307 
308 static void
log_add_attribute_update_key(struct dsync_transaction_log_scan * ctx,const char * attr_change,uint64_t modseq)309 log_add_attribute_update_key(struct dsync_transaction_log_scan *ctx,
310 			     const char *attr_change, uint64_t modseq)
311 {
312 	struct dsync_mailbox_attribute lookup_attr, *attr;
313 
314 	i_assert(strlen(attr_change) > 2); /* checked by lib-index */
315 
316 	lookup_attr.type = attr_change[1] == 'p' ?
317 		MAIL_ATTRIBUTE_TYPE_PRIVATE : MAIL_ATTRIBUTE_TYPE_SHARED;
318 	lookup_attr.key = attr_change+2;
319 
320 	attr = hash_table_lookup(ctx->attr_changes, &lookup_attr);
321 	if (attr == NULL) {
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);
326 	}
327 	attr->deleted = attr_change[0] == '-';
328 	attr->modseq = modseq;
329 }
330 
331 static void
log_add_attribute_update(struct dsync_transaction_log_scan * ctx,const void * data,const struct mail_transaction_header * hdr,uint64_t modseq)332 log_add_attribute_update(struct dsync_transaction_log_scan *ctx,
333 			 const void *data,
334 			 const struct mail_transaction_header *hdr,
335 			 uint64_t modseq)
336 {
337 	const char *attr_changes = data;
338 	unsigned int i;
339 
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;
343 	}
344 }
345 
346 static int
dsync_log_set(struct dsync_transaction_log_scan * ctx,struct mail_index_view * view,bool pvt_scan,struct mail_transaction_log_view * log_view,uint64_t modseq)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)
350 {
351 	uint32_t log_seq, end_seq;
352 	uoff_t log_offset, end_offset;
353 	const char *reason;
354 	bool reset;
355 	int ret;
356 
357 	end_seq = view->log_file_head_seq;
358 	end_offset = view->log_file_head_offset;
359 
360 	if (modseq != 0 &&
361 	    mail_index_modseq_get_next_log_offset(view, modseq,
362 						  &log_seq, &log_offset)) {
363 		/* scan the view only up to end of the current view.
364 		   if there are more changes, we don't care about them until
365 		   the next sync. */
366 		ret = mail_transaction_log_view_set(log_view,
367 						    log_seq, log_offset,
368 						    end_seq, end_offset,
369 						    &reset, &reason);
370 		if (ret != 0)
371 			return ret;
372 	}
373 
374 	/* return everything we've got (until the end of the view) */
375 	if (!pvt_scan)
376 		ctx->returned_all_changes = TRUE;
377 	if (mail_transaction_log_view_set_all(log_view) < 0)
378 		return -1;
379 
380 	mail_transaction_log_view_get_prev_pos(log_view, &log_seq, &log_offset);
381 	if (log_seq > end_seq ||
382 	    (log_seq == end_seq && log_offset > end_offset)) {
383 		end_seq = log_seq;
384 		end_offset = log_offset;
385 	}
386 	ret = mail_transaction_log_view_set(log_view,
387 					    log_seq, log_offset,
388 					    end_seq, end_offset,
389 					    &reset, &reason);
390 	if (ret == 0) {
391 		/* we shouldn't get here. _view_set_all() already
392 		   reserved all the log files, the _view_set() only
393 		   removed unwanted ones. */
394 		i_error("%s: Couldn't set transaction log view (seq %u..%u): %s",
395 			view->index->filepath, log_seq, end_seq, reason);
396 		ret = -1;
397 	}
398 	if (ret < 0)
399 		return -1;
400 	if (modseq != 0) {
401 		/* we didn't see all the changes that we wanted to */
402 		return 0;
403 	}
404 	return 1;
405 }
406 
407 static int
dsync_log_scan(struct dsync_transaction_log_scan * ctx,struct mail_index_view * view,uint64_t modseq,bool pvt_scan)408 dsync_log_scan(struct dsync_transaction_log_scan *ctx,
409 	       struct mail_index_view *view, uint64_t modseq, bool pvt_scan)
410 {
411 	struct mail_transaction_log_view *log_view;
412 	const struct mail_transaction_header *hdr;
413 	const void *data;
414 	uint32_t file_seq, max_seq;
415 	uoff_t file_offset, max_offset;
416 	uint64_t cur_modseq;
417 	int ret;
418 
419 	log_view = mail_transaction_log_view_open(view->index->log);
420 	if ((ret = dsync_log_set(ctx, view, pvt_scan, log_view, modseq)) < 0) {
421 		mail_transaction_log_view_close(&log_view);
422 		return -1;
423 	}
424 
425 	/* read the log only up to current position in view */
426 	max_seq = view->log_file_expunge_seq;
427 	max_offset = view->log_file_expunge_offset;
428 
429 	mail_transaction_log_view_get_prev_pos(log_view, &file_seq,
430 					       &file_offset);
431 
432 	while (mail_transaction_log_view_next(log_view, &hdr, &data) > 0) {
433 		mail_transaction_log_view_get_prev_pos(log_view, &file_seq,
434 						       &file_offset);
435 		if (file_offset >= max_offset && file_seq == max_seq)
436 			break;
437 
438 		if ((hdr->type & MAIL_TRANSACTION_SYNC) != 0) {
439 			/* ignore changes done by dsync, unless we can get
440 			   expunged message's GUID from it */
441 			if ((hdr->type & MAIL_TRANSACTION_TYPE_MASK) !=
442 			    MAIL_TRANSACTION_EXPUNGE_GUID)
443 				continue;
444 		}
445 
446 		switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
447 		case MAIL_TRANSACTION_EXPUNGE:
448 			if (!pvt_scan)
449 				log_add_expunge(ctx, data, hdr);
450 			break;
451 		case MAIL_TRANSACTION_EXPUNGE_GUID:
452 			if (!pvt_scan)
453 				log_add_expunge_guid(ctx, view, data, hdr);
454 			break;
455 		case MAIL_TRANSACTION_FLAG_UPDATE:
456 			log_add_flag_update(ctx, data, hdr);
457 			break;
458 		case MAIL_TRANSACTION_KEYWORD_RESET:
459 			log_add_keyword_reset(ctx, data, hdr);
460 			break;
461 		case MAIL_TRANSACTION_KEYWORD_UPDATE:
462 			T_BEGIN {
463 				log_add_keyword_update(ctx, data, hdr);
464 			} T_END;
465 			break;
466 		case MAIL_TRANSACTION_MODSEQ_UPDATE:
467 			log_add_modseq_update(ctx, data, hdr, pvt_scan);
468 			break;
469 		case MAIL_TRANSACTION_ATTRIBUTE_UPDATE:
470 			cur_modseq = mail_transaction_log_view_get_prev_modseq(log_view);
471 			log_add_attribute_update(ctx, data, hdr, cur_modseq);
472 			break;
473 		}
474 	}
475 
476 	if (!pvt_scan) {
477 		ctx->last_log_seq = file_seq;
478 		ctx->last_log_offset = file_offset;
479 	}
480 	mail_transaction_log_view_close(&log_view);
481 	return ret;
482 }
483 
484 static int
dsync_mailbox_attribute_cmp(const struct dsync_mailbox_attribute * attr1,const struct dsync_mailbox_attribute * attr2)485 dsync_mailbox_attribute_cmp(const struct dsync_mailbox_attribute *attr1,
486 			    const struct dsync_mailbox_attribute *attr2)
487 {
488 	if (attr1->type < attr2->type)
489 		return -1;
490 	if (attr1->type > attr2->type)
491 		return 1;
492 	return strcmp(attr1->key, attr2->key);
493 }
494 
495 static unsigned int
dsync_mailbox_attribute_hash(const struct dsync_mailbox_attribute * attr)496 dsync_mailbox_attribute_hash(const struct dsync_mailbox_attribute *attr)
497 {
498 	return str_hash(attr->key) ^ attr->type;
499 }
500 
dsync_transaction_log_scan_init(struct mail_index_view * view,struct mail_index_view * pvt_view,uint32_t highest_wanted_uid,uint64_t modseq,uint64_t pvt_modseq,struct dsync_transaction_log_scan ** scan_r,bool * pvt_too_old_r)501 int dsync_transaction_log_scan_init(struct mail_index_view *view,
502 				    struct mail_index_view *pvt_view,
503 				    uint32_t highest_wanted_uid,
504 				    uint64_t modseq, uint64_t pvt_modseq,
505 				    struct dsync_transaction_log_scan **scan_r,
506 				    bool *pvt_too_old_r)
507 {
508 	struct dsync_transaction_log_scan *ctx;
509 	pool_t pool;
510 	int ret, ret2;
511 
512 	*pvt_too_old_r = FALSE;
513 
514 	pool = pool_alloconly_create(MEMPOOL_GROWING"dsync transaction log scan",
515 				     10240);
516 	ctx = p_new(pool, struct dsync_transaction_log_scan, 1);
517 	ctx->pool = pool;
518 	hash_table_create_direct(&ctx->changes, pool, 0);
519 	hash_table_create(&ctx->attr_changes, pool, 0,
520 			  dsync_mailbox_attribute_hash,
521 			  dsync_mailbox_attribute_cmp);
522 	ctx->view = view;
523 	ctx->highest_wanted_uid = highest_wanted_uid;
524 
525 	if ((ret = dsync_log_scan(ctx, view, modseq, FALSE)) < 0)
526 		return -1;
527 	if (pvt_view != NULL) {
528 		if ((ret2 = dsync_log_scan(ctx, pvt_view, pvt_modseq, TRUE)) < 0)
529 			return -1;
530 		if (ret2 == 0) {
531 			ret = 0;
532 			*pvt_too_old_r = TRUE;
533 		}
534 	}
535 
536 	*scan_r = ctx;
537 	return ret;
538 }
539 
540 HASH_TABLE_TYPE(dsync_uid_mail_change)
dsync_transaction_log_scan_get_hash(struct dsync_transaction_log_scan * scan)541 dsync_transaction_log_scan_get_hash(struct dsync_transaction_log_scan *scan)
542 {
543 	return scan->changes;
544 }
545 
546 HASH_TABLE_TYPE(dsync_attr_change)
dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan * scan)547 dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan *scan)
548 {
549 	return scan->attr_changes;
550 }
551 
552 bool
dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan * scan)553 dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan *scan)
554 {
555 	return scan->returned_all_changes;
556 }
557 
558 struct dsync_mail_change *
dsync_transaction_log_scan_find_new_expunge(struct dsync_transaction_log_scan * scan,uint32_t uid)559 dsync_transaction_log_scan_find_new_expunge(struct dsync_transaction_log_scan *scan,
560 					    uint32_t uid)
561 {
562 	struct mail_transaction_log_view *log_view;
563 	const struct mail_transaction_header *hdr;
564 	const void *data;
565 	const char *reason;
566 	bool reset, found = FALSE;
567 
568 	i_assert(uid > 0);
569 
570 	if (scan->highest_wanted_uid < uid)
571 		scan->highest_wanted_uid = uid;
572 
573 	log_view = mail_transaction_log_view_open(scan->view->index->log);
574 	if (mail_transaction_log_view_set(log_view,
575 					  scan->last_log_seq,
576 					  scan->last_log_offset,
577 					  (uint32_t)-1, UOFF_T_MAX,
578 					  &reset, &reason) > 0) {
579 		while (!found &&
580 		       mail_transaction_log_view_next(log_view, &hdr, &data) > 0) {
581 			switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
582 			case MAIL_TRANSACTION_EXPUNGE:
583 				if (log_add_expunge_uid(scan, data, hdr, uid))
584 					found = TRUE;
585 				break;
586 			case MAIL_TRANSACTION_EXPUNGE_GUID:
587 				if (log_add_expunge_guid_uid(scan, data, hdr, uid))
588 					found = TRUE;
589 				break;
590 			}
591 		}
592 	}
593 	mail_transaction_log_view_close(&log_view);
594 
595 	return !found ? NULL :
596 		hash_table_lookup(scan->changes, POINTER_CAST(uid));
597 }
598 
dsync_transaction_log_scan_deinit(struct dsync_transaction_log_scan ** _scan)599 void dsync_transaction_log_scan_deinit(struct dsync_transaction_log_scan **_scan)
600 {
601 	struct dsync_transaction_log_scan *scan = *_scan;
602 
603 	*_scan = NULL;
604 
605 	hash_table_destroy(&scan->changes);
606 	hash_table_destroy(&scan->attr_changes);
607 	pool_unref(&scan->pool);
608 }
609