~ubuntu-branches/ubuntu/wily/dovecot/wily

« back to all changes in this revision

Viewing changes to src/lib-index/mail-cache-transaction.c

  • Committer: Package Import Robot
  • Author(s): Jaldhar H. Vyas
  • Date: 2013-09-09 00:57:32 UTC
  • mfrom: (1.13.11)
  • mto: (4.8.5 experimental) (1.16.1)
  • mto: This revision was merged to the branch mainline in revision 97.
  • Revision ID: package-import@ubuntu.com-20130909005732-dn1eell8srqbhh0e
Tags: upstream-2.2.5
ImportĀ upstreamĀ versionĀ 2.2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Copyright (c) 2003-2012 Dovecot authors, see the included COPYING file */
 
1
/* Copyright (c) 2003-2013 Dovecot authors, see the included COPYING file */
2
2
 
3
3
#include "lib.h"
4
4
#include "ioloop.h"
15
15
#include <stddef.h>
16
16
#include <sys/stat.h>
17
17
 
18
 
#define MAIL_CACHE_WRITE_BUFFER 32768
 
18
#define MAIL_CACHE_INIT_WRITE_BUFFER (1024*16)
 
19
#define MAIL_CACHE_MAX_WRITE_BUFFER (1024*256)
19
20
 
20
21
#define CACHE_TRANS_CONTEXT(obj) \
21
22
        MODULE_CONTEXT(obj, cache_mail_index_transaction_module)
22
23
 
23
 
struct mail_cache_reservation {
24
 
        uint32_t offset;
25
 
        uint32_t size;
 
24
struct mail_cache_transaction_rec {
 
25
        uint32_t seq;
 
26
        uint32_t cache_data_pos;
26
27
};
27
28
 
28
29
struct mail_cache_transaction_ctx {
37
38
        uint32_t first_new_seq;
38
39
 
39
40
        buffer_t *cache_data;
40
 
        ARRAY_DEFINE(cache_data_seq, uint32_t);
41
 
        uint32_t prev_seq;
42
 
        size_t prev_pos;
 
41
        ARRAY(struct mail_cache_transaction_rec) cache_data_seq;
 
42
        uint32_t prev_seq, min_seq;
 
43
        size_t last_rec_pos;
43
44
 
44
 
        ARRAY_DEFINE(reservations, struct mail_cache_reservation);
45
 
        uint32_t reserved_space_offset, reserved_space;
46
 
        uint32_t last_grow_size;
 
45
        unsigned int records_written;
47
46
 
48
47
        unsigned int tried_compression:1;
49
48
        unsigned int changes:1;
52
51
static MODULE_CONTEXT_DEFINE_INIT(cache_mail_index_transaction_module,
53
52
                                  &mail_index_module_register);
54
53
 
55
 
static void
56
 
mail_cache_transaction_free_reservations(struct mail_cache_transaction_ctx *ctx);
57
 
static int mail_cache_link_unlocked(struct mail_cache *cache,
58
 
                                    uint32_t old_offset, uint32_t new_offset);
 
54
static int mail_cache_transaction_lock(struct mail_cache_transaction_ctx *ctx);
59
55
 
60
56
static void mail_index_transaction_cache_reset(struct mail_index_transaction *t)
61
57
{
73
69
        struct mail_cache_transaction_ctx *ctx = CACHE_TRANS_CONTEXT(t);
74
70
        struct mail_index_transaction_vfuncs super = ctx->super;
75
71
 
76
 
        mail_cache_transaction_commit(&ctx);
 
72
        /* a failed cache commit isn't important enough to fail the entire
 
73
           index transaction, so we'll just ignore it */
 
74
        (void)mail_cache_transaction_commit(&ctx);
77
75
        return super.commit(t, result_r);
78
76
}
79
77
 
103
101
        ctx->cache = view->cache;
104
102
        ctx->view = view;
105
103
        ctx->trans = t;
106
 
        i_array_init(&ctx->reservations, 32);
107
104
 
108
105
        i_assert(view->transaction == NULL);
109
106
        view->transaction = ctx;
130
127
        if (array_is_created(&ctx->cache_data_seq))
131
128
                array_clear(&ctx->cache_data_seq);
132
129
        ctx->prev_seq = 0;
133
 
        ctx->prev_pos = 0;
134
 
 
135
 
