1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "mail-index-private.h"
6 #include "mail-index-modseq.h"
7 #include "mail-transaction-log-private.h"
8 #include "mail-index-transaction-private.h"
9 
10 struct mail_index_export_context {
11 	struct mail_index_transaction *trans;
12 	struct mail_transaction_log_append_ctx *append_ctx;
13 };
14 
15 static void
log_append_buffer(struct mail_index_export_context * ctx,const buffer_t * buf,enum mail_transaction_type type)16 log_append_buffer(struct mail_index_export_context *ctx,
17 		  const buffer_t *buf, enum mail_transaction_type type)
18 {
19 	mail_transaction_log_append_add(ctx->append_ctx, type,
20 					buf->data, buf->used);
21 }
22 
log_append_flag_updates(struct mail_index_export_context * ctx,struct mail_index_transaction * t)23 static void log_append_flag_updates(struct mail_index_export_context *ctx,
24 				    struct mail_index_transaction *t)
25 {
26 	ARRAY(struct mail_transaction_flag_update) log_updates;
27 	const struct mail_index_flag_update *updates;
28 	struct mail_transaction_flag_update *log_update;
29 	unsigned int i, count;
30 
31 	updates = array_get(&t->updates, &count);
32 	if (count == 0)
33 		return;
34 
35 	i_array_init(&log_updates, count);
36 
37 	for (i = 0; i < count; i++) {
38 		log_update = array_append_space(&log_updates);
39 		log_update->uid1 = updates[i].uid1;
40 		log_update->uid2 = updates[i].uid2;
41 		log_update->add_flags = updates[i].add_flags & 0xff;
42 		log_update->remove_flags = updates[i].remove_flags & 0xff;
43 		if ((updates[i].add_flags & MAIL_INDEX_MAIL_FLAG_UPDATE_MODSEQ) != 0)
44 			log_update->modseq_inc_flag = 1;
45 	}
46 	log_append_buffer(ctx, log_updates.arr.buffer,
47 			  MAIL_TRANSACTION_FLAG_UPDATE);
48 	array_free(&log_updates);
49 }
50 
51 static const buffer_t *
log_get_hdr_update_buffer(struct mail_index_transaction * t,bool prepend)52 log_get_hdr_update_buffer(struct mail_index_transaction *t, bool prepend)
53 {
54 	buffer_t *buf;
55 	const unsigned char *data, *mask;
56 	struct mail_transaction_header_update u;
57 	uint16_t offset;
58 	int state = 0;
59 
60 	i_zero(&u);
61 
62 	data = prepend ? t->pre_hdr_change : t->post_hdr_change;
63 	mask = prepend ? t->pre_hdr_mask : t->post_hdr_mask;
64 
65 	buf = t_buffer_create(256);
66 	for (offset = 0; offset <= sizeof(t->pre_hdr_change); offset++) {
67 		if (offset < sizeof(t->pre_hdr_change) && mask[offset] != 0) {
68 			if (state == 0) {
69 				u.offset = offset;
70 				state++;
71 			}
72 		} else {
73 			if (state > 0) {
74 				u.size = offset - u.offset;
75 				buffer_append(buf, &u, sizeof(u));
76 				buffer_append(buf, data + u.offset, u.size);
77 				state = 0;
78 			}
79 		}
80 	}
81 	return buf;
82 }
83 
84 static unsigned int
ext_hdr_update_get_size(const struct mail_index_transaction_ext_hdr_update * hu)85 ext_hdr_update_get_size(const struct mail_index_transaction_ext_hdr_update *hu)
86 {
87 	unsigned int i;
88 
89 	for (i = hu->alloc_size; i > 0; i--) {
90 		if (hu->mask[i-1] != 0)
91 			return i;
92 	}
93 	return 0;
94 }
95 
log_append_ext_intro(struct mail_index_export_context * ctx,uint32_t ext_id,uint32_t reset_id,unsigned int * hdr_size_r)96 static void log_append_ext_intro(struct mail_index_export_context *ctx,
97 				 uint32_t ext_id, uint32_t reset_id,
98 				 unsigned int *hdr_size_r)
99 {
100 	struct mail_index_transaction *t = ctx->trans;
101 	const struct mail_index_registered_ext *rext;
102 	const struct mail_index_ext *ext;
103         struct mail_transaction_ext_intro *intro, *resizes;
104 	buffer_t *buf;
105 	uint32_t idx;
106 	unsigned int count;
107 
108 	i_assert(ext_id != (uint32_t)-1);
109 
110 	if (t->reset ||
111 	    !mail_index_map_get_ext_idx(t->view->index->map, ext_id, &idx)) {
112 		/* new extension */
113 		idx = (uint32_t)-1;
114 	}
115 
116 	rext = array_idx(&t->view->index->extensions, ext_id);
117 	if (!array_is_created(&t->ext_resizes)) {
118 		resizes = NULL;
119 		count = 0;
120 	} else {
121 		resizes = array_get_modifiable(&t->ext_resizes, &count);
122 	}
123 
124 	buf = t_buffer_create(128);
125 	if (ext_id < count && resizes[ext_id].name_size != 0) {
126 		/* we're resizing the extension. use the resize struct. */
127 		intro = &resizes[ext_id];
128 
129 		if (idx != (uint32_t)-1) {
130 			intro->ext_id = idx;
131 			intro->name_size = 0;
132 		} else {
133 			intro->ext_id = (uint32_t)-1;
134 			intro->name_size = strlen(rext->name);
135 		}
136 		buffer_append(buf, intro, sizeof(*intro));
137 	} else {
138 		/* generate a new intro structure */
139 		intro = buffer_append_space_unsafe(buf, sizeof(*intro));
140 		intro->ext_id = idx;
141 		intro->record_size = rext->record_size;
142 		intro->record_align = rext->record_align;
143 		if (idx == (uint32_t)-1) {
144 			intro->hdr_size = rext->hdr_size;
145 			intro->name_size = strlen(rext->name);
146 		} else {
147 			ext = array_idx(&t->view->index->map->extensions, idx);
148 			intro->hdr_size = ext->hdr_size;
149 			intro->name_size = 0;
150 		}
151 		intro->flags = MAIL_TRANSACTION_EXT_INTRO_FLAG_NO_SHRINK;
152 
153 		/* handle increasing header size automatically */
154 		if (array_is_created(&t->ext_hdr_updates) &&
155 		    ext_id < array_count(&t->ext_hdr_updates)) {
156 			const struct mail_index_transaction_ext_hdr_update *hu;
157 			unsigned int hdr_update_size;
158 
159 			hu = array_idx(&t->ext_hdr_updates, ext_id);
160 			hdr_update_size = ext_hdr_update_get_size(hu);
161 			if (intro->hdr_size < hdr_update_size)
162 				intro->hdr_size = hdr_update_size;
163 		}
164 	}
165 	i_assert(intro->record_size != 0 || intro->hdr_size != 0);
166 	if (reset_id != 0) {
167 		/* we're going to reset this extension in this transaction */
168 		intro->reset_id = reset_id;
169 	} else if (idx != (uint32_t)-1) {
170 		/* use the existing reset_id */
171 		const struct mail_index_ext *map_ext =
172 			array_idx(&t->view->index->map->extensions, idx);
173 		intro->reset_id = map_ext->reset_id;
174 	} else {
175 		/* new extension, reset_id defaults to 0 */
176 	}
177 	buffer_append(buf, rext->name, intro->name_size);
178 	if ((buf->used % 4) != 0)
179 		buffer_append_zero(buf, 4 - (buf->used % 4));
180 
181 	if (ctx->append_ctx->new_highest_modseq == 0 &&
182 	    strcmp(rext->name, MAIL_INDEX_MODSEQ_EXT_NAME) == 0) {
183 		/* modseq tracking started */
184 		ctx->append_ctx->new_highest_modseq = 1;
185 	}
186 
187 	log_append_buffer(ctx, buf, MAIL_TRANSACTION_EXT_INTRO);
188 	*hdr_size_r = intro->hdr_size;
189 }
190 
191 static void
log_append_ext_hdr_update(struct mail_index_export_context * ctx,const struct mail_index_transaction_ext_hdr_update * hdr,unsigned int ext_hdr_size)192 log_append_ext_hdr_update(struct mail_index_export_context *ctx,
193 			  const struct mail_index_transaction_ext_hdr_update *hdr,
194 			  unsigned int ext_hdr_size)
195 {
196 	buffer_t *buf;
197 	const unsigned char *data, *mask;
198 	struct mail_transaction_ext_hdr_update u;
199 	struct mail_transaction_ext_hdr_update32 u32;
200 	size_t offset;
201 	bool started = FALSE, use_32 = hdr->alloc_size >= 65536;
202 
203 	i_zero(&u);
204 	i_zero(&u32);
205 
206 	data = hdr->data;
207 	mask = hdr->mask;
208 
209 	buf = buffer_create_dynamic(default_pool, 256);
210 	for (offset = 0; offset <= hdr->alloc_size; offset++) {
211 		if (offset < hdr->alloc_size && mask[offset] != 0) {
212 			if (!started) {
213 				u32.offset = offset;
214 				started = TRUE;
215 			}
216 		} else {
217 			if (started) {
218 				u32.size = offset - u32.offset;
219 				if (use_32)
220 					buffer_append(buf, &u32, sizeof(u32));
221 				else {
222 					u.offset = u32.offset;
223 					u.size = u32.size;
224 					buffer_append(buf, &u, sizeof(u));
225 				}
226 				i_assert(u32.offset + u32.size <= ext_hdr_size);
227 				buffer_append(buf, data + u32.offset, u32.size);
228 				started = FALSE;
229 			}
230 		}
231 	}
232 	if (buf->used % 4 != 0)
233 		buffer_append_zero(buf, 4 - buf->used % 4);
234 	log_append_buffer(ctx, buf, use_32 ? MAIL_TRANSACTION_EXT_HDR_UPDATE32 :
235 			  MAIL_TRANSACTION_EXT_HDR_UPDATE);
236 	buffer_free(&buf);
237 }
238 
239 static void
mail_transaction_log_append_ext_intros(struct mail_index_export_context * ctx)240 mail_transaction_log_append_ext_intros(struct mail_index_export_context *ctx)
241 {
242 	struct mail_index_transaction *t = ctx->trans;
243         const struct mail_transaction_ext_intro *resize;
244 	const struct mail_index_transaction_ext_hdr_update *hdrs;
245 	struct mail_transaction_ext_reset ext_reset;
246 	unsigned int resize_count, ext_count = 0;
247 	unsigned int hdrs_count, reset_id_count, reset_count, hdr_size;
248 	uint32_t ext_id, reset_id;
249 	const struct mail_transaction_ext_reset *reset;
250 	const uint32_t *reset_ids;
251 	buffer_t reset_buf;
252 
253 	if (!array_is_created(&t->ext_resizes)) {
254 		resize = NULL;
255 		resize_count = 0;
256 	} else {
257 		resize = array_get(&t->ext_resizes, &resize_count);
258 		if (ext_count < resize_count)
259 			ext_count = resize_count;
260 	}
261 
262 	if (!array_is_created(&t->ext_reset_ids)) {
263 		reset_ids = NULL;
264 		reset_id_count = 0;
265 	} else {
266 		reset_ids = array_get(&t->ext_reset_ids, &reset_id_count);
267 	}
268 
269 	if (!array_is_created(&t->ext_resets)) {
270 		reset = NULL;
271 		reset_count = 0;
272 	} else {
273 		reset = array_get(&t->ext_resets, &reset_count);
274 		if (ext_count < reset_count)
275 			ext_count = reset_count;
276 	}
277 
278 	if (!array_is_created(&t->ext_hdr_updates)) {
279 		hdrs = NULL;
280 		hdrs_count = 0;
281 	} else {
282 		hdrs = array_get(&t->ext_hdr_updates, &hdrs_count);
283 		if (ext_count < hdrs_count)
284 			ext_count = hdrs_count;
285 	}
286 
287 	i_zero(&ext_reset);
288 	buffer_create_from_data(&reset_buf, &ext_reset, sizeof(ext_reset));
289 	buffer_set_used_size(&reset_buf, sizeof(ext_reset));
290 
291 	for (ext_id = 0; ext_id < ext_count; ext_id++) {
292 		if (ext_id < reset_count)
293 			ext_reset = reset[ext_id];
294 		else
295 			ext_reset.new_reset_id = 0;
296 		if ((ext_id < resize_count && resize[ext_id].name_size > 0) ||
297 		    ext_reset.new_reset_id != 0 ||
298 		    (ext_id < hdrs_count && hdrs[ext_id].alloc_size > 0)) {
299 			if (ext_reset.new_reset_id != 0) {
300 				/* we're going to reset this extension
301 				   immediately after the intro */
302 				reset_id = 0;
303 			} else {
304 				reset_id = ext_id < reset_id_count ?
305 					reset_ids[ext_id] : 0;
306 			}
307 			log_append_ext_intro(ctx, ext_id, reset_id, &hdr_size);
308 		} else {
309 			hdr_size = 0;
310 		}
311 		if (ext_reset.new_reset_id != 0) {
312 			i_assert(ext_id < reset_id_count &&
313 				 ext_reset.new_reset_id == reset_ids[ext_id]);
314 			log_append_buffer(ctx, &reset_buf,
315 					  MAIL_TRANSACTION_EXT_RESET);
316 		}
317 		if (ext_id < hdrs_count && hdrs[ext_id].alloc_size > 0) {
318 			T_BEGIN {
319 				log_append_ext_hdr_update(ctx, &hdrs[ext_id],
320 							  hdr_size);
321 			} T_END;
322 		}
323 	}
324 }
325 
log_append_ext_recs(struct mail_index_export_context * ctx,const ARRAY_TYPE (seq_array_array)* arr,enum mail_transaction_type type)326 static void log_append_ext_recs(struct mail_index_export_context *ctx,
327 				const ARRAY_TYPE(seq_array_array) *arr,
328 				enum mail_transaction_type type)
329 {
330 	struct mail_index_transaction *t = ctx->trans;
331 	const ARRAY_TYPE(seq_array) *updates;
332 	const uint32_t *reset_ids;
333 	unsigned int ext_id, count, reset_id_count, hdr_size;
334 	uint32_t reset_id;
335 
336 	if (!array_is_created(&t->ext_reset_ids)) {
337 		reset_ids = NULL;
338 		reset_id_count = 0;
339 	} else {
340 		reset_ids = array_get_modifiable(&t->ext_reset_ids,
341 						 &reset_id_count);
342 	}
343 
344 	updates = array_get(arr, &count);
345 	for (ext_id = 0; ext_id < count; ext_id++) {
346 		if (!array_is_created(&updates[ext_id]))
347 			continue;
348 
349 		reset_id = ext_id < reset_id_count ? reset_ids[ext_id] : 0;
350 		log_append_ext_intro(ctx, ext_id, reset_id, &hdr_size);
351 
352 		log_append_buffer(ctx, updates[ext_id].arr.buffer, type);
353 	}
354 }
355 
356 static void
log_append_keyword_update(struct mail_index_export_context * ctx,buffer_t * tmp_buf,enum modify_type modify_type,const char * keyword,const buffer_t * uid_buffer)357 log_append_keyword_update(struct mail_index_export_context *ctx,
358 			  buffer_t *tmp_buf, enum modify_type modify_type,
359 			  const char *keyword, const buffer_t *uid_buffer)
360 {
361 	struct mail_transaction_keyword_update kt_hdr;
362 
363 	i_assert(uid_buffer->used > 0);
364 
365 	i_zero(&kt_hdr);
366 	kt_hdr.modify_type = modify_type;
367 	kt_hdr.name_size = strlen(keyword);
368 
369 	buffer_set_used_size(tmp_buf, 0);
370 	buffer_append(tmp_buf, &kt_hdr, sizeof(kt_hdr));
371 	buffer_append(tmp_buf, keyword, kt_hdr.name_size);
372 	if ((tmp_buf->used % 4) != 0)
373 		buffer_append_zero(tmp_buf, 4 - (tmp_buf->used % 4));
374 	buffer_append(tmp_buf, uid_buffer->data, uid_buffer->used);
375 
376 	log_append_buffer(ctx, tmp_buf, MAIL_TRANSACTION_KEYWORD_UPDATE);
377 }
378 
379 static bool
log_append_keyword_updates(struct mail_index_export_context * ctx)380 log_append_keyword_updates(struct mail_index_export_context *ctx)
381 {
382         const struct mail_index_transaction_keyword_update *updates;
383 	const char *const *keywords;
384 	buffer_t *tmp_buf;
385 	unsigned int i, count, keywords_count;
386 	bool changed = FALSE;
387 
388 	tmp_buf = t_buffer_create(64);
389 
390 	keywords = array_get_modifiable(&ctx->trans->view->index->keywords,
391 					&keywords_count);
392 	updates = array_get_modifiable(&ctx->trans->keyword_updates, &count);
393 	i_assert(count <= keywords_count);
394 
395 	for (i = 0; i < count; i++) {
396 		if (array_is_created(&updates[i].add_seq) &&
397 		    array_count(&updates[i].add_seq) > 0) {
398 			changed = TRUE;
399 			log_append_keyword_update(ctx, tmp_buf,
400 					MODIFY_ADD, keywords[i],
401 					updates[i].add_seq.arr.buffer);
402 		}
403 		if (array_is_created(&updates[i].remove_seq) &&
404 		    array_count(&updates[i].remove_seq) > 0) {
405 			changed = TRUE;
406 			log_append_keyword_update(ctx, tmp_buf,
407 					MODIFY_REMOVE, keywords[i],
408 					updates[i].remove_seq.arr.buffer);
409 		}
410 	}
411 	return changed;
412 }
413 
mail_index_transaction_export(struct mail_index_transaction * t,struct mail_transaction_log_append_ctx * append_ctx,enum mail_index_transaction_change * changes_r)414 void mail_index_transaction_export(struct mail_index_transaction *t,
415 				   struct mail_transaction_log_append_ctx *append_ctx,
416 				   enum mail_index_transaction_change *changes_r)
417 {
418 	static uint8_t null4[4] = { 0, 0, 0, 0 };
419 	enum mail_index_fsync_mask change_mask = 0;
420 	struct mail_index_export_context ctx;
421 
422 	*changes_r = 0;
423 
424 	i_zero(&ctx);
425 	ctx.trans = t;
426 	ctx.append_ctx = append_ctx;
427 
428 	if (t->index_undeleted) {
429 		i_assert(!t->index_deleted);
430 		mail_transaction_log_append_add(ctx.append_ctx,
431 			MAIL_TRANSACTION_INDEX_UNDELETED, &null4, 4);
432 	}
433 
434 	/* send all extension introductions and resizes before appends
435 	   to avoid resize overhead as much as possible */
436         mail_transaction_log_append_ext_intros(&ctx);
437 
438 	if (t->pre_hdr_changed) {
439 		log_append_buffer(&ctx, log_get_hdr_update_buffer(t, TRUE),
440 				  MAIL_TRANSACTION_HEADER_UPDATE);
441 	}
442 
443 	if (append_ctx->output->used > 0)
444 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
445 
446 	if (t->attribute_updates != NULL) {
447 		buffer_append_c(t->attribute_updates, '\0');
448 		/* need to have 32bit alignment */
449 		if (t->attribute_updates->used % 4 != 0) {
450 			buffer_append_zero(t->attribute_updates,
451 					   4 - t->attribute_updates->used % 4);
452 		}
453 		/* append the timestamp and value lengths */
454 		buffer_append(t->attribute_updates,
455 			      t->attribute_updates_suffix->data,
456 			      t->attribute_updates_suffix->used);
457 		i_assert(t->attribute_updates->used % 4 == 0);
458 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_ATTRIBUTE;
459 		log_append_buffer(&ctx, t->attribute_updates,
460 				  MAIL_TRANSACTION_ATTRIBUTE_UPDATE);
461 	}
462 	if (array_is_created(&t->appends)) {
463 		change_mask |= MAIL_INDEX_FSYNC_MASK_APPENDS;
464 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_APPEND;
465 		log_append_buffer(&ctx, t->appends.arr.buffer,
466 				  MAIL_TRANSACTION_APPEND);
467 	}
468 
469 	if (array_is_created(&t->updates)) {
470 		change_mask |= MAIL_INDEX_FSYNC_MASK_FLAGS;
471 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_FLAGS;
472 		log_append_flag_updates(&ctx, t);
473 	}
474 
475 	if (array_is_created(&t->ext_rec_updates)) {
476 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
477 		log_append_ext_recs(&ctx, &t->ext_rec_updates,
478 				    MAIL_TRANSACTION_EXT_REC_UPDATE);
479 	}
480 	if (array_is_created(&t->ext_rec_atomics)) {
481 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
482 		log_append_ext_recs(&ctx, &t->ext_rec_atomics,
483 				    MAIL_TRANSACTION_EXT_ATOMIC_INC);
484 	}
485 
486 	if (array_is_created(&t->keyword_updates)) {
487 		if (log_append_keyword_updates(&ctx)) {
488 			change_mask |= MAIL_INDEX_FSYNC_MASK_KEYWORDS;
489 			*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_KEYWORDS;
490 		}
491 	}
492 	/* keep modseq updates almost last */
493 	if (array_is_created(&t->modseq_updates)) {
494 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_MODSEQ;
495 		log_append_buffer(&ctx, t->modseq_updates.arr.buffer,
496 				  MAIL_TRANSACTION_MODSEQ_UPDATE);
497 	}
498 
499 	if (array_is_created(&t->expunges)) {
500 		/* non-external expunges are only requests, ignore them when
501 		   checking fsync_mask */
502 		if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0) {
503 			change_mask |= MAIL_INDEX_FSYNC_MASK_EXPUNGES;
504 			*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_EXPUNGE;
505 		} else {
506 			*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
507 		}
508 		log_append_buffer(&ctx, t->expunges.arr.buffer,
509 				  MAIL_TRANSACTION_EXPUNGE_GUID);
510 	}
511 
512 	if (t->post_hdr_changed) {
513 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
514 		log_append_buffer(&ctx, log_get_hdr_update_buffer(t, FALSE),
515 				  MAIL_TRANSACTION_HEADER_UPDATE);
516 	}
517 
518 	if (t->index_deleted) {
519 		i_assert(!t->index_undeleted);
520 		*changes_r |= MAIL_INDEX_TRANSACTION_CHANGE_OTHERS;
521 		mail_transaction_log_append_add(ctx.append_ctx,
522 						MAIL_TRANSACTION_INDEX_DELETED,
523 						&null4, 4);
524 	}
525 
526 	i_assert((append_ctx->output->used > 0) == (*changes_r != 0));
527 
528 	append_ctx->index_sync_transaction = t->sync_transaction;
529 	append_ctx->tail_offset_changed = t->tail_offset_changed;
530 	append_ctx->want_fsync =
531 		(t->view->index->set.fsync_mask & change_mask) != 0 ||
532 		(t->flags & MAIL_INDEX_TRANSACTION_FLAG_FSYNC) != 0;
533 }
534