280
static int mail_cache_grow_file(struct mail_cache *cache, size_t size)
283
uoff_t new_fsize, grow_size;
285
i_assert(cache->locked);
288
new_fsize = cache->hdr_copy.used_file_size + size;
289
grow_size = new_fsize / 100 * MAIL_CACHE_GROW_PERCENTAGE;
290
if (grow_size < 16384)
292
new_fsize += grow_size;
295
if (fstat(cache->fd, &st) < 0) {
296
mail_cache_set_syscall_error(cache, "fstat()");
300
if ((uoff_t)st.st_size < new_fsize) {
301
if (file_set_size(cache->fd, new_fsize) < 0) {
302
mail_cache_set_syscall_error(cache, "file_set_size()");
309
static bool mail_cache_unlink_hole(struct mail_cache *cache, size_t size,
310
struct mail_cache_hole_header *hole_r)
312
struct mail_cache_header *hdr = &cache->hdr_copy;
313
struct mail_cache_hole_header hole;
314
uint32_t offset, prev_offset;
316
i_assert(cache->locked);
318
if (hdr->minor_version > 0)
319
return FALSE; /* this is record_count field in v2.2+ */
321
offset = hdr->hole_offset; prev_offset = 0;
322
while (offset != 0) {
323
if (pread_full(cache->fd, &hole, sizeof(hole), offset) <= 0) {
324
mail_cache_set_syscall_error(cache, "pread_full()");
328
if (hole.magic != MAIL_CACHE_HOLE_HEADER_MAGIC) {
329
mail_cache_set_corrupted(cache,
330
"Invalid magic in hole header");
334
if (hole.size >= size)
337
prev_offset = offset;
338
offset = hole.next_offset;
343
if (prev_offset == 0)
344
hdr->hole_offset = hole.next_offset;
346
if (mail_cache_write(cache, &hole.next_offset,
347
sizeof(hole.next_offset), prev_offset) < 0)
350
hdr->deleted_space -= hole.size;
351
cache->hdr_modified = TRUE;
353
hole_r->next_offset = offset;
354
hole_r->size = hole.size;
359
mail_cache_transaction_add_reservation(struct mail_cache_transaction_ctx *ctx,
360
uint32_t offset, uint32_t size)
362
struct mail_cache_reservation res;
364
ctx->reserved_space_offset = offset;
365
ctx->reserved_space = size;
370
array_append(&ctx->reservations, &res, 1);
374
mail_cache_transaction_partial_commit(struct mail_cache_transaction_ctx *ctx,
375
uint32_t offset, uint32_t size)
377
struct mail_cache_reservation *res;
280
const struct mail_cache_record *
281
mail_cache_transaction_lookup_rec(struct mail_cache_transaction_ctx *ctx,
283
unsigned int *trans_next_idx)
285
const struct mail_cache_transaction_rec *recs;
378
286
unsigned int i, count;
380
if (offset + size == ctx->cache->hdr_copy.used_file_size &&
381
offset + size == ctx->reserved_space_offset) {
382
i_assert(ctx->reserved_space == 0);
383
ctx->reserved_space_offset = 0;
386
res = array_get_modifiable(&ctx->reservations, &count);
387
for (i = 0; i < count; i++) {
388
if (res[i].offset == offset) {
389
if (res[i].size == size) {
390
array_delete(&ctx->reservations, i, 1);
392
i_assert(res[i].size > size);
393
res[i].offset += size;
402
mail_cache_transaction_reserve_more(struct mail_cache_transaction_ctx *ctx,
403
size_t block_size, bool commit)
405
struct mail_cache *cache = ctx->cache;
406
struct mail_cache_header *hdr = &cache->hdr_copy;
407
struct mail_cache_hole_header hole;
408
struct mail_cache_reservation *reservations;
411
i_assert(cache->locked);
413
if (mail_cache_unlink_hole(cache, block_size, &hole)) {
414
/* found a large enough hole. */
415
mail_cache_transaction_add_reservation(ctx, hole.next_offset,
420
if (MAIL_CACHE_IS_UNUSABLE(cache)) {
421
/* mail_cache_unlink_hole() could have noticed corruption */
425
if ((uint32_t)-1 - hdr->used_file_size < block_size) {
426
mail_index_set_error(cache->index, "Cache file too large: %s",
431
if (!commit && block_size < MAIL_CACHE_MAX_RESERVED_BLOCK_SIZE) {
432
/* allocate some more space than we need */
433
size_t new_block_size = (block_size + ctx->last_grow_size) * 2;
434
if (new_block_size > MAIL_CACHE_MAX_RESERVED_BLOCK_SIZE)
435
new_block_size = MAIL_CACHE_MAX_RESERVED_BLOCK_SIZE;
437
if ((uint32_t)-1 - hdr->used_file_size >= new_block_size) {
438
block_size = new_block_size;
439
ctx->last_grow_size = new_block_size;
443
if (mail_cache_grow_file(ctx->cache, block_size) < 0)
446
if (ctx->reserved_space_offset + ctx->reserved_space ==
447
hdr->used_file_size) {
448
/* we can simply grow it */
450
/* grow reservation. it's probably the last one in the buffer,
451
but it's not guarateed because we might have used holes
453
reservations = array_get_modifiable(&ctx->reservations, &count);
458
} while (reservations[count].offset +
459
reservations[count].size != hdr->used_file_size);
461
reservations[count].size += block_size;
462
ctx->reserved_space += block_size;
464
mail_cache_transaction_add_reservation(ctx, hdr->used_file_size,
468
cache->hdr_modified = TRUE;
469
hdr->used_file_size = ctx->reserved_space_offset + ctx->reserved_space;
474
mail_cache_free_space(struct mail_cache *cache, uint32_t offset, uint32_t size)
476
struct mail_cache_hole_header hole;
478
i_assert(cache->locked);
480
if (MAIL_CACHE_IS_UNUSABLE(cache))
483
if (offset + size == cache->hdr_copy.used_file_size) {
484
/* we can just set used_file_size back */
485
cache->hdr_modified = TRUE;
486
cache->hdr_copy.used_file_size = offset;
487
} else if (size >= MAIL_CACHE_MIN_HOLE_SIZE &&
488
cache->hdr_copy.minor_version == 0) {
489
/* set it up as a hole */
490
hole.next_offset = cache->hdr_copy.hole_offset;
492
hole.magic = MAIL_CACHE_HOLE_HEADER_MAGIC;
494
if (mail_cache_write(cache, &hole, sizeof(hole), offset) < 0)
497
cache->hdr_copy.deleted_space += size;
498
cache->hdr_copy.hole_offset = offset;
499
cache->hdr_modified = TRUE;
504
mail_cache_transaction_free_reservations(struct mail_cache_transaction_ctx *ctx)
506
const struct mail_cache_reservation *reservations;
509
if (ctx->reserved_space == 0 && array_count(&ctx->reservations) == 0)
512
if (mail_cache_transaction_lock(ctx) <= 0)
515
reservations = array_get(&ctx->reservations, &count);
517
/* free flushed data as well. do it from end to beginning so we have
518
a better chance of updating used_file_size instead of adding holes */
521
mail_cache_free_space(ctx->cache,
522
reservations[count].offset,
523
reservations[count].size);
525
(void)mail_cache_unlock(ctx->cache);
529
mail_cache_transaction_free_space(struct mail_cache_transaction_ctx *ctx)
531
bool locked = ctx->cache->locked;
533
if (ctx->reserved_space == 0)
537
if (mail_cache_transaction_lock(ctx) <= 0)
541
/* check again - locking might have reopened the cache file */
542
if (ctx->reserved_space != 0) {
543
i_assert(ctx->cache_file_seq == ctx->cache->hdr->file_seq);
544
mail_cache_free_space(ctx->cache, ctx->reserved_space_offset,
545
ctx->reserved_space);
546
ctx->reserved_space_offset = 0;
547
ctx->reserved_space = 0;
551
if (mail_cache_unlock(ctx->cache) < 0)
558
mail_cache_transaction_get_space(struct mail_cache_transaction_ctx *ctx,
559
size_t min_size, size_t max_size,
560
uint32_t *offset_r, size_t *available_space_r,
563
bool locked = ctx->cache->locked;
564
uint32_t cache_file_seq;
568
i_assert((min_size & 3) == 0);
569
i_assert((max_size & 3) == 0);
571
if (min_size > ctx->reserved_space) {
572
/* not enough preallocated space in transaction, get more */
573
cache_file_seq = ctx->cache_file_seq;
575
if ((ret = mail_cache_transaction_lock(ctx)) <= 0)
578
ret = mail_cache_transaction_reserve_more(ctx, max_size,
581
if (mail_cache_unlock(ctx->cache) < 0)
588
if (cache_file_seq != ctx->cache_file_seq) {
589
/* cache file reopened - need to abort */
595
size = I_MIN(max_size, ctx->reserved_space);
598
*offset_r = ctx->reserved_space_offset;
599
ctx->reserved_space_offset += size;
600
ctx->reserved_space -= size;
601
if (available_space_r != NULL)
602
*available_space_r = size;
603
i_assert((size & 3) == 0);
605
if (size == max_size && commit) {
606
/* final commit - see if we can free the rest of the
608
if (mail_cache_transaction_free_space(ctx) < 0)
612
i_assert(size >= min_size);
288
recs = array_get(&ctx->cache_data_seq, &count);
289
for (i = *trans_next_idx; i < count; i++) {
290
if (recs[i].seq == seq) {
291
*trans_next_idx = i + 1;
292
return CONST_PTR_OFFSET(ctx->cache_data->data,
293
recs[i].cache_data_pos);
617
301
mail_cache_transaction_update_index(struct mail_cache_transaction_ctx *ctx,
618
const struct mail_cache_record *rec,
619
const uint32_t *seq, uint32_t *seq_idx,
620
uint32_t seq_limit, uint32_t write_offset,
302
uint32_t write_offset)
623
304
struct mail_cache *cache = ctx->cache;
624
uint32_t i, old_offset, orig_write_offset;
305
const struct mail_cache_record *rec = ctx->cache_data->data;
306
const struct mail_cache_transaction_rec *recs;
307
uint32_t i, seq_count;
626
309
mail_index_ext_using_reset_id(ctx->trans, ctx->cache->ext_id,
627
310
ctx->cache_file_seq);
629
312
/* write the cache_offsets to index file. records' prev_offset
630
313
is updated to point to old cache record when index is being
632
orig_write_offset = write_offset;
633
for (i = *seq_idx; i < seq_limit; i++) {
634
mail_index_update_ext(ctx->trans, seq[i], cache->ext_id,
635
&write_offset, &old_offset);
636
if (old_offset != 0) {
637
/* we added records for this message multiple
638
times in this same uncommitted transaction.
639
only the new one will be written to
640
transaction log, we need to do the linking
642
if (old_offset > write_offset) {
643
if (mail_cache_link_unlocked(cache, old_offset,
647
/* if we're combining multiple transactions,
648
make sure the one with the smallest offset
649
is written into index. this is required for
650
non-file-mmaped cache to work properly. */
651
mail_index_update_ext(ctx->trans, seq[i],
654
if (mail_cache_link_unlocked(cache,
315
recs = array_get(&ctx->cache_data_seq, &seq_count);
316
for (i = 0; i < seq_count; i++) {
317
mail_index_update_ext(ctx->trans, recs[i].seq, cache->ext_id,
318
&write_offset, NULL);
661
320
write_offset += rec->size;
662
321
rec = CONST_PTR_OFFSET(rec, rec->size);
666
*size_r = write_offset - orig_write_offset;
327
mail_cache_link_records(struct mail_cache_transaction_ctx *ctx,
328
uint32_t write_offset)
330
struct mail_index_map *map;
331
struct mail_cache_record *rec;
332
const struct mail_cache_transaction_rec *recs;
333
const uint32_t *prev_offsetp;
334
ARRAY_TYPE(uint32_t) seq_offsets;
335
uint32_t i, seq_count, reset_id, prev_offset, *offsetp;
338
i_assert(ctx->min_seq != 0);
340
i_array_init(&seq_offsets, 64);
341
recs = array_get(&ctx->cache_data_seq, &seq_count);
342
rec = buffer_get_modifiable_data(ctx->cache_data, NULL);
343
for (i = 0; i < seq_count; i++) {
344
offsetp = array_idx_modifiable(&seq_offsets,
345
recs[i].seq - ctx->min_seq);
347
prev_offset = *offsetp;
349
mail_index_lookup_ext_full(ctx->view->trans_view, recs[i].seq,
350
ctx->cache->ext_id, &map,
354
if (prev_offsetp == NULL || *prev_offsetp == 0)
356
else if (mail_index_ext_get_reset_id(ctx->view->trans_view, map,
359
reset_id == ctx->cache_file_seq)
360
prev_offset = *prev_offsetp;
363
if (prev_offset >= write_offset) {
364
mail_cache_set_corrupted(ctx->cache,
365
"Cache record offset points outside existing file");
366
array_free(&seq_offsets);
371
if (prev_offset != 0) {
372
/* link this record to previous one */
373
rec->prev_offset = prev_offset;
374
ctx->cache->hdr_copy.continued_record_count++;
376
ctx->cache->hdr_copy.record_count++;
378
*offsetp = write_offset;
380
write_offset += rec->size;
381
rec = PTR_OFFSET(rec, rec->size);
383
array_free(&seq_offsets);
384
ctx->cache->hdr_modified = TRUE;
671
389
mail_cache_transaction_flush(struct mail_cache_transaction_ctx *ctx)
673
struct mail_cache *cache = ctx->cache;
674
const struct mail_cache_record *rec, *tmp_rec;
676
uint32_t write_offset, write_size, rec_pos, seq_idx, seq_limit;
677
size_t size, max_size;
678
unsigned int seq_count;
682
if (MAIL_CACHE_IS_UNUSABLE(cache))
392
uint32_t write_offset = 0;
395
i_assert(!ctx->cache->locked);
397
if (array_count(&ctx->cache_data_seq) == 0) {
398
/* we had done some changes, but they were aborted. */
399
i_assert(ctx->last_rec_pos == 0);
404
if (mail_cache_transaction_lock(ctx) <= 0)
685
commit = ctx->prev_seq == 0;
687
/* committing, remove the last dummy record */
688
buffer_set_used_size(ctx->cache_data, ctx->prev_pos);
691
if (ctx->cache_file_seq != ctx->cache->hdr->file_seq) {
692
/* cache file reopened - need to abort */
693
mail_cache_transaction_reset(ctx);
697
rec = buffer_get_data(ctx->cache_data, &size);
698
i_assert(ctx->prev_pos <= size);
700
seq = array_get(&ctx->cache_data_seq, &seq_count);
703
for (seq_idx = 0, rec_pos = 0; rec_pos < ctx->prev_pos;) {
704
max_size = ctx->prev_pos - rec_pos;
706
ret = mail_cache_transaction_get_space(ctx, rec->size,
707
max_size, &write_offset,
710
/* error / couldn't lock / cache file reopened */
714
if (rec_pos + max_size < ctx->prev_pos) {
715
/* see how much we can really write there */
717
for (size = 0; size + tmp_rec->size <= max_size; ) {
719
size += tmp_rec->size;
720
tmp_rec = CONST_PTR_OFFSET(tmp_rec,
725
seq_limit = seq_count;
728
/* write it to file */
729
i_assert(ctx->cache_file_seq == cache->hdr->file_seq);
730
if (mail_cache_write(cache, rec, max_size, write_offset) < 0)
733
if (mail_cache_transaction_update_index(ctx, rec, seq,
739
rec_pos += write_size;
740
rec = CONST_PTR_OFFSET(rec, write_size);
407
i_assert(ctx->cache_data != NULL);
408
i_assert(ctx->last_rec_pos <= ctx->cache_data->used);
410
/* we need to get the final write offset for linking records */
411
if (fstat(ctx->cache->fd, &st) < 0) {
412
if (!ESTALE_FSTAT(errno))
413
mail_cache_set_syscall_error(ctx->cache, "fstat()");
415
} else if ((uint32_t)-1 < st.st_size + ctx->last_rec_pos) {
416
mail_cache_set_corrupted(ctx->cache, "Cache file too large");
419
write_offset = st.st_size;
420
if (mail_cache_link_records(ctx, write_offset) < 0)
424
/* write to cache file */
426
mail_cache_append(ctx->cache, ctx->cache_data->data,
427
ctx->last_rec_pos, &write_offset) < 0)
430
/* update records' cache offsets to index */
431
ctx->records_written++;
432
ret = mail_cache_transaction_update_index(ctx, write_offset);
434
if (mail_cache_unlock(ctx->cache) < 0)
743
437
/* drop the written data from buffer */
744
438
buffer_copy(ctx->cache_data, 0,
745
ctx->cache_data, ctx->prev_pos, (size_t)-1);
439
ctx->cache_data, ctx->last_rec_pos, (size_t)-1);
746
440
buffer_set_used_size(ctx->cache_data,
747
buffer_get_used_size(ctx->cache_data) -
441
ctx->cache_data->used - ctx->last_rec_pos);
442
ctx->last_rec_pos = 0;
751
445
array_clear(&ctx->cache_data_seq);
450
mail_cache_transaction_update_last_rec(struct mail_cache_transaction_ctx *ctx)
452
struct mail_cache_transaction_rec *trans_rec;
453
struct mail_cache_record *rec;
457
data = buffer_get_modifiable_data(ctx->cache_data, &size);
458
rec = PTR_OFFSET(data, ctx->last_rec_pos);
459
rec->size = size - ctx->last_rec_pos;
460
i_assert(rec->size > sizeof(*rec));
462
if (rec->size > MAIL_CACHE_RECORD_MAX_SIZE) {
463
buffer_set_used_size(ctx->cache_data, ctx->last_rec_pos);
467
if (ctx->min_seq > ctx->prev_seq || ctx->min_seq == 0)
468
ctx->min_seq = ctx->prev_seq;
469
trans_rec = array_append_space(&ctx->cache_data_seq);
470
trans_rec->seq = ctx->prev_seq;
471
trans_rec->cache_data_pos = ctx->last_rec_pos;
472
ctx->last_rec_pos = size;
756
476
mail_cache_transaction_switch_seq(struct mail_cache_transaction_ctx *ctx)
758
struct mail_cache_record *rec, new_rec;
478
struct mail_cache_record new_rec;
762
480
if (ctx->prev_seq != 0) {
763
/* fix record size */
764
data = buffer_get_modifiable_data(ctx->cache_data, &size);
765
rec = PTR_OFFSET(data, ctx->prev_pos);
766
rec->size = size - ctx->prev_pos;
767
i_assert(rec->size > sizeof(*rec));
769
/* FIXME: here would be a good place to set prev_offset to
770
avoid doing it later, but avoid circular prev_offsets
771
when cache is updated multiple times within the same
774
array_append(&ctx->cache_data_seq, &ctx->prev_seq, 1);
775
ctx->prev_pos = size;
481
/* update previously added cache record's size */
482
mail_cache_transaction_update_last_rec(ctx);
776
483
} else if (ctx->cache_data == NULL) {
777
484
ctx->cache_data =
778
485
buffer_create_dynamic(default_pool,
779
MAIL_CACHE_WRITE_BUFFER);
486
MAIL_CACHE_INIT_WRITE_BUFFER);
780
487
i_array_init(&ctx->cache_data_seq, 64);
790
497
int mail_cache_transaction_commit(struct mail_cache_transaction_ctx **_ctx)
792
499
struct mail_cache_transaction_ctx *ctx = *_ctx;
793
struct mail_cache *cache = ctx->cache;
796
if (!ctx->changes || MAIL_CACHE_IS_UNUSABLE(cache)) {
797
mail_cache_transaction_free(_ctx);
801
if (mail_cache_transaction_lock(ctx) <= 0) {
802
mail_cache_transaction_rollback(_ctx);
806
if (ctx->prev_seq != 0)
807
mail_cache_transaction_switch_seq(ctx);
809
if (mail_cache_transaction_flush(ctx) < 0)
812
/* Here would be a good place to do fdatasync() to make sure
813
everything is written before offsets are updated to index.
814
However it slows down I/O unneededly and we're pretty good at
815
catching and fixing cache corruption, so we no longer do it. */
817
if (mail_cache_unlock(cache) < 0)
819
mail_cache_transaction_free(_ctx);
503
if (ctx->prev_seq != 0)
504
mail_cache_transaction_update_last_rec(ctx);
505
if (mail_cache_transaction_flush(ctx) < 0)
508
/* successfully wrote everything */
509
ctx->records_written = 0;
511
/* Here would be a good place to do fdatasync() to make sure
512
everything is written before offsets are updated to index.
513
However it slows down I/O unneededly and we're pretty good
514
at catching and fixing cache corruption, so we no longer do
517
mail_cache_transaction_rollback(_ctx);
823
void mail_cache_transaction_rollback(struct mail_cache_transaction_ctx **_ctx)
825
struct mail_cache_transaction_ctx *ctx = *_ctx;
827
mail_cache_transaction_free_reservations(ctx);
828
mail_cache_transaction_free(_ctx);
832
522
mail_cache_header_fields_write(struct mail_cache_transaction_ctx *ctx,
833
523
const buffer_t *buffer)
835
525
struct mail_cache *cache = ctx->cache;
836
size_t size = buffer->used;
837
526
uint32_t offset, hdr_offset;
839
if (mail_cache_transaction_get_space(ctx, size, size,
840
&offset, NULL, TRUE) <= 0)
528
i_assert(cache->locked);
843
if (mail_cache_write(cache, buffer->data, size, offset) < 0)
531
if (mail_cache_append(cache, buffer->data, buffer->used, &offset) < 0)
846
534
if (cache->index->fsync_mode == FSYNC_MODE_ALWAYS) {
1083
767
return mail_cache_field_exists(ctx->view, seq, field_idx) == 0;
1086
static int mail_cache_link_unlocked(struct mail_cache *cache,
1087
uint32_t old_offset, uint32_t new_offset)
1089
new_offset += offsetof(struct mail_cache_record, prev_offset);
1090
return mail_cache_write(cache, &old_offset, sizeof(old_offset),
1094
int mail_cache_link(struct mail_cache *cache, uint32_t old_offset,
1095
uint32_t new_offset)
1097
const struct mail_cache_record *rec;
1101
i_assert(cache->locked);
1103
if (MAIL_CACHE_IS_UNUSABLE(cache))
1106
/* this function is called for each added cache record (or cache
1107
extension record update actually) with new_offset pointing to the
1108
new record and old_offset pointing to the previous record.
1110
we want to keep the old and new records linked so both old and new
1111
cached data is found. normally they are already linked correctly.
1112
the problem only comes when multiple processes are adding cache
1113
records at the same time. we'd rather not lose those additions, so
1114
force the linking order to be new_offset -> old_offset if it isn't
1116
ret = mail_cache_map(cache, new_offset, sizeof(*rec), &data);
1119
mail_cache_set_corrupted(cache,
1120
"Cache record offset %u points outside file",
1126
if (rec->prev_offset == old_offset) {
1127
/* link is already correct */
1131
if (mail_cache_link_unlocked(cache, old_offset, new_offset) < 0)
1134
cache->hdr_copy.continued_record_count++;
1135
cache->hdr_modified = TRUE;
1139
static int mail_cache_delete_real(struct mail_cache *cache, uint32_t offset)
1141
const struct mail_cache_record *rec;
1142
struct mail_cache_loop_track loop_track;
1145
i_assert(cache->locked);
1147
/* we'll only update the deleted_space in header. we can't really
1148
do any actual deleting as other processes might still be using
1149
the data. also it's actually useful as some index views are still
1150
able to ask cached data from messages that have already been
770
void mail_cache_delete(struct mail_cache *cache)
772
i_assert(cache->locked);
774
/* we'll only update the deleted record count in the header. we can't
775
really do any actual deleting as other processes might still be
776
using the data. also it's actually useful as old index views are
777
still able to ask cached data for messages that have already been
1152
memset(&loop_track, 0, sizeof(loop_track));
1153
while (offset != 0 &&
1154
(ret = mail_cache_get_record(cache, offset, &rec)) == 0) {
1155
if (mail_cache_track_loops(&loop_track, offset, rec->size)) {
1156
mail_cache_set_corrupted(cache,
1157
"record list is circular");
1161
cache->hdr_copy.deleted_space += rec->size;
1162
offset = rec->prev_offset;
1167
int mail_cache_delete(struct mail_cache *cache, uint32_t offset)
1171
i_assert(cache->locked);
1173
ret = mail_cache_delete_real(cache, offset);
779
cache->hdr_copy.deleted_record_count++;
780
if (cache->hdr_copy.record_count > 0)
781
cache->hdr_copy.record_count--;
1175
782
cache->hdr_modified = TRUE;