        array_clear(&ctx->reservations);
136
 
        ctx->reserved_space_offset = 0;
137
 
        ctx->reserved_space = 0;
138
 
        ctx->last_grow_size = 0;
 
130
        ctx->last_rec_pos = 0;
139
131
 
140
132
        ctx->changes = FALSE;
141
133
}
142
134
 
143
 
static void
144
 
mail_cache_transaction_free(struct mail_cache_transaction_ctx **_ctx)
 
135
void mail_cache_transaction_rollback(struct mail_cache_transaction_ctx **_ctx)
145
136
{
146
137
        struct mail_cache_transaction_ctx *ctx = *_ctx;
147
138
 
148
139
        *_ctx = NULL;
149
140
 
 
141
        if (ctx->records_written > 0) {
 
142
                /* we already wrote to the cache file. we can't (or don't want
 
143
                   to) delete that data, so just mark it as deleted space */
 
144
                if (mail_cache_transaction_lock(ctx) > 0) {
 
145
                        ctx->cache->hdr_copy.deleted_record_count +=
 
146
                                ctx->records_written;
 
147
                        (void)mail_cache_unlock(ctx->cache);
 
148
                }
 
149
        }
 
150
 
150
151
        MODULE_CONTEXT_UNSET(ctx->trans, cache_mail_index_transaction_module);
151
152
 
152
153
        ctx->view->transaction = NULL;
157
158
                buffer_free(&ctx->cache_data);
158
159
        if (array_is_created(&ctx->cache_data_seq))
159
160
                array_free(&ctx->cache_data_seq);
160
 
        array_free(&ctx->reservations);
161
161
        i_free(ctx);
162
162
}
163
163
 
277
277
        return 1;
278
278
}
279
279
 
280
 
static int mail_cache_grow_file(struct mail_cache *cache, size_t size)
281
 
{
282
 
        struct stat st;
283
 
        uoff_t new_fsize, grow_size;
284
 
 
285
 
        i_assert(cache->locked);
286
 
 
287
 
        /* grow the file */
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)
291
 
                grow_size = 16384;
292
 
        new_fsize += grow_size;
293
 
        new_fsize &= ~1023;
294
 
 
295
 
        if (fstat(cache->fd, &st) < 0) {
296
 
                mail_cache_set_syscall_error(cache, "fstat()");
297
 
                return -1;
298
 
        }
299
 
 
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()");
303
 
                        return -1;
304
 
                }
305
 
        }
306
 
        return 0;
307
 
}
308
 
 
309
 
static bool mail_cache_unlink_hole(struct mail_cache *cache, size_t size,
310
 
                                   struct mail_cache_hole_header *hole_r)
311
 
{
312
 
        struct mail_cache_header *hdr = &cache->hdr_copy;
313
 
        struct mail_cache_hole_header hole;
314
 
        uint32_t offset, prev_offset;
315
 
 
316
 
        i_assert(cache->locked);
317
 
 
318
 
        if (hdr->minor_version > 0)
319
 
                return FALSE; /* this is record_count field in v2.2+ */
320
 
 
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()");
325
 
                        return FALSE;
326
 
                }
327
 
 
328
 
                if (hole.magic != MAIL_CACHE_HOLE_HEADER_MAGIC) {
329
 
                        mail_cache_set_corrupted(cache,
330
 
                                "Invalid magic in hole header");
331
 
                        return FALSE;
332
 
                }
333
 
 
334
 
                if (hole.size >= size)
335
 
                        break;
336
 
 
337
 
                prev_offset = offset;
338
 
                offset = hole.next_offset;
339
 
        }
340
 
        if (offset == 0)
341
 
                return FALSE;
342
 
 
343
 
        if (prev_offset == 0)
344
 
                hdr->hole_offset = hole.next_offset;
345
 
        else {
346
 
                if (mail_cache_write(cache, &hole.next_offset,
347
 
                                     sizeof(hole.next_offset), prev_offset) < 0)
348
 
                        return FALSE;
349
 
        }
350
 
        hdr->deleted_space -= hole.size;
351
 
        cache->hdr_modified = TRUE;
352
 
 
353
 
        hole_r->next_offset = offset;
354
 
        hole_r->size = hole.size;
355
 
        return TRUE;
356
 
}
357
 
 
358
 
static void
359
 
