1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "array.h"
6 #include "buffer.h"
7 #include "module-context.h"
8 #include "file-cache.h"
9 #include "file-set-size.h"
10 #include "read-full.h"
11 #include "write-full.h"
12 #include "mail-cache-private.h"
13 #include "mail-index-transaction-private.h"
14
15 #include <stddef.h>
16 #include <sys/stat.h>
17
18 #define MAIL_CACHE_INIT_WRITE_BUFFER (1024*16)
19
20 #define CACHE_TRANS_CONTEXT(obj) \
21 MODULE_CONTEXT(obj, cache_mail_index_transaction_module)
22 #define CACHE_TRANS_CONTEXT_REQUIRE(obj) \
23 MODULE_CONTEXT_REQUIRE(obj, cache_mail_index_transaction_module)
24
25 struct mail_cache_transaction_rec {
26 uint32_t seq;
27 uint32_t cache_data_pos;
28 };
29
30 struct mail_cache_transaction_ctx {
31 union mail_index_transaction_module_context module_ctx;
32 struct mail_index_transaction_vfuncs super;
33
34 struct mail_cache *cache;
35 struct mail_cache_view *view;
36 struct mail_index_transaction *trans;
37
38 uint32_t cache_file_seq;
39 uint32_t first_new_seq;
40
41 buffer_t *cache_data;
42 ARRAY(uint8_t) cache_field_idx_used;
43 ARRAY(struct mail_cache_transaction_rec) cache_data_seq;
44 ARRAY_TYPE(seq_range) cache_data_wanted_seqs;
45 uint32_t prev_seq, min_seq;
46 size_t last_rec_pos;
47
48 unsigned int records_written;
49
50 bool tried_purging:1;
51 bool decisions_refreshed:1;
52 bool have_noncommited_mails:1;
53 bool changes:1;
54 };
55
56 static MODULE_CONTEXT_DEFINE_INIT(cache_mail_index_transaction_module,
57 &mail_index_module_register);
58
59 static int mail_cache_transaction_lock(struct mail_cache_transaction_ctx *ctx);
60 static bool
61 mail_cache_transaction_update_last_rec_size(struct mail_cache_transaction_ctx *ctx,
62 size_t *size_r);
63 static int mail_cache_header_rewrite_fields(struct mail_cache *cache);
64
mail_index_transaction_cache_reset(struct mail_index_transaction * t)65 static void mail_index_transaction_cache_reset(struct mail_index_transaction *t)
66 {
67 struct mail_cache_transaction_ctx *ctx = CACHE_TRANS_CONTEXT_REQUIRE(t);
68 struct mail_index_transaction_vfuncs super = ctx->super;
69
70 mail_cache_transaction_reset(ctx);
71 super.reset(t);
72 }
73
74 static int
mail_index_transaction_cache_commit(struct mail_index_transaction * t,struct mail_index_transaction_commit_result * result_r)75 mail_index_transaction_cache_commit(struct mail_index_transaction *t,
76 struct mail_index_transaction_commit_result *result_r)
77 {
78 struct mail_cache_transaction_ctx *ctx = CACHE_TRANS_CONTEXT_REQUIRE(t);
79 struct mail_index_transaction_vfuncs super = ctx->super;
80
81 /* a failed cache commit isn't important enough to fail the entire
82 index transaction, so we'll just ignore it */
83 (void)mail_cache_transaction_commit(&ctx);
84 return super.commit(t, result_r);
85 }
86
87 static void
mail_index_transaction_cache_rollback(struct mail_index_transaction * t)88 mail_index_transaction_cache_rollback(struct mail_index_transaction *t)
89 {
90 struct mail_cache_transaction_ctx *ctx = CACHE_TRANS_CONTEXT_REQUIRE(t);
91 struct mail_index_transaction_vfuncs super = ctx->super;
92
93 mail_cache_transaction_rollback(&ctx);
94 super.rollback(t);
95 }
96
97 struct mail_cache_transaction_ctx *
mail_cache_get_transaction(struct mail_cache_view * view,struct mail_index_transaction * t)98 mail_cache_get_transaction(struct mail_cache_view *view,
99 struct mail_index_transaction *t)
100 {
101 struct mail_cache_transaction_ctx *ctx;
102
103 ctx = !cache_mail_index_transaction_module.id.module_id_set ? NULL :
104 CACHE_TRANS_CONTEXT(t);
105
106 if (ctx != NULL)
107 return ctx;
108
109 ctx = i_new(struct mail_cache_transaction_ctx, 1);
110 ctx->cache = view->cache;
111 ctx->view = view;
112 ctx->trans = t;
113
114 i_assert(view->transaction == NULL);
115 view->transaction = ctx;
116 view->trans_view = mail_index_transaction_open_updated_view(t);
117
118 ctx->super = t->v;
119 t->v.reset = mail_index_transaction_cache_reset;
120 t->v.commit = mail_index_transaction_cache_commit;
121 t->v.rollback = mail_index_transaction_cache_rollback;
122
123 MODULE_CONTEXT_SET(t, cache_mail_index_transaction_module, ctx);
124 return ctx;
125 }
126
127 static void
mail_cache_transaction_forget_flushed(struct mail_cache_transaction_ctx * ctx,bool reset_id_changed)128 mail_cache_transaction_forget_flushed(struct mail_cache_transaction_ctx *ctx,
129 bool reset_id_changed)
130 {
131 uint32_t new_cache_file_seq = MAIL_CACHE_IS_UNUSABLE(ctx->cache) ? 0 :
132 ctx->cache->hdr->file_seq;
133 if (reset_id_changed && ctx->records_written > 0) {
134 e_warning(ctx->cache->event,
135 "Purging lost %u written cache records "
136 "(reset_id changed %u -> %u)", ctx->records_written,
137 ctx->cache_file_seq, new_cache_file_seq);
138 /* don't increase deleted_record_count in the new file */
139 ctx->records_written = 0;
140 }
141 ctx->cache_file_seq = new_cache_file_seq;
142 /* forget all cache extension updates even if reset_id doesn't change */
143 mail_index_ext_set_reset_id(ctx->trans, ctx->cache->ext_id,
144 ctx->cache_file_seq);
145 }
146
mail_cache_transaction_reset(struct mail_cache_transaction_ctx * ctx)147 void mail_cache_transaction_reset(struct mail_cache_transaction_ctx *ctx)
148 {
149 mail_cache_transaction_forget_flushed(ctx, FALSE);
150 if (ctx->cache_data != NULL)
151 buffer_set_used_size(ctx->cache_data, 0);
152 if (array_is_created(&ctx->cache_data_seq))
153 array_clear(&ctx->cache_data_seq);
154 ctx->prev_seq = 0;
155 ctx->last_rec_pos = 0;
156
157 ctx->changes = FALSE;
158 }
159
mail_cache_transaction_rollback(struct mail_cache_transaction_ctx ** _ctx)160 void mail_cache_transaction_rollback(struct mail_cache_transaction_ctx **_ctx)
161 {
162 struct mail_cache_transaction_ctx *ctx = *_ctx;
163
164 *_ctx = NULL;
165
166 if (ctx->records_written > 0) {
167 /* we already wrote to the cache file. we can't (or don't want
168 to) delete that data, so just mark it as deleted space */
169 if (mail_cache_transaction_lock(ctx) > 0) {
170 ctx->cache->hdr_copy.deleted_record_count +=
171 ctx->records_written;
172 ctx->cache->hdr_modified = TRUE;
173 (void)mail_cache_flush_and_unlock(ctx->cache);
174 }
175 }
176
177 MODULE_CONTEXT_UNSET(ctx->trans, cache_mail_index_transaction_module);
178
179 ctx->view->transaction = NULL;
180 ctx->view->trans_seq1 = ctx->view->trans_seq2 = 0;
181
182 mail_index_view_close(&ctx->view->trans_view);
183 buffer_free(&ctx->cache_data);
184 if (array_is_created(&ctx->cache_data_seq))
185 array_free(&ctx->cache_data_seq);
186 if (array_is_created(&ctx->cache_data_wanted_seqs))
187 array_free(&ctx->cache_data_wanted_seqs);
188 array_free(&ctx->cache_field_idx_used);
189 i_free(ctx);
190 }
191
mail_cache_transactions_have_changes(struct mail_cache * cache)192 bool mail_cache_transactions_have_changes(struct mail_cache *cache)
193 {
194 struct mail_cache_view *view;
195
196 for (view = cache->views; view != NULL; view = view->next) {
197 if (view->transaction != NULL &&
198 view->transaction->changes)
199 return TRUE;
200 }
201 return FALSE;
202 }
203
204 static int
mail_cache_transaction_purge(struct mail_cache_transaction_ctx * ctx,const char * reason)205 mail_cache_transaction_purge(struct mail_cache_transaction_ctx *ctx,
206 const char *reason)
207 {
208 struct mail_cache *cache = ctx->cache;
209
210 ctx->tried_purging = TRUE;
211
212 uint32_t purge_file_seq =
213 MAIL_CACHE_IS_UNUSABLE(cache) ? 0 : cache->hdr->file_seq;
214
215 int ret = mail_cache_purge(cache, purge_file_seq, reason);
216 /* already written cache records must be forgotten, but records in
217 memory can still be written to the new cache file */
218 mail_cache_transaction_forget_flushed(ctx, TRUE);
219 return ret;
220 }
221
mail_cache_transaction_lock(struct mail_cache_transaction_ctx * ctx)222 static int mail_cache_transaction_lock(struct mail_cache_transaction_ctx *ctx)
223 {
224 struct mail_cache *cache = ctx->cache;
225 const uoff_t cache_max_size =
226 cache->index->optimization_set.cache.max_size;
227 int ret;
228
229 if ((ret = mail_cache_lock(cache)) <= 0) {
230 if (ret < 0)
231 return -1;
232
233 if (!ctx->tried_purging) {
234 if (mail_cache_transaction_purge(ctx, "creating cache") < 0)
235 return -1;
236 return mail_cache_transaction_lock(ctx);
237 } else {
238 return 0;
239 }
240 }
241 i_assert(!MAIL_CACHE_IS_UNUSABLE(cache));
242
243 if (!ctx->tried_purging && ctx->cache_data != NULL &&
244 cache->last_stat_size + ctx->cache_data->used > cache_max_size) {
245 /* Looks like cache file is becoming too large. Try to purge
246 it to free up some space. */
247 if (cache->hdr->continued_record_count > 0 ||
248 cache->hdr->deleted_record_count > 0) {
249 mail_cache_unlock(cache);
250 (void)mail_cache_transaction_purge(ctx, "cache is too large");
251 return mail_cache_transaction_lock(ctx);
252 }
253 }
254
255 if (ctx->cache_file_seq == 0)
256 ctx->cache_file_seq = cache->hdr->file_seq;
257 else if (ctx->cache_file_seq != cache->hdr->file_seq) {
258 /* already written cache records must be forgotten, but records
259 in memory can still be written to the new cache file */
260 mail_cache_transaction_forget_flushed(ctx, TRUE);
261 i_assert(ctx->cache_file_seq == cache->hdr->file_seq);
262 }
263 return 1;
264 }
265
266 const struct mail_cache_record *
mail_cache_transaction_lookup_rec(struct mail_cache_transaction_ctx * ctx,unsigned int seq,unsigned int * trans_next_idx)267 mail_cache_transaction_lookup_rec(struct mail_cache_transaction_ctx *ctx,
268 unsigned int seq,
269 unsigned int *trans_next_idx)
270 {
271 const struct mail_cache_transaction_rec *recs;
272 unsigned int i, count;
273
274 recs = array_get(&ctx->cache_data_seq, &count);
275 for (i = *trans_next_idx; i < count; i++) {
276 if (recs[i].seq == seq) {
277 *trans_next_idx = i + 1;
278 return CONST_PTR_OFFSET(ctx->cache_data->data,
279 recs[i].cache_data_pos);
280 }
281 }
282 *trans_next_idx = i + 1;
283 if (seq == ctx->prev_seq && i == count) {
284 /* update the unfinished record's (temporary) size and
285 return it */
286 size_t size;
287 if (!mail_cache_transaction_update_last_rec_size(ctx, &size))
288 return NULL;
289 return CONST_PTR_OFFSET(ctx->cache_data->data,
290 ctx->last_rec_pos);
291 }
292 return NULL;
293 }
294
295 static void
mail_cache_transaction_update_index(struct mail_cache_transaction_ctx * ctx,uint32_t write_offset,bool committing)296 mail_cache_transaction_update_index(struct mail_cache_transaction_ctx *ctx,
297 uint32_t write_offset, bool committing)
298 {
299 struct mail_cache *cache = ctx->cache;
300 struct mail_index_transaction *trans;
301 const struct mail_cache_record *rec = ctx->cache_data->data;
302 const struct mail_cache_transaction_rec *recs;
303 uint32_t i, seq_count;
304
305 if (committing) {
306 /* The transaction is being committed now. Use it. */
307 trans = ctx->trans;
308 } else if (ctx->have_noncommited_mails) {
309 /* Some of the mails haven't been committed yet. We must use
310 the provided transaction to update the cache records. */
311 trans = ctx->trans;
312 } else {
313 /* We can commit these changes immediately. This way even if
314 the provided transaction runs for a very long time, we
315 still once in a while commit the cache changes so they
316 become visible to other processes as well. */
317 trans = mail_index_transaction_begin(ctx->view->trans_view,
318 MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
319 }
320
321 mail_index_ext_using_reset_id(trans, ctx->cache->ext_id,
322 ctx->cache_file_seq);
323
324 /* write the cache_offsets to index file. records' prev_offset
325 is updated to point to old cache record when index is being
326 synced. */
327 recs = array_get(&ctx->cache_data_seq, &seq_count);
328 for (i = 0; i < seq_count; i++) {
329 mail_index_update_ext(trans, recs[i].seq, cache->ext_id,
330 &write_offset, NULL);
331
332 write_offset += rec->size;
333 rec = CONST_PTR_OFFSET(rec, rec->size);
334 ctx->records_written++;
335 }
336 if (trans != ctx->trans) {
337 i_assert(cache->index->log_sync_locked);
338 if (mail_index_transaction_commit(&trans) < 0) {
339 /* failed, but can't really do anything */
340 } else {
341 ctx->records_written = 0;
342 }
343 }
344 }
345
346 static int
mail_cache_link_records(struct mail_cache_transaction_ctx * ctx,uint32_t write_offset)347 mail_cache_link_records(struct mail_cache_transaction_ctx *ctx,
348 uint32_t write_offset)
349 {
350 struct mail_index_map *map;
351 struct mail_cache_record *rec;
352 const struct mail_cache_transaction_rec *recs;
353 const uint32_t *prev_offsetp;
354 ARRAY_TYPE(uint32_t) seq_offsets;
355 uint32_t i, seq_count, reset_id, prev_offset, *offsetp;
356 const void *data;
357
358 i_assert(ctx->min_seq != 0);
359
360 i_array_init(&seq_offsets, 64);
361 recs = array_get(&ctx->cache_data_seq, &seq_count);
362 rec = buffer_get_modifiable_data(ctx->cache_data, NULL);
363 for (i = 0; i < seq_count; i++) {
364 offsetp = array_idx_get_space(&seq_offsets,
365 recs[i].seq - ctx->min_seq);
366 if (*offsetp != 0)
367 prev_offset = *offsetp;
368 else {
369 mail_index_lookup_ext_full(ctx->view->trans_view, recs[i].seq,
370 ctx->cache->ext_id, &map,
371 &data, NULL);
372 prev_offsetp = data;
373
374 if (prev_offsetp == NULL || *prev_offsetp == 0)
375 prev_offset = 0;
376 else if (mail_index_ext_get_reset_id(ctx->view->trans_view, map,
377 ctx->cache->ext_id,
378 &reset_id) &&
379 reset_id == ctx->cache_file_seq)
380 prev_offset = *prev_offsetp;
381 else
382 prev_offset = 0;
383 if (prev_offset >= write_offset) {
384 mail_cache_set_corrupted(ctx->cache,
385 "Cache record offset points outside existing file");
386 array_free(&seq_offsets);
387 return -1;
388 }
389 }
390
391 if (prev_offset != 0) {
392 /* link this record to previous one */
393 rec->prev_offset = prev_offset;
394 ctx->cache->hdr_copy.continued_record_count++;
395 } else {
396 ctx->cache->hdr_copy.record_count++;
397 }
398 *offsetp = write_offset;
399
400 write_offset += rec->size;
401 rec = PTR_OFFSET(rec, rec->size);
402 }
403 array_free(&seq_offsets);
404 ctx->cache->hdr_modified = TRUE;
405 return 0;
406 }
407
408 static bool
mail_cache_transaction_set_used(struct mail_cache_transaction_ctx * ctx)409 mail_cache_transaction_set_used(struct mail_cache_transaction_ctx *ctx)
410 {
411 const uint8_t *cache_fields_used;
412 unsigned int field_idx, count;
413 bool missing_file_fields = FALSE;
414
415 cache_fields_used = array_get(&ctx->cache_field_idx_used, &count);
416 i_assert(count <= ctx->cache->fields_count);
417 for (field_idx = 0; field_idx < count; field_idx++) {
418 if (cache_fields_used[field_idx] != 0) {
419 ctx->cache->fields[field_idx].used = TRUE;
420 if (ctx->cache->field_file_map[field_idx] == (uint32_t)-1)
421 missing_file_fields = TRUE;
422 }
423 }
424 return missing_file_fields;
425 }
426
427 static int
mail_cache_transaction_update_fields(struct mail_cache_transaction_ctx * ctx)428 mail_cache_transaction_update_fields(struct mail_cache_transaction_ctx *ctx)
429 {
430 unsigned char *p;
431 const unsigned char *end, *rec_end;
432 uint32_t field_idx, data_size;
433
434 if (mail_cache_transaction_set_used(ctx)) {
435 /* add missing fields to cache */
436 if (mail_cache_header_rewrite_fields(ctx->cache) < 0)
437 return -1;
438 /* make sure they were actually added */
439 if (mail_cache_transaction_set_used(ctx)) {
440 mail_index_set_error(ctx->cache->index,
441 "Cache file %s: Unexpectedly lost newly added field",
442 ctx->cache->filepath);
443 return -1;
444 }
445 }
446
447 /* Go through all the added cache records and replace the in-memory
448 field_idx with the cache file-specific field index. Update only
449 up to last_rec_pos, because that's how far flushing is done. The
450 fields after that keep the in-memory field_idx until the next
451 flush. */
452 p = buffer_get_modifiable_data(ctx->cache_data, NULL);
453 end = CONST_PTR_OFFSET(ctx->cache_data->data, ctx->last_rec_pos);
454 rec_end = p;
455 while (p < end) {
456 if (p >= rec_end) {
457 /* next cache record */
458 i_assert(p == rec_end);
459 const struct mail_cache_record *rec =
460 (const struct mail_cache_record *)p;
461 /* note that the last rec->size==0 */
462 rec_end = CONST_PTR_OFFSET(p, rec->size);
463 p += sizeof(*rec);
464 }
465 /* replace field_idx */
466 uint32_t *file_fieldp = (uint32_t *)p;
467 field_idx = *file_fieldp;
468 *file_fieldp = ctx->cache->field_file_map[field_idx];
469 i_assert(*file_fieldp != (uint32_t)-1);
470 p += sizeof(field_idx);
471
472 /* Skip to next cache field. Next is <data size> if the field
473 is not fixed size. */
474 data_size = ctx->cache->fields[field_idx].field.field_size;
475 if (data_size == UINT_MAX) {
476 memcpy(&data_size, p, sizeof(data_size));
477 p += sizeof(data_size);
478 }
479 /* data & 32bit padding */
480 p += data_size;
481 if ((data_size & 3) != 0)
482 p += 4 - (data_size & 3);
483 }
484 i_assert(p == end);
485 return 0;
486 }
487
488 static void
mail_cache_transaction_drop_last_flush(struct mail_cache_transaction_ctx * ctx)489 mail_cache_transaction_drop_last_flush(struct mail_cache_transaction_ctx *ctx)
490 {
491 buffer_copy(ctx->cache_data, 0,
492 ctx->cache_data, ctx->last_rec_pos, SIZE_MAX);
493 buffer_set_used_size(ctx->cache_data,
494 ctx->cache_data->used - ctx->last_rec_pos);
495 ctx->last_rec_pos = 0;
496 ctx->min_seq = 0;
497
498 array_clear(&ctx->cache_data_seq);
499 array_clear(&ctx->cache_data_wanted_seqs);
500 }
501
502 static int
mail_cache_transaction_flush(struct mail_cache_transaction_ctx * ctx,bool committing)503 mail_cache_transaction_flush(struct mail_cache_transaction_ctx *ctx,
504 bool committing)
505 {
506 struct stat st;
507 uint32_t write_offset = 0;
508 int ret = 0;
509
510 i_assert(!ctx->cache->locked);
511
512 if (array_count(&ctx->cache_data_seq) == 0) {
513 /* we had done some changes, but they were aborted. */
514 i_assert(ctx->last_rec_pos == 0);
515 ctx->min_seq = 0;
516 return 0;
517 }
518
519 /* If we're going to be committing a transaction, the log must be
520 locked before we lock cache or we can deadlock. */
521 bool lock_log = !ctx->cache->index->log_sync_locked &&
522 !committing && !ctx->have_noncommited_mails;
523 if (lock_log) {
524 uint32_t file_seq;
525 uoff_t file_offset;
526
527 if (mail_transaction_log_sync_lock(ctx->cache->index->log,
528 "mail cache transaction flush",
529 &file_seq, &file_offset) < 0)
530 return -1;
531 }
532
533 if (mail_cache_transaction_lock(ctx) <= 0) {
534 if (lock_log) {
535 mail_transaction_log_sync_unlock(ctx->cache->index->log,
536 "mail cache transaction flush: cache lock failed");
537 }
538 return -1;
539 }
540
541 i_assert(ctx->cache_data != NULL);
542 i_assert(ctx->last_rec_pos <= ctx->cache_data->used);
543
544 if (mail_cache_transaction_update_fields(ctx) < 0) {
545 if (lock_log) {
546 mail_transaction_log_sync_unlock(ctx->cache->index->log,
547 "mail cache transaction flush: field update failed");
548 }
549 mail_cache_unlock(ctx->cache);
550 return -1;
551 }
552
553 /* we need to get the final write offset for linking records */
554 if (fstat(ctx->cache->fd, &st) < 0) {
555 if (!ESTALE_FSTAT(errno))
556 mail_cache_set_syscall_error(ctx->cache, "fstat()");
557 ret = -1;
558 } else if ((uoff_t)st.st_size + ctx->last_rec_pos > ctx->cache->index->optimization_set.cache.max_size) {
559 mail_cache_set_corrupted(ctx->cache, "Cache file too large");
560 ret = -1;
561 } else {
562 write_offset = st.st_size;
563 if (mail_cache_link_records(ctx, write_offset) < 0)
564 ret = -1;
565 }
566
567 /* write to cache file */
568 if (ret < 0 ||
569 mail_cache_append(ctx->cache, ctx->cache_data->data,
570 ctx->last_rec_pos, &write_offset) < 0)
571 ret = -1;
572 else {
573 /* update records' cache offsets to index */
574 mail_cache_transaction_update_index(ctx, write_offset,
575 committing);
576 }
577 if (mail_cache_flush_and_unlock(ctx->cache) < 0)
578 ret = -1;
579
580 if (lock_log) {
581 mail_transaction_log_sync_unlock(ctx->cache->index->log,
582 "mail cache transaction flush");
583 }
584 return ret;
585 }
586
587 static void
mail_cache_transaction_drop_unwanted(struct mail_cache_transaction_ctx * ctx,size_t space_needed)588 mail_cache_transaction_drop_unwanted(struct mail_cache_transaction_ctx *ctx,
589 size_t space_needed)
590 {
591 struct mail_cache_transaction_rec *recs;
592 unsigned int i, count;
593
594 recs = array_get_modifiable(&ctx->cache_data_seq, &count);
595 /* find out how many records to delete. delete all unwanted sequences,
596 and if that's not enough delete some more. */
597 for (i = 0; i < count; i++) {
598 if (seq_range_exists(&ctx->cache_data_wanted_seqs, recs[i].seq)) {
599 if (recs[i].cache_data_pos >= space_needed)
600 break;
601 /* we're going to forcibly delete it - remove it also
602 from the array since it's no longer useful there */
603 seq_range_array_remove(&ctx->cache_data_wanted_seqs,
604 recs[i].seq);
605 }
606 }
607 unsigned int deleted_count = i;
608 size_t deleted_space = i < count ?
609 recs[i].cache_data_pos : ctx->last_rec_pos;
610 for (; i < count; i++)
611 recs[i].cache_data_pos -= deleted_space;
612 ctx->last_rec_pos -= deleted_space;
613 array_delete(&ctx->cache_data_seq, 0, deleted_count);
614 buffer_delete(ctx->cache_data, 0, deleted_space);
615 }
616
617 static bool
mail_cache_transaction_update_last_rec_size(struct mail_cache_transaction_ctx * ctx,size_t * size_r)618 mail_cache_transaction_update_last_rec_size(struct mail_cache_transaction_ctx *ctx,
619 size_t *size_r)
620 {
621 struct mail_cache_record *rec;
622 void *data;
623 size_t size;
624
625 data = buffer_get_modifiable_data(ctx->cache_data, &size);
626 rec = PTR_OFFSET(data, ctx->last_rec_pos);
627 rec->size = size - ctx->last_rec_pos;
628 if (rec->size == sizeof(*rec))
629 return FALSE;
630 i_assert(rec->size > sizeof(*rec));
631 *size_r = rec->size;
632 return TRUE;
633 }
634
635 static void
mail_cache_transaction_update_last_rec(struct mail_cache_transaction_ctx * ctx)636 mail_cache_transaction_update_last_rec(struct mail_cache_transaction_ctx *ctx)
637 {
638 struct mail_cache_transaction_rec *trans_rec;
639 size_t size;
640
641 if (!mail_cache_transaction_update_last_rec_size(ctx, &size) ||
642 size > ctx->cache->index->optimization_set.cache.record_max_size) {
643 buffer_set_used_size(ctx->cache_data, ctx->last_rec_pos);
644 return;
645 }
646
647 if (ctx->min_seq > ctx->prev_seq || ctx->min_seq == 0)
648 ctx->min_seq = ctx->prev_seq;
649 trans_rec = array_append_space(&ctx->cache_data_seq);
650 trans_rec->seq = ctx->prev_seq;
651 trans_rec->cache_data_pos = ctx->last_rec_pos;
652 ctx->last_rec_pos = ctx->cache_data->used;
653 }
654
655 static void
mail_cache_transaction_switch_seq(struct mail_cache_transaction_ctx * ctx)656 mail_cache_transaction_switch_seq(struct mail_cache_transaction_ctx *ctx)
657 {
658 struct mail_cache_record new_rec;
659
660 if (ctx->prev_seq != 0) {
661 /* update previously added cache record's size */
662 mail_cache_transaction_update_last_rec(ctx);
663 } else if (ctx->cache_data == NULL) {
664 ctx->cache_data =
665 buffer_create_dynamic(default_pool,
666 MAIL_CACHE_INIT_WRITE_BUFFER);
667 i_array_init(&ctx->cache_data_seq, 64);
668 i_array_init(&ctx->cache_data_wanted_seqs, 32);
669 i_array_init(&ctx->cache_field_idx_used, 64);
670 }
671
672 i_zero(&new_rec);
673 buffer_append(ctx->cache_data, &new_rec, sizeof(new_rec));
674
675 ctx->prev_seq = 0;
676 ctx->changes = TRUE;
677 }
678
mail_cache_transaction_commit(struct mail_cache_transaction_ctx ** _ctx)679 int mail_cache_transaction_commit(struct mail_cache_transaction_ctx **_ctx)
680 {
681 struct mail_cache_transaction_ctx *ctx = *_ctx;
682 int ret = 0;
683
684 if (ctx->changes) {
685 if (ctx->prev_seq != 0)
686 mail_cache_transaction_update_last_rec(ctx);
687 if (mail_cache_transaction_flush(ctx, TRUE) < 0)
688 ret = -1;
689 else {
690 /* successfully wrote everything */
691 ctx->records_written = 0;
692 }
693 /* Here would be a good place to do fdatasync() to make sure
694 everything is written before offsets are updated to index.
695 However it slows down I/O needlessly and we're pretty good
696 at catching and fixing cache corruption, so we no longer do
697 it. */
698 }
699 mail_cache_transaction_rollback(_ctx);
700 return ret;
701 }
702
703 static int
mail_cache_header_fields_write(struct mail_cache * cache,const buffer_t * buffer)704 mail_cache_header_fields_write(struct mail_cache *cache, const buffer_t *buffer)
705 {
706 uint32_t offset, hdr_offset;
707
708 i_assert(cache->locked);
709
710 offset = 0;
711 if (mail_cache_append(cache, buffer->data, buffer->used, &offset) < 0)
712 return -1;
713
714 if (cache->index->set.fsync_mode == FSYNC_MODE_ALWAYS) {
715 if (fdatasync(cache->fd) < 0) {
716 mail_cache_set_syscall_error(cache, "fdatasync()");
717 return -1;
718 }
719 }
720 /* find offset to the previous header's "next_offset" field */
721 if (mail_cache_header_fields_get_next_offset(cache, &hdr_offset) < 0)
722 return -1;
723
724 /* update the next_offset offset, so our new header will be found */
725 offset = mail_index_uint32_to_offset(offset);
726 if (mail_cache_write(cache, &offset, sizeof(offset), hdr_offset) < 0)
727 return -1;
728
729 if (hdr_offset == offsetof(struct mail_cache_header,
730 field_header_offset)) {
731 /* we're adding the first field. hdr_copy needs to be kept
732 in sync so unlocking won't overwrite it. */
733 cache->hdr_copy.field_header_offset = hdr_offset;
734 cache->hdr_ro_copy.field_header_offset = hdr_offset;
735 }
736 return 0;
737 }
738
mail_cache_header_rewrite_fields(struct mail_cache * cache)739 static int mail_cache_header_rewrite_fields(struct mail_cache *cache)
740 {
741 int ret;
742
743 /* re-read header to make sure we don't lose any fields. */
744 if (mail_cache_header_fields_read(cache) < 0)
745 return -1;
746
747 T_BEGIN {
748 buffer_t *buffer;
749
750 buffer = t_buffer_create(256);
751 mail_cache_header_fields_get(cache, buffer);
752 ret = mail_cache_header_fields_write(cache, buffer);
753 } T_END;
754
755 if (ret == 0) {
756 /* we wrote all the headers, so there are no pending changes */
757 cache->field_header_write_pending = FALSE;
758 ret = mail_cache_header_fields_read(cache);
759 }
760 return ret;
761 }
762
763 static void
mail_cache_transaction_refresh_decisions(struct mail_cache_transaction_ctx * ctx)764 mail_cache_transaction_refresh_decisions(struct mail_cache_transaction_ctx *ctx)
765 {
766 if (ctx->decisions_refreshed)
767 return;
768
769 /* Read latest caching decisions from the cache file's header once
770 per transaction. */
771 if (!ctx->cache->opened)
772 (void)mail_cache_open_and_verify(ctx->cache);
773 else
774 (void)mail_cache_header_fields_read(ctx->cache);
775 ctx->decisions_refreshed = TRUE;
776 }
777
mail_cache_add(struct mail_cache_transaction_ctx * ctx,uint32_t seq,unsigned int field_idx,const void * data,size_t data_size)778 void mail_cache_add(struct mail_cache_transaction_ctx *ctx, uint32_t seq,
779 unsigned int field_idx, const void *data, size_t data_size)
780 {
781 uint32_t data_size32;
782 unsigned int fixed_size;
783 size_t full_size, record_size;
784
785 i_assert(field_idx < ctx->cache->fields_count);
786 i_assert(data_size < (uint32_t)-1);
787
788 if (ctx->cache->fields[field_idx].field.decision ==
789 (MAIL_CACHE_DECISION_NO | MAIL_CACHE_DECISION_FORCED))
790 return;
791
792 if (seq >= ctx->trans->first_new_seq)
793 ctx->have_noncommited_mails = TRUE;
794
795 /* If the cache file exists, make sure the caching decisions have been
796 read. */
797 mail_cache_transaction_refresh_decisions(ctx);
798
799 mail_cache_decision_add(ctx->view, seq, field_idx);
800
801 fixed_size = ctx->cache->fields[field_idx].field.field_size;
802 i_assert(fixed_size == UINT_MAX || fixed_size == data_size);
803
804 data_size32 = (uint32_t)data_size;
805 full_size = sizeof(field_idx) + ((data_size + 3) & ~3U);
806 if (fixed_size == UINT_MAX)
807 full_size += sizeof(data_size32);
808
809 if (ctx->prev_seq != seq) {
810 mail_cache_transaction_switch_seq(ctx);
811 ctx->prev_seq = seq;
812 seq_range_array_add(&ctx->cache_data_wanted_seqs, seq);
813
814 /* remember roughly what we have modified, so cache lookups can
815 look into transactions to see changes. */
816 if (seq < ctx->view->trans_seq1 || ctx->view->trans_seq1 == 0)
817 ctx->view->trans_seq1 = seq;
818 if (seq > ctx->view->trans_seq2)
819 ctx->view->trans_seq2 = seq;
820 }
821
822 if (mail_cache_transaction_update_last_rec_size(ctx, &record_size) &&
823 record_size + full_size >
824 ctx->cache->index->optimization_set.cache.record_max_size) {
825 /* Adding this field would exceed the cache record's maximum
826 size. If we don't add this, it's possible that other fields
827 could still be added. */
828 return;
829 }
830
831 /* Remember that this field has been used within the transaction. Later
832 on we fill mail_cache_field_private.used with it. We can't rely on
833 setting it here, because cache purging may run and clear it. */
834 uint8_t field_idx_set = 1;
835 array_idx_set(&ctx->cache_field_idx_used, field_idx, &field_idx_set);
836
837 /* Remember that this value exists for the mail, in case we try to look
838 it up. Note that this gets forgotten whenever changing the mail. */
839 buffer_write(ctx->view->cached_exists_buf, field_idx,
840 &ctx->view->cached_exists_value, 1);
841
842 if (ctx->cache_data->used + full_size > MAIL_CACHE_MAX_WRITE_BUFFER &&
843 ctx->last_rec_pos > 0) {
844 /* time to flush our buffer. */
845 if (MAIL_INDEX_IS_IN_MEMORY(ctx->cache->index)) {
846 /* just drop the old data to free up memory */
847 size_t space_needed = ctx->cache_data->used +
848 full_size - MAIL_CACHE_MAX_WRITE_BUFFER;
849 mail_cache_transaction_drop_unwanted(ctx, space_needed);
850 } else {
851 if (mail_cache_transaction_flush(ctx, FALSE) < 0) {
852 /* If this is a syscall failure, the already
853 flushed changes could still be finished by
854 writing the offsets to .log file. If this is
855 a corruption/lost cache, the offsets will
856 point to a nonexistent file or be ignored.
857 Either way, we don't really need to handle
858 this failure in any special way. */
859 }
860 /* Regardless of whether the flush succeeded, drop all
861 data that it would have written. This way the flush
862 is attempted only once, but it could still be
863 possible to write new data later. Also don't reset
864 the transaction entirely so that the last partially
865 cached mail can still be accessed from memory. */
866 mail_cache_transaction_drop_last_flush(ctx);
867 }
868 }
869
870 buffer_append(ctx->cache_data, &field_idx, sizeof(field_idx));
871 if (fixed_size == UINT_MAX) {
872 buffer_append(ctx->cache_data, &data_size32,
873 sizeof(data_size32));
874 }
875
876 buffer_append(ctx->cache_data, data, data_size);
877 if ((data_size & 3) != 0)
878 buffer_append_zero(ctx->cache_data, 4 - (data_size & 3));
879 }
880
mail_cache_field_want_add(struct mail_cache_transaction_ctx * ctx,uint32_t seq,unsigned int field_idx)881 bool mail_cache_field_want_add(struct mail_cache_transaction_ctx *ctx,
882 uint32_t seq, unsigned int field_idx)
883 {
884 enum mail_cache_decision_type decision;
885
886 mail_cache_transaction_refresh_decisions(ctx);
887
888 decision = mail_cache_field_get_decision(ctx->view->cache, field_idx);
889 decision &= ENUM_NEGATE(MAIL_CACHE_DECISION_FORCED);
890 switch (decision) {
891 case MAIL_CACHE_DECISION_NO:
892 return FALSE;
893 case MAIL_CACHE_DECISION_TEMP:
894 /* add it only if it's newer than what we would drop when
895 purging */
896 if (ctx->first_new_seq == 0) {
897 ctx->first_new_seq =
898 mail_cache_get_first_new_seq(ctx->view->view);
899 }
900 if (seq < ctx->first_new_seq)
901 return FALSE;
902 break;
903 default:
904 break;
905 }
906
907 return mail_cache_field_exists(ctx->view, seq, field_idx) == 0;
908 }
909
mail_cache_field_can_add(struct mail_cache_transaction_ctx * ctx,uint32_t seq,unsigned int field_idx)910 bool mail_cache_field_can_add(struct mail_cache_transaction_ctx *ctx,
911 uint32_t seq, unsigned int field_idx)
912 {
913 enum mail_cache_decision_type decision;
914
915 mail_cache_transaction_refresh_decisions(ctx);
916
917 decision = mail_cache_field_get_decision(ctx->view->cache, field_idx);
918 if (decision == (MAIL_CACHE_DECISION_FORCED | MAIL_CACHE_DECISION_NO))
919 return FALSE;
920
921 return mail_cache_field_exists(ctx->view, seq, field_idx) == 0;
922 }
923
mail_cache_close_mail(struct mail_cache_transaction_ctx * ctx,uint32_t seq)924 void mail_cache_close_mail(struct mail_cache_transaction_ctx *ctx,
925 uint32_t seq)
926 {
927 if (array_is_created(&ctx->cache_data_wanted_seqs))
928 seq_range_array_remove(&ctx->cache_data_wanted_seqs, seq);
929 }
930