mail_cache_transaction_add_reservation(struct mail_cache_transaction_ctx *ctx,
360
 
                                       uint32_t offset, uint32_t size)
361
 
{
362
 
        struct mail_cache_reservation res;
363
 
 
364
 
        ctx->reserved_space_offset = offset;
365
 
        ctx->reserved_space = size;
366
 
 
367
 
        res.offset = offset;
368
 
        res.size = size;
369
 
 
370
 
        array_append(&ctx->reservations, &res, 1);
371
 
}
372
 
 
373
 
static void
374
 
mail_cache_transaction_partial_commit(struct mail_cache_transaction_ctx *ctx,
375
 
                                      uint32_t offset, uint32_t size)
376
 
{
377
 
        struct mail_cache_reservation *res;
 
280
const struct mail_cache_record *
 
281
mail_cache_transaction_lookup_rec(struct mail_cache_transaction_ctx *ctx,
 
282
                                  unsigned int seq,
 
283
                                  unsigned int *trans_next_idx)
 
284
{
 
285
        const struct mail_cache_transaction_rec *recs;
378
286
        unsigned int i, count;
379
287
 
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;
384
 
        }
385
 
 
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);
391
 
                        } else {
392
 
                                i_assert(res[i].size > size);
393
 
                                res[i].offset += size;
394
 
                                res[i].size -= size;
395
 
                        }
396
 
                        break;
397
 
                }
398
 
        }
399
 
}
400
 
 
401
 
static int
402
 
mail_cache_transaction_reserve_more(struct mail_cache_transaction_ctx *ctx,
403
 
                                    size_t block_size, bool commit)
404
 
{
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;
409
 
        unsigned int count;
410
 
 
411
 
        i_assert(cache->locked);
412
 
 
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,
416
 
                                                       hole.size);
417
 
                return 0;
418
 
        }
419
 
 
420
 
        if (MAIL_CACHE_IS_UNUSABLE(cache)) {
421
 
                /* mail_cache_unlink_hole() could have noticed corruption */
422
 
                return -1;
423
 
        }
424
 
 
425
 
        if ((uint32_t)-1 - hdr->used_file_size < block_size) {
426
 
                mail_index_set_error(cache->index, "Cache file too large: %s",
427
 
                                     cache->filepath);
428
 
                return -1;
429
 
        }
430
 
 
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;
436
 
 
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;
440
 
                }
441
 
        }
442
 
 
443
 
        if (mail_cache_grow_file(ctx->cache, block_size) < 0)
444
 
                return -1;
445
 
 
446
 
        if (ctx->reserved_space_offset + ctx->reserved_space ==
447
 
            hdr->used_file_size) {
448
 
                /* we can simply grow it */
449
 
 
450
 
                /* grow reservation. it's probably the last one in the buffer,
451
 
                   but it's not guarateed because we might have used holes
452
 
                   as well */
453
 
                reservations = array_get_modifiable(&ctx->reservations, &count);
454
 
 
455
 
                do {
456
 
                        i_assert(count > 0);
457
 
                        count--;
458
 
                } while (reservations[count].offset +
459
 
                         reservations[count].size != hdr->used_file_size);
460
 
 
461
 
                reservations[count].size += block_size;
462
 
                ctx->reserved_space += block_size;
463
 
        } else {
464
 
                mail_cache_transaction_add_reservation(ctx, hdr->used_file_size,
465
 
                                                       block_size);
466
 
        }
467
 
 
468
 
        cache->hdr_modified = TRUE;
469
 
        hdr->used_file_size = ctx->reserved_space_offset + ctx->reserved_space;
470
 
        return 0;
471
 
}
472
 
 
473
 
static void
474
 
mail_cache_free_space(struct mail_cache *cache, uint32_t offset, uint32_t size)
475
 
{
476
 
        struct mail_cache_hole_header hole;
477
 
 
478
 
        i_assert(cache->locked);
479
 
 
480
 
        if (MAIL_CACHE_IS_UNUSABLE(cache))
481
 
                return;
482
 
 
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;
491
 
                hole.size = size;
492
 
                hole.magic = MAIL_CACHE_HOLE_HEADER_MAGIC;
493
 
 
494
 
                if (mail_cache_write(cache, &hole, sizeof(hole), offset) < 0)
495
 
                        return;
496
 
 
497
 
                cache->hdr_copy.deleted_space += size;
498
 
                cache->hdr_copy.hole_offset = offset;
499
 
                cache->hdr_modified = TRUE;
500
 
        }
501
 
}
502
 
 
503
 
static void
504
 
mail_cache_transaction_free_reservations(struct mail_cache_transaction_ctx *ctx)
505
 
{
506
 
        const struct mail_cache_reservation *reservations;
507
 
        unsigned int count;
508
 
 
509
 
        if (ctx->reserved_space == 0 && array_count(&ctx->reservations) == 0)
510
 
                return;
511
 
 
512
 
        if (mail_cache_transaction_lock(ctx) <= 0)
513
 
                return;
514
 
 
515
 
        reservations = array_get(&ctx->reservations, &count);
516
 
 
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 */
519
 
        while (count > 0) {
520
 
                count--;
521
 
                mail_cache_free_space(ctx->cache,
522
 
                                      reservations[count].offset,
523
 
                                      reservations[count].size);
524
 
        }
525
 
        (void)mail_cache_unlock(ctx->cache);
526
 
}
527
 
 
528
 
static int
529
 
mail_cache_transaction_free_space(struct mail_cache_transaction_ctx *ctx)
530
 
{
531
 
        bool locked = ctx->cache->locked;
532
 
 
533
 
        if (ctx->reserved_space == 0)
534
 
                return 0;
535
 
 
536
 
        if (!locked) {
537
 
                if (mail_cache_transaction_lock(ctx) <= 0)
538
 
                        return 0;
539
 
        }
540
 
 
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;
548
 
        }
549
 
 
550
 
        if (!locked) {
551
 
                if (mail_cache_unlock(ctx->cache) < 0)
552
 
                        return -1;
553
 
        }
554
 
        return 0;
555
 
}
556
 
 
557
 
static int
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,
561
 
                                 bool commit)
562
 
{
563
 
        bool locked = ctx->cache->locked;
564
 
        uint32_t cache_file_seq;
565
 
        size_t size;
566
 
        int ret;
567
 
 
568
 
        i_assert((min_size & 3) == 0);
569
 
        i_assert((max_size & 3) == 0);
570
 
 
571
 
        if (min_size > ctx->reserved_space) {
572
 
                /* not enough preallocated space in transaction, get more */
573
 
                cache_file_seq = ctx->cache_file_seq;
574
 
                if (!locked) {
575
 
                        if ((ret = mail_cache_transaction_lock(ctx)) <= 0)
576
 
                                return ret;
577
 
                }
578
 
                ret = mail_cache_transaction_reserve_more(ctx, max_size,
579
 
                                                          commit);
580
 
                if (!locked) {
581
 
                        if (mail_cache_unlock(ctx->cache) < 0)
582
 
                                return -1;
583
 
                }
584
 
 
585
 
                if (ret < 0)
586
 
                        return -1;
587
 
 
588
 
                if (cache_file_seq != ctx->cache_file_seq) {
589
 
                        /* cache file reopened - need to abort */
590
 
                        return 0;
591
 
                }
592
 
 
593
 
                size = max_size;
594
 
        } else {
595
 
                size = I_MIN(max_size, ctx->reserved_space);
596
 
        }
597
 
 
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);
604
 
 
605
 
        if (size == max_size && commit) {
606
 
                /* final commit - see if we can free the rest of the
607
 
                   reserved space */
608
 
                if (mail_cache_transaction_free_space(ctx) < 0)
609
 
                        return -1;
610
 
        }
611
 
 
612
 
        i_assert(size >= min_size);
613
 
        return 1;
 
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);
 
294
                }
 
295
        }
 
296
        *trans_next_idx = i;
 
297
        return NULL;
614
298
}
615
299
 
616
300
static int
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,
621
 
                                    uint32_t *size_r)
 
302
                                    uint32_t write_offset)
622
303
{
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;
625
308
 
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
631
314
           synced. */
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
641
 
                           ourself here. */
642
 
                        if (old_offset > write_offset) {
643
 
                                if (mail_cache_link_unlocked(cache, old_offset,
644
 
                                                             write_offset) < 0)
645
 
                                        return -1;
646
 
                        } else {
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],
652
 
                                                      cache->ext_id,
653
 
                                                      &old_offset, NULL);
654
 
                                if (mail_cache_link_unlocked(cache,
655
 
                                                             write_offset,
656
 
                                                             old_offset) < 0)
657
 
                                        return -1;
658
 
                        }
659
 
                }
 
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);
660
319
 
661
320
                write_offset += rec->size;
662
321
                rec = CONST_PTR_OFFSET(rec, rec->size);
663
322
        }
664
 
 
665
 
        *seq_idx = i;
666
 
        *size_r = write_offset - orig_write_offset;
 
323
        return 0;
 
324
}
 
325
 
 
326
static int
 
327
mail_cache_link_records(struct mail_cache_transaction_ctx *ctx,
 
328
                        uint32_t write_offset)
 
329
{
 
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;
 
336
        const void *data;
 
337
 
 
338
        i_assert(ctx->min_seq != 0);
 
339
 
 
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);
 
346
                if (*offsetp != 0)
 
347
                        prev_offset = *offsetp;
 
348
                else {
 
349
                        mail_index_lookup_ext_full(ctx->view->trans_view, recs[i].seq,
 
350
                                                   ctx->cache->ext_id, &map,
 
351
                                                   &data, NULL);
 
352
                        prev_offsetp = data;
 
353
 
 
354
                        if (prev_offsetp == NULL || *prev_offsetp == 0)
 
355
                                prev_offset = 0;
 
356
                        else if (mail_index_ext_get_reset_id(ctx->view->trans_view, map,
 
357
                                                             ctx->cache->ext_id,
 
358
                                                             &reset_id) &&
 
359
                                 reset_id == ctx->cache_file_seq)
 
360
                                prev_offset = *prev_offsetp;
 
361
                        else
 
362
                                prev_offset = 0;
 
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);
 
367
                                return -1;
 
368
                        }
 
369
                }
 
370
 
 
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++;
 
375
                } else {
 
376
                        ctx->cache->hdr_copy.record_count++;
 
377
                }
 
378
                *offsetp = write_offset;
 
379
 
 
380
                write_offset += rec->size;
 
381
                rec = PTR_OFFSET(rec, rec->size);
 
382
        }
 
383
        array_free(&seq_offsets);
 
384
        ctx->cache->hdr_modified = TRUE;
667
385
        return 0;
668
386
}
669
387
 
670
388
static int
671
389
mail_cache_transaction_flush(struct mail_cache_transaction_ctx *ctx)
672
390
{
673
 
        struct mail_cache *cache = ctx->cache;
674
 
        const struct mail_cache_record *rec, *tmp_rec;
675
 
        const uint32_t *seq;
676
 
        uint32_t write_offset, write_size, rec_pos, seq_idx, seq_limit;
677
 
        size_t size, max_size;
678
 
        unsigned int seq_count;
679
 
        int ret;
680
 
        bool commit;
681
 
 
682
 
        if (MAIL_CACHE_IS_UNUSABLE(cache))
 
391
        struct stat st;
 
392
        uint32_t write_offset = 0;
 
393
        int ret = 0;
 
394
 
 
395
        i_assert(!ctx->cache->locked);
 
396
 
 
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);
 
400
                ctx->min_seq = 0;
 
401
                return 0;
 
402
        }
 
403
 
 
404
        if (mail_cache_transaction_lock(ctx) <= 0)
683
405
                return -1;
684
406
 
685
 
        commit = ctx->prev_seq == 0;
686
 
        if (commit) {
687
 
                /* committing, remove the last dummy record */
688
 
                buffer_set_used_size(ctx->cache_data, ctx->prev_pos);
689
 
        }
690
 
 
691
 
        if (ctx->cache_file_seq != ctx->cache->hdr->file_seq) {
692
 
                /* cache file reopened - need to abort */
693
 
                mail_cache_transaction_reset(ctx);
694
 
                return 0;
695
 
        }
696
 
 
697
 
        rec = buffer_get_data(ctx->cache_data, &size);
698
 
        i_assert(ctx->prev_pos <= size);
699
 
 
700
 
        seq = array_get(&ctx->cache_data_seq, &seq_count);
701
 
        seq_limit = 0;
702
 
 
703
 
        for (seq_idx = 0, rec_pos = 0; rec_pos < ctx->prev_pos;) {
704
 
                max_size = ctx->prev_pos - rec_pos;
705
 
 
706
 
                ret = mail_cache_transaction_get_space(ctx, rec->size,
707
 
                                                       max_size, &write_offset,
708
 
                                                       &max_size, commit);
709
 
                if (ret <= 0) {
710
 
                        /* error / couldn't lock / cache file reopened */
711
 
                        return ret;
712
 
                }
713
 
 
714
 
                if (rec_pos + max_size < ctx->prev_pos) {
715
 
                        /* see how much we can really write there */
716
 
                        tmp_rec = rec;
717
 
                        for (size = 0; size + tmp_rec->size <= max_size; ) {
718
 
                                seq_limit++;
719
 
                                size += tmp_rec->size;
720
 
                                tmp_rec = CONST_PTR_OFFSET(tmp_rec,
721
 
                                                           tmp_rec->size);
722
 
                        }
723
 
                        max_size = size;
724
 
                } else {
725
 
                        seq_limit = seq_count;
726
 
                }
727
 
 
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)
731
 
                        return -1;
732
 
 
733
 
                if (mail_cache_transaction_update_index(ctx, rec, seq,
734
 
                                                        &seq_idx, seq_limit,
735
 
                                                        write_offset,
736
 
                                                        &write_size) < 0)
737
 
                        return -1;
738
 
 
739
 
                rec_pos += write_size;
740
 
                rec = CONST_PTR_OFFSET(rec, write_size);
741
 
        }
 
407
        i_assert(ctx->cache_data != NULL);
 
408
        i_assert(ctx->last_rec_pos <= ctx->cache_data->used);
 
409
 
 
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()");
 
414
                ret = -1;
 
415
        } else if ((uint32_t)-1 < st.st_size + ctx->last_rec_pos) {
 
416
                mail_cache_set_corrupted(ctx->cache, "Cache file too large");
 
417
                ret = -1;
 
418
        } else {
 
419
                write_offset = st.st_size;
 
420
                if (mail_cache_link_records(ctx, write_offset) < 0)
 
421
                        ret = -1;
 
422
        }
 
423
 
 
424
        /* write to cache file */
 
425
        if (ret < 0 ||
 
426
            mail_cache_append(ctx->cache, ctx->cache_data->data,
 
427
                              ctx->last_rec_pos, &write_offset) < 0)
 
428
                ret = -1;
 
429
        else {
 
430
                /* update records' cache offsets to index */
 
431
                ctx->records_written++;
 
432
                ret = mail_cache_transaction_update_index(ctx, write_offset);
 
433
        }
 
434
        if (mail_cache_unlock(ctx->cache) < 0)
 
435
                ret = -1;
742
436
 
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) -
748
 
                             ctx->prev_pos);
749
 
        ctx->prev_pos = 0;
 
441
                             ctx->cache_data->used - ctx->last_rec_pos);
 
442
        ctx->last_rec_pos = 0;
 
443
        ctx->min_seq = 0;
750
444
 
751
445
        array_clear(&ctx->cache_data_seq);
752
 
        return 1;
 
446
        return ret;
 
447
}
 
448
 
 
449
static void
 
450
mail_cache_transaction_update_last_rec(struct mail_cache_transaction_ctx *ctx)
 
451
{
 
452
        struct mail_cache_transaction_rec *trans_rec;
 
453
        struct mail_cache_record *rec;
 
454
        void *data;
 
455
        size_t size;
 
456
 
 
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));
 
461
 
 
462
        if (rec->size > MAIL_CACHE_RECORD_MAX_SIZE) {
 
463
                buffer_set_used_size(ctx->cache_data, ctx->last_rec_pos);
 
464
                return;
 
465
        }
 
466
 
 
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;
753
473
}
754
474
 
755
475
static void
756
476
mail_cache_transaction_switch_seq(struct mail_cache_transaction_ctx *ctx)
757
477
{
758
 
        struct mail_cache_record *rec, new_rec;
759
 
        void *data;
760
 
        size_t size;
 
478
        struct mail_cache_record new_rec;
761
479
 
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));
768
 
 
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
772
 
                   transaction */
773
 
 
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);
781
488
        }
782
489
 
790
497
int mail_cache_transaction_commit(struct mail_cache_transaction_ctx **_ctx)
791
498
{
792
499
        struct mail_cache_transaction_ctx *ctx = *_ctx;
793
 
        struct mail_cache *cache = ctx->cache;
794
500
        int ret = 0;
795
501
 
796
 
        if (!ctx->changes || MAIL_CACHE_IS_UNUSABLE(cache)) {
797
 
                mail_cache_transaction_free(_ctx);
798
 
                return 0;
799
 
        }
800
 
 
801
 
        if (mail_cache_transaction_lock(ctx) <= 0) {
802
 
                mail_cache_transaction_rollback(_ctx);
803
 
                return -1;
804
 
        }
805
 
 
806
 
        if (ctx->prev_seq != 0)
807
 
                mail_cache_transaction_switch_seq(ctx);
808
 
 
809
 
        if (mail_cache_transaction_flush(ctx) < 0)
810
 
                ret = -1;
811
 
 
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. */
816
 
 
817
 
        if (mail_cache_unlock(cache) < 0)
818
 
                ret = -1;
819
 
        mail_cache_transaction_free(_ctx);
 
502
        if (ctx->changes) {
 
503
                if (ctx->prev_seq != 0)
 
504
                        mail_cache_transaction_update_last_rec(ctx);
 
505
                if (mail_cache_transaction_flush(ctx) < 0)
 
506
                        ret = -1;
 
507
                else {
 
508
                        /* successfully wrote everything */
 
509
                        ctx->records_written = 0;
 
510
                }
 
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
 
515
                   it. */
 
516
        }
 
517
        mail_cache_transaction_rollback(_ctx);
820
518
        return ret;
821
519
}
822
520
 
823
 
void mail_cache_transaction_rollback(struct mail_cache_transaction_ctx **_ctx)
824
 
{
825
 
        struct mail_cache_transaction_ctx *ctx = *_ctx;
826
 
 
827
 
        mail_cache_transaction_free_reservations(ctx);
828
 
        mail_cache_transaction_free(_ctx);
829
 
}
830
 
 
831
521
static int
832
522
mail_cache_header_fields_write(struct mail_cache_transaction_ctx *ctx,
833
523
                               const buffer_t *buffer)
834
524
{
835
525
        struct mail_cache *cache = ctx->cache;
836
 
        size_t size = buffer->used;
837
526
        uint32_t offset, hdr_offset;
838
527
 
839
 
        if (mail_cache_transaction_get_space(ctx, size, size,
840
 
                                             &offset, NULL, TRUE) <= 0)
841
 
                return -1;
 
528
        i_assert(cache->locked);
842
529
 
843
 
        if (mail_cache_write(cache, buffer->data, size, offset) < 0)
 
530
        offset = 0;
 
531
        if (mail_cache_append(cache, buffer->data, buffer->used, &offset) < 0)
844
532
                return -1;
845
533
 
846
534
        if (cache->index->fsync_mode == FSYNC_MODE_ALWAYS) {
849
537
                        return -1;
850
538
                }
851
539
        }
 
540
        /* find offset to the previous header's "next_offset" field */
852
541
        if (mail_cache_header_fields_get_next_offset(cache, &hdr_offset) < 0)
853
542
                return -1;
854
543
 
855
 
        /* if we rollback the transaction, we must not overwrite this
856
 
           area because it's already committed after updating the
857
 
           header offset */
858
 
        mail_cache_transaction_partial_commit(ctx, offset, size);
859
 
 
860
 
        /* after it's guaranteed to be in disk, update header offset */
 
544
        /* update the next_offset offset, so our new header will be found */
861
545
        offset = mail_index_uint32_to_offset(offset);
862
546
        if (mail_cache_write(cache, &offset, sizeof(offset), hdr_offset) < 0)
863
547
                return -1;
990
674
        mail_cache_decision_add(ctx->view, seq, field_idx);
991
675
 
992
676
        fixed_size = ctx->cache->fields[field_idx].field.field_size;
993
 
        i_assert(fixed_size == (unsigned int)-1 || fixed_size == data_size);
 
677
        i_assert(fixed_size == UINT_MAX || fixed_size == data_size);
994
678
 
995
679
        data_size32 = (uint32_t)data_size;
996
680
 
1011
695
                     &ctx->view->cached_exists_value, 1);
1012
696
 
1013
697
        full_size = (data_size + 3) & ~3;
1014
 
        if (fixed_size == (unsigned int)-1)
 
698
        if (fixed_size == UINT_MAX)
1015
699
                full_size += sizeof(data_size32);
1016
700
 
1017
 
        if (ctx->cache_data->used + full_size >
1018
 
            buffer_get_size(ctx->cache_data) && ctx->prev_pos > 0) {
 
701
        if (ctx->cache_data->used + full_size > MAIL_CACHE_MAX_WRITE_BUFFER &&
 
702
            ctx->last_rec_pos > 0) {
1019
703
                /* time to flush our buffer. if flushing fails because the
1020
704
                   cache file had been compressed and was reopened, return
1021
705
                   without adding the cached data since cache_data buffer
1022
706
                   doesn't contain the cache_rec anymore. */
1023
 
                if (mail_cache_transaction_flush(ctx) <= 0) {
 
707
                if (mail_cache_transaction_flush(ctx) < 0) {
1024
708
                        /* make sure the transaction is reset, so we don't
1025
709
                           constantly try to flush for each call to this
1026
710
                           function */
1030
714
        }
1031
715
 
1032
716
        buffer_append(ctx->cache_data, &file_field, sizeof(file_field));
1033
 
        if (fixed_size == (unsigned int)-1) {
 
717
        if (fixed_size == UINT_MAX) {
1034
718
                buffer_append(ctx->cache_data, &data_size32,
1035
719
                              sizeof(data_size32));
1036
720
        }
1083
767
        return mail_cache_field_exists(ctx->view, seq, field_idx) == 0;
1084
768
}
1085
769
 
1086
 
static int mail_cache_link_unlocked(struct mail_cache *cache,
1087
 
                                    uint32_t old_offset, uint32_t new_offset)
1088
 
{
1089
 
        new_offset += offsetof(struct mail_cache_record, prev_offset);
1090
 
        return mail_cache_write(cache, &old_offset, sizeof(old_offset),
1091
 
                                new_offset);
1092
 
}
1093
 
 
1094
 
int mail_cache_link(struct mail_cache *cache, uint32_t old_offset,
1095
 
                    uint32_t new_offset)
1096
 
{
1097
 
        const struct mail_cache_record *rec;
1098
 
        const void *data;
1099
 
        int ret;
1100
 
 
1101
 
        i_assert(cache->locked);
1102
 
 
1103
 
        if (MAIL_CACHE_IS_UNUSABLE(cache))
1104
 
                return -1;
1105
 
 
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.
1109
 
 
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
1115
 
           already. */
1116
 
        ret = mail_cache_map(cache, new_offset, sizeof(*rec), &data);
1117
 
        if (ret <= 0) {
1118
 
                if (ret == 0) {
1119
 
                        mail_cache_set_corrupted(cache,
1120
 
                                "Cache record offset %u points outside file",
1121
 
                                new_offset);
1122
 
                }
1123
 
                return -1;
1124
 
        }
1125
 
        rec = data;
1126
 
        if (rec->prev_offset == old_offset) {
1127
 
                /* link is already correct */
1128
 
                return 0;
1129
 
        }
1130
 
 
1131
 
        if (mail_cache_link_unlocked(cache, old_offset, new_offset) < 0)
1132
 
                return -1;
1133
 
 
1134
 
        cache->hdr_copy.continued_record_count++;
1135
 
        cache->hdr_modified = TRUE;
1136
 
        return 0;
1137
 
}
1138
 
 
1139
 
static int mail_cache_delete_real(struct mail_cache *cache, uint32_t offset)
1140
 
{
1141
 
        const struct mail_cache_record *rec;
1142
 
        struct mail_cache_loop_track loop_track;
1143
 
        int ret = 0;
1144
 
 
1145
 
        i_assert(cache->locked);
1146
 
 
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)
 
771
{
 
772
        i_assert(cache->locked);
 
773
 
 
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
1151
778
           expunged. */
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");
1158
 
                        return -1;
1159
 
                }
1160
 
 
1161
 
                cache->hdr_copy.deleted_space += rec->size;
1162
 
                offset = rec->prev_offset;
1163
 
        }
1164
 
        return ret;
1165
 
}
1166
 
 
1167
 
int mail_cache_delete(struct mail_cache *cache, uint32_t offset)
1168
 
{
1169
 
        int ret;
1170
 
 
1171
 
        i_assert(cache->locked);
1172
 
        T_BEGIN {
1173
 
                ret = mail_cache_delete_real(cache, offset);
1174
 
        } T_END;
 
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;
1176
 
        return ret;
1177
783
}