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

« back to all changes in this revision

Viewing changes to src/lib-index/mail-index-strmap.c

  • Committer: Bazaar Package Importer
  • Author(s): CHuck Short, Chuck Short
  • Date: 2009-11-06 00:47:29 UTC
  • mfrom: (4.1.9 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091106004729-i39n7v9e7d4h51f6
Tags: 1:1.2.6-1ubuntu1
* Merge from debian testing, remaining changes:
  Add new binary pkg dovecot-postfix that integrates postfix and dovecot
  automatically: (LP: #164837)
  + debian/control:
    - add new binary with short description
    - set Architecture all for dovecot-postfix (LP: #329878)
  + debian/dovecot-postfix.postinst:
    - create initial certificate symlinks to snakeoil.
    - set up postfix with postconf to:
      - use Maildir/ as the default mailbox.
      - use dovecot as the sasl authentication server.
      - use dovecot LDA (deliver).
      - use tls for smtp{d} services.
    - fix certificates paths in postfix' main.cf
    - add reject_unauth_destination to postfix' recipient restrictions
    - add reject_unknown_sender_domain to postfix' sender restrictions
    - rename configuration name on remove, delete on purge
    - restart dovecot after linking certificates
    - handle use case when postfix is unconfigurated
   + debian/dovecot-postfix.dirs: create backup directory for postfix's configuration
   + restart postfix and dovecot.
   + debian/dovecot-postfix.postrm:
     - remove all dovecot related configuration from postfix.
     - restart postfix and dovecot.
   + debian/dovecot-common.init:
     - check if /etc/dovecot/dovecot-postfix.conf exists and use it
       as the configuration file if so.
   + debian/patches/warning-ubuntu-postfix.dpatch
     - add warning about dovecot-postfix.conf in dovecot default 
       configuration file
   + debian/patches/dovecot-postfix.conf.diff:
     - Ubuntu server custom changes to the default dovecot configuration for
       better interfation with postfix
     - enable sieve plugin
   + debian/patches/dovecot-postfix.conf.diff:
     + Ubuntu server custom changes to the default dovecot configuration for
       better integration with postfix:
       - enable imap, pop3, imaps, pop3s and managesieve by default.
       - enable dovecot LDA (deliver).
       - enable SASL auth socket in postfix private directory.
   + debian/rules:
     - copy, patch and install dovecot-postfix.conf in /etc/dovecot/.
     - build architecure independent packages too
   + Use Snakeoil SSL certificates by default.
     - debian/control: Depend on ssl-cert.
     - debian/patches/ssl-cert-snakeoil.dpatch: Change default SSL cert
       paths to snakeoil.
     - debian/dovecot-common.postinst: Relax grep for SSL_* a bit.
   + Add autopkgtest to debian/tests/*.
   + Fast TearDown: Update the lsb init header to not stop in level 6.
   + Add ufw integration:
     - Created debian/dovecot-common.ufw.profile.
     - debian/rules:
       + install profile
     - debian/control:
       + Suggest ufw
   + debian/{control,rules}: enable PIE hardening.
   + dovecot-imapd, dovecot-pop3: Replaces dovecot-common (<< 1:1.1). LP: #254721
   + debian/control:
     - Update Vcs-* headers.
   + debian/rules:
     - Create emtpy stamp.h.in files in dovecot-sieve/ and dovecot-managesieve/
       if they're not there since empty files are not included in the diff.gz 
       file.
   + Add SMTP-AUTH support for Outlook (login auth mechanism)
   + Dropped:
     - debian/patches/security-CVE-2009-3235: Applied upstream.
     - debian/patches/fix-pop3-assertion.dpatch: Applied upstream.
     - dovecot-sieve and dovecot-managesieve: Use the debian patches instead.

  [Chuck Short]
  - Updated dovecot-sieve to 0.1.13.
  - Updated dovecot-managesieve to 0.11.9.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (c) 2008-2009 Dovecot authors, see the included COPYING file */
 
2
 
 
3
#include "lib.h"
 
4
#include "array.h"
 
5
#include "bsearch-insert-pos.h"
 
6
#include "istream.h"
 
7
#include "ostream.h"
 
8
#include "file-lock.h"
 
9
#include "file-dotlock.h"
 
10
#include "crc32.h"
 
11
#include "safe-mkstemp.h"
 
12
#include "str.h"
 
13
#include "mail-index-private.h"
 
14
#include "mail-index-strmap.h"
 
15
 
 
16
#include <stdio.h>
 
17
 
 
18
struct mail_index_strmap {
 
19
        struct mail_index *index;
 
20
        char *path;
 
21
        int fd;
 
22
        struct istream *input;
 
23
 
 
24
        struct file_lock *file_lock;
 
25
        struct dotlock *dotlock;
 
26
        struct dotlock_settings dotlock_settings;
 
27
};
 
28
 
 
29
struct mail_index_strmap_view {
 
30
        struct mail_index_strmap *strmap;
 
31
        struct mail_index_view *view;
 
32
 
 
33
        ARRAY_TYPE(mail_index_strmap_rec) recs;
 
34
        ARRAY_DEFINE(recs_crc32, uint32_t);
 
35
        struct hash2_table *hash;
 
36
 
 
37
        mail_index_strmap_key_cmp_t *key_compare;
 
38
        mail_index_strmap_rec_cmp_t *rec_compare;
 
39
        mail_index_strmap_remap_t *remap_cb;
 
40
        void *cb_context;
 
41
 
 
42
        uoff_t last_read_block_offset;
 
43
        uint32_t last_read_uid;
 
44
        uint32_t last_added_uid;
 
45
        uint32_t total_ref_count;
 
46
 
 
47
        uint32_t last_ref_index;
 
48
        uint32_t next_str_idx;
 
49
        uint32_t lost_expunged_uid;
 
50
 
 
51
        unsigned int desynced:1;
 
52
};
 
53
 
 
54
struct mail_index_strmap_read_context {
 
55
        struct mail_index_strmap_view *view;
 
56
 
 
57
        struct istream *input;
 
58
        uoff_t end_offset;
 
59
        uint32_t highest_str_idx;
 
60
        uint32_t uid_lookup_idx;
 
61
        uint32_t lost_expunged_uid;
 
62
 
 
63
        const unsigned char *data, *end, *str_idx_base;
 
64
        struct mail_index_strmap_rec rec;
 
65
        uint32_t next_ref_index;
 
66
        unsigned int rec_size;
 
67
 
 
68
        unsigned int too_large_uids:1;
 
69
};
 
70
 
 
71
struct mail_index_strmap_view_sync {
 
72
        struct mail_index_strmap_view *view;
 
73
};
 
74
 
 
75
struct mail_index_strmap_hash_key {
 
76
        const char *str;
 
77
        uint32_t crc32;
 
78
};
 
79
 
 
80
/* number of bytes required to store one string idx */
 
81
#define STRMAP_FILE_STRIDX_SIZE (sizeof(uint32_t)*2)
 
82
 
 
83
/* renumber the string indexes when highest string idx becomes larger than
 
84
   <number of indexes>*STRMAP_FILE_MAX_STRIDX_MULTIPLIER */
 
85
#define STRMAP_FILE_MAX_STRIDX_MULTIPLIER 2
 
86
 
 
87
#define STRIDX_MUST_RENUMBER(highest_idx, n_unique_indexes) \
 
88
        (highest_idx > n_unique_indexes * STRMAP_FILE_MAX_STRIDX_MULTIPLIER)
 
89
 
 
90
#define MAIL_INDEX_STRMAP_TIMEOUT_SECS 10
 
91
 
 
92
const struct dotlock_settings default_dotlock_settings = {
 
93
        MEMBER(temp_prefix) NULL,
 
94
        MEMBER(lock_suffix) NULL,
 
95
 
 
96
        MEMBER(timeout) MAIL_INDEX_STRMAP_TIMEOUT_SECS,
 
97
        MEMBER(stale_timeout) 30
 
98
};
 
99
 
 
100
struct mail_index_strmap *
 
101
mail_index_strmap_init(struct mail_index *index, const char *suffix)
 
102
{
 
103
        struct mail_index_strmap *strmap;
 
104
 
 
105
        strmap = i_new(struct mail_index_strmap, 1);
 
106
        strmap->index = index;
 
107
        strmap->path = i_strconcat(index->filepath, suffix, NULL);
 
108
        strmap->fd = -1;
 
109
 
 
110
        strmap->dotlock_settings = default_dotlock_settings;
 
111
        strmap->dotlock_settings.use_excl_lock = index->use_excl_dotlocks;
 
112
        strmap->dotlock_settings.nfs_flush = index->nfs_flush;
 
113
        return strmap;
 
114
}
 
115
 
 
116
static bool
 
117
mail_index_strmap_read_rec_next(struct mail_index_strmap_read_context *ctx,
 
118
                                uint32_t *crc32_r);
 
119
 
 
120
static void
 
121
mail_index_strmap_set_syscall_error(struct mail_index_strmap *strmap,
 
122
                                    const char *function)
 
123
{
 
124
        i_assert(function != NULL);
 
125
 
 
126
        if (ENOSPACE(errno)) {
 
127
                strmap->index->nodiskspace = TRUE;
 
128
                return;
 
129
        }
 
130
 
 
131
        mail_index_set_error(strmap->index,
 
132
                             "%s failed with strmap index file %s: %m",
 
133
                             function, strmap->path);
 
134
}
 
135
 
 
136
static void mail_index_strmap_close(struct mail_index_strmap *strmap)
 
137
{
 
138
        if (strmap->file_lock != NULL)
 
139
                file_lock_free(&strmap->file_lock);
 
140
        else if (strmap->dotlock != NULL)
 
141
                file_dotlock_delete(&strmap->dotlock);
 
142
 
 
143
        if (strmap->fd != -1) {
 
144
                if (close(strmap->fd) < 0)
 
145
                        mail_index_strmap_set_syscall_error(strmap, "close()");
 
146
                strmap->fd = -1;
 
147
        }
 
148
        if (strmap->input != NULL)
 
149
                i_stream_unref(&strmap->input);
 
150
}
 
151
 
 
152
void mail_index_strmap_deinit(struct mail_index_strmap **_strmap)
 
153
{
 
154
        struct mail_index_strmap *strmap = *_strmap;
 
155
 
 
156
        *_strmap = NULL;
 
157
        mail_index_strmap_close(strmap);
 
158
        i_free(strmap->path);
 
159
        i_free(strmap);
 
160
}
 
161
 
 
162
static unsigned int mail_index_strmap_hash_key(const void *_key)
 
163
{
 
164
        const struct mail_index_strmap_hash_key *key = _key;
 
165
 
 
166
        return key->crc32;
 
167
}
 
168
 
 
169
static bool
 
170
mail_index_strmap_hash_cmp(const void *_key, const void *_value, void *context)
 
171
{
 
172
        const struct mail_index_strmap_hash_key *key = _key;
 
173
        const struct mail_index_strmap_rec *rec = _value;
 
174
        struct mail_index_strmap_view *view = context;
 
175
 
 
176
        return view->key_compare(key->str, rec, view->cb_context);
 
177
}
 
178
 
 
179
struct mail_index_strmap_view *
 
180
mail_index_strmap_view_open(struct mail_index_strmap *strmap,
 
181
                            struct mail_index_view *idx_view,
 
182
                            mail_index_strmap_key_cmp_t *key_compare_cb,
 
183
                            mail_index_strmap_rec_cmp_t *rec_compare_cb,
 
184
                            mail_index_strmap_remap_t *remap_cb,
 
185
                            void *context,
 
186
                            const ARRAY_TYPE(mail_index_strmap_rec) **recs_r,
 
187
                            const struct hash2_table **hash_r)
 
188
{
 
189
        struct mail_index_strmap_view *view;
 
190
 
 
191
        view = i_new(struct mail_index_strmap_view, 1);
 
192
        view->strmap = strmap;
 
193
        view->view = idx_view;
 
194
        view->key_compare = key_compare_cb;
 
195
        view->rec_compare = rec_compare_cb;
 
196
        view->remap_cb = remap_cb;
 
197
        view->cb_context = context;
 
198
        view->next_str_idx = 1;
 
199
 
 
200
        i_array_init(&view->recs, 64);
 
201
        i_array_init(&view->recs_crc32, 64);
 
202
        view->hash = hash2_create(0, sizeof(struct mail_index_strmap_rec),
 
203
                                  mail_index_strmap_hash_key,
 
204
                                  mail_index_strmap_hash_cmp, view);
 
205
        *recs_r = &view->recs;
 
206
        *hash_r = view->hash;
 
207
        return view;
 
208
}
 
209
 
 
210
void mail_index_strmap_view_close(struct mail_index_strmap_view **_view)
 
211
{
 
212
        struct mail_index_strmap_view *view = *_view;
 
213
 
 
214
        *_view = NULL;
 
215
        array_free(&view->recs);
 
216
        array_free(&view->recs_crc32);
 
217
        hash2_destroy(&view->hash);
 
218
        i_free(view);
 
219
}
 
220
 
 
221
uint32_t mail_index_strmap_view_get_highest_idx(struct mail_index_strmap_view *view)
 
222
{
 
223
        return view->next_str_idx-1;
 
224
}
 
225
 
 
226
static void mail_index_strmap_view_reset(struct mail_index_strmap_view *view)
 
227
{
 
228
        view->remap_cb(NULL, 0, 0, view->cb_context);
 
229
        array_clear(&view->recs);
 
230
        array_clear(&view->recs_crc32);
 
231
        hash2_clear(view->hash);
 
232
 
 
233
        view->last_added_uid = 0;
 
234
        view->lost_expunged_uid = 0;
 
235
        view->desynced = FALSE;
 
236
}
 
237
 
 
238
static void
 
239
mail_index_strmap_view_set_corrupted(struct mail_index_strmap_view *view)
 
240
{
 
241
        mail_index_set_error(view->strmap->index,
 
242
                             "Corrupted strmap index file: %s",
 
243
                             view->strmap->path);
 
244
        (void)unlink(view->strmap->path);
 
245
        mail_index_strmap_close(view->strmap);
 
246
        mail_index_strmap_view_reset(view);
 
247
}
 
248
 
 
249
static int mail_index_strmap_open(struct mail_index_strmap_view *view)
 
250
{
 
251
        struct mail_index_strmap *strmap = view->strmap;
 
252
        const struct mail_index_header *idx_hdr;
 
253
        struct mail_index_strmap_header hdr;
 
254
        const unsigned char *data;
 
255
        size_t size;
 
256
        int ret;
 
257
 
 
258
        i_assert(strmap->fd == -1);
 
259
 
 
260
        strmap->fd = open(strmap->path, O_RDWR);
 
261
        if (strmap->fd == -1) {
 
262
                if (errno == ENOENT)
 
263
                        return 0;
 
264
                mail_index_strmap_set_syscall_error(strmap, "open()");
 
265
                return -1;
 
266
        }
 
267
        strmap->input = i_stream_create_fd(strmap->fd, (size_t)-1, FALSE);
 
268
        ret = i_stream_read_data(strmap->input, &data, &size, sizeof(hdr)-1);
 
269
        if (ret <= 0) {
 
270
                if (ret < 0) {
 
271
                        mail_index_strmap_set_syscall_error(strmap, "read()");
 
272
                        mail_index_strmap_close(strmap);
 
273
                } else {
 
274
                        i_assert(ret == 0);
 
275
                        mail_index_strmap_view_set_corrupted(view);
 
276
                }
 
277
                return ret;
 
278
        }
 
279
        memcpy(&hdr, data, sizeof(hdr));
 
280
 
 
281
        idx_hdr = mail_index_get_header(view->view);
 
282
        if (hdr.version != MAIL_INDEX_STRMAP_VERSION ||
 
283
            hdr.uid_validity != idx_hdr->uid_validity) {
 
284
                /* need to rebuild. if we already had something in the strmap,
 
285
                   we can keep it. */
 
286
                (void)unlink(strmap->path);
 
287
                mail_index_strmap_close(strmap);
 
288
                return 0;
 
289
        }
 
290
 
 
291
        /* we'll read the entire file from the beginning */
 
292
        view->last_added_uid = 0;
 
293
        view->last_read_uid = 0;
 
294
        view->total_ref_count = 0;
 
295
        view->last_read_block_offset = sizeof(struct mail_index_strmap_header);
 
296
        view->next_str_idx = 1;
 
297
 
 
298
        mail_index_strmap_view_reset(view);
 
299
        return 0;
 
300
}
 
301
 
 
302
static bool mail_index_strmap_need_reopen(struct mail_index_strmap *strmap)
 
303
{
 
304
        struct stat st1, st2;
 
305
 
 
306
        /* FIXME: nfs flush */
 
307
        if (fstat(strmap->fd, &st1) < 0) {
 
308
                if (errno != ESTALE)
 
309
                        mail_index_strmap_set_syscall_error(strmap, "fstat()");
 
310
                return TRUE;
 
311
        }
 
312
        if (stat(strmap->path, &st2) < 0) {
 
313
                mail_index_strmap_set_syscall_error(strmap, "stat()");
 
314
                return TRUE;
 
315
        }
 
316
        return st1.st_ino != st2.st_ino || !CMP_DEV_T(st1.st_dev, st2.st_dev);
 
317
}
 
318
 
 
319
static int mail_index_strmap_refresh(struct mail_index_strmap_view *view)
 
320
{
 
321
        uint32_t seq;
 
322
 
 
323
        if (MAIL_INDEX_IS_IN_MEMORY(view->strmap->index))
 
324
                return -1;
 
325
 
 
326
        if (view->strmap->fd != -1) {
 
327
                if (!mail_index_strmap_need_reopen(view->strmap)) {
 
328
                        if (view->lost_expunged_uid != 0) {
 
329
                                /* last read failed because view had a message
 
330
                                   that didn't exist in the strmap (because it
 
331
                                   was expunged by another session). if the
 
332
                                   message still isn't expunged in this view,
 
333
                                   just continue using the current strmap. */
 
334
                                if (mail_index_lookup_seq(view->view,
 
335
                                                view->lost_expunged_uid, &seq))
 
336
                                        return -1;
 
337
                        } else if (view->desynced) {
 
338
                                /* our view isn't synced with the disk, we
 
339
                                   can't read strmap without first resetting
 
340
                                   the view */
 
341
                        } else {
 
342
                                i_stream_sync(view->strmap->input);
 
343
                                return 0;
 
344
                        }
 
345
                }
 
346
                mail_index_strmap_close(view->strmap);
 
347
        }
 
348
 
 
349
        return mail_index_strmap_open(view);
 
350
}
 
351
 
 
352
static int
 
353
mail_index_strmap_read_packed(struct mail_index_strmap_read_context *ctx,
 
354
                              uint32_t *num_r)
 
355
{
 
356
        const unsigned char *data;
 
357
        const uint8_t *bytes, *p, *end;
 
358
        size_t size;
 
359
        int ret;
 
360
 
 
361
        ret = i_stream_read_data(ctx->input, &data, &size, sizeof(*num_r) - 1);
 
362
        if (ret <= 0)
 
363
                return ret;
 
364
 
 
365
        if (ctx->input->v_offset + size > ctx->end_offset)
 
366
                size = ctx->end_offset - ctx->input->v_offset;
 
367
        bytes = p = (const uint8_t *)data;
 
368
        end = bytes + size;
 
369
 
 
370
        if (mail_index_unpack_num(&p, end, num_r) <  0)
 
371
                return -1;
 
372
        i_stream_skip(ctx->input, p - bytes);
 
373
        return 1;
 
374
}
 
375
 
 
376
static int
 
377
mail_index_strmap_uid_exists(struct mail_index_strmap_read_context *ctx,
 
378
                             uint32_t uid)
 
379
{
 
380
        const struct mail_index_record *rec;
 
381
 
 
382
        if (ctx->uid_lookup_idx >= ctx->view->view->map->hdr.messages_count) {
 
383
                if (uid >= ctx->view->view->map->hdr.next_uid) {
 
384
                        /* thread index has larger UIDs than what we've seen
 
385
                           in our view. we'll have to read them again later
 
386
                           when we know about them */
 
387
                        ctx->too_large_uids = TRUE;
 
388
                }
 
389
                return 0;
 
390
        }
 
391
 
 
392
        rec = MAIL_INDEX_MAP_IDX(ctx->view->view->map, ctx->uid_lookup_idx);
 
393
        if (rec->uid == uid) {
 
394
                ctx->uid_lookup_idx++;
 
395
                return 1;
 
396
        } else if (rec->uid > uid) {
 
397
                return 0;
 
398
        } else {
 
399
                /* record that exists in index is missing from strmap.
 
400
                   see if it's because the strmap is corrupted or because
 
401
                   our current view is a bit stale and the message has already
 
402
                   been expunged. */
 
403
                (void)mail_index_refresh(ctx->view->view->index);
 
404
                if (mail_index_is_expunged(ctx->view->view,
 
405
                                           ctx->uid_lookup_idx + 1))
 
406
                        ctx->lost_expunged_uid = rec->uid;
 
407
                return -1;
 
408
        }
 
409
}
 
410
 
 
411
static int
 
412
mail_index_strmap_read_rec_first(struct mail_index_strmap_read_context *ctx,
 
413
                                 uint32_t *crc32_r)
 
414
{
 
415
        size_t size;
 
416
        uint32_t n, i, count, str_idx;
 
417
        int ret;
 
418
 
 
419
        /* <uid> <n> <crc32>*count <str_idx>*count
 
420
           where
 
421
             n = 0 -> count=1 (only Message-ID:)
 
422
             n = 1 -> count=2 (Message-ID: + In-Reply-To:)
 
423
             n = 2+ -> count=n (Message-ID: + References:)
 
424
        */
 
425
        if (mail_index_strmap_read_packed(ctx, &n) <= 0)
 
426
                return -1;
 
427
        count = n < 2 ? n + 1 : n;
 
428
        ctx->view->total_ref_count += count;
 
429
 
 
430
        ctx->rec_size = count * (sizeof(ctx->rec.str_idx) + sizeof(*crc32_r));
 
431
        ret = mail_index_strmap_uid_exists(ctx, ctx->rec.uid);
 
432
        if (ret < 0)
 
433
                return -1;
 
434
        if (i_stream_read_data(ctx->view->strmap->input, &ctx->data, &size,
 
435
                               ctx->rec_size - 1) <= 0)
 
436
                return -1;
 
437
        ctx->str_idx_base = ctx->data + count * sizeof(uint32_t);
 
438
 
 
439
        if (ret == 0) {
 
440
                /* this message has already been expunged, ignore it.
 
441
                   update highest string indexes anyway. */
 
442
                for (i = 0; i < count; i++) {
 
443
                        memcpy(&str_idx, ctx->str_idx_base, sizeof(str_idx));
 
444
                        if (ctx->highest_str_idx < str_idx)
 
445
                                ctx->highest_str_idx = str_idx;
 
446
                        ctx->str_idx_base += sizeof(str_idx);
 
447
                }
 
448
                i_stream_skip(ctx->view->strmap->input, ctx->rec_size);
 
449
                return 0;
 
450
        }
 
451
 
 
452
        /* everything exists. save it. FIXME: these ref_index values
 
453
           are thread index specific, perhaps something more generic
 
454
           should be used some day */
 
455
        ctx->end = ctx->data + count * sizeof(*crc32_r);
 
456
 
 
457
        ctx->next_ref_index = 0;
 
458
        if (!mail_index_strmap_read_rec_next(ctx, crc32_r))
 
459
                i_unreached();
 
460
        ctx->next_ref_index = n == 1 ? 1 : 2;
 
461
        return 1;
 
462
}
 
463
 
 
464
static bool
 
465
mail_index_strmap_read_rec_next(struct mail_index_strmap_read_context *ctx,
 
466
                                uint32_t *crc32_r)
 
467
{
 
468
        if (ctx->data == ctx->end) {
 
469
                i_stream_skip(ctx->view->strmap->input, ctx->rec_size);
 
470
                return FALSE;
 
471
        }
 
472
 
 
473
        /* FIXME: str_idx could be stored as packed relative values
 
474
           (first relative to highest_idx, the rest relative to the
 
475
           previous str_idx) */
 
476
 
 
477
        /* read the record contents */
 
478
        memcpy(&ctx->rec.str_idx, ctx->str_idx_base, sizeof(ctx->rec.str_idx));
 
479
        memcpy(crc32_r, ctx->data, sizeof(*crc32_r));
 
480
 
 
481
        ctx->rec.ref_index = ctx->next_ref_index++;
 
482
 
 
483
        if (ctx->highest_str_idx < ctx->rec.str_idx)
 
484
                ctx->highest_str_idx = ctx->rec.str_idx;
 
485
 
 
486
        /* get to the next record */
 
487
        ctx->data += sizeof(*crc32_r);
 
488
        ctx->str_idx_base += sizeof(ctx->rec.str_idx);
 
489
        return TRUE;
 
490
}
 
491
 
 
492
static int
 
493
strmap_read_block_init(struct mail_index_strmap_view *view,
 
494
                       struct mail_index_strmap_read_context *ctx)
 
495
{
 
496
        struct mail_index_strmap *strmap = view->strmap;
 
497
        const unsigned char *data;
 
498
        size_t size;
 
499
        uint32_t block_size, seq1, seq2;
 
500
        int ret;
 
501
 
 
502
        if (view->last_read_uid + 1 >= view->view->map->hdr.next_uid) {
 
503
                /* come back later when we know about the new UIDs */
 
504
                return 0;
 
505
        }
 
506
 
 
507
        memset(ctx, 0, sizeof(*ctx));
 
508
        ret = i_stream_read_data(strmap->input, &data, &size,
 
509
                                 sizeof(block_size)-1);
 
510
        if (ret <= 0) {
 
511
                if (strmap->input->stream_errno == 0) {
 
512
                        /* no new data */
 
513
                        return 0;
 
514
                }
 
515
                mail_index_strmap_set_syscall_error(strmap, "read()");
 
516
                return -1;
 
517
        }
 
518
        memcpy(&block_size, data, sizeof(block_size));
 
519
        block_size = mail_index_offset_to_uint32(block_size) >> 2;
 
520
        if (block_size == 0) {
 
521
                /* the rest of the file is either not written, or the previous
 
522
                   write didn't finish */
 
523
                return 0;
 
524
        }
 
525
        i_stream_skip(strmap->input, sizeof(block_size));
 
526
 
 
527
        ctx->view = view;
 
528
        ctx->input = strmap->input;
 
529
        ctx->end_offset = strmap->input->v_offset + block_size;
 
530
        if (ctx->end_offset < strmap->input->v_offset) {
 
531
                /* block size too large */
 
532
                mail_index_strmap_view_set_corrupted(view);
 
533
                return -1;
 
534
        }
 
535
        ctx->rec.uid = view->last_read_uid + 1;
 
536
 
 
537
        /* FIXME: when reading multiple blocks we shouldn't have to calculate
 
538
           this every time */
 
539
        if (!mail_index_lookup_seq_range(view->view, ctx->rec.uid, (uint32_t)-1,
 
540
                                         &seq1, &seq2))
 
541
                seq1 = mail_index_view_get_messages_count(view->view) + 1;
 
542
        ctx->uid_lookup_idx = seq1 - 1;
 
543
        return 1;
 
544
}
 
545
 
 
546
static int
 
547
strmap_read_block_next(struct mail_index_strmap_read_context *ctx,
 
548
                       uint32_t *crc32_r)
 
549
{
 
550
        uint32_t uid_diff;
 
551
        int ret;
 
552
 
 
553
        if (mail_index_strmap_read_rec_next(ctx, crc32_r))
 
554
                return 1;
 
555
 
 
556
        /* get next UID */
 
557
        do {
 
558
                if (ctx->input->v_offset == ctx->end_offset) {
 
559
                        /* this block is done */
 
560
                        return 0;
 
561
                }
 
562
                if (mail_index_strmap_read_packed(ctx, &uid_diff) < 0)
 
563
                        return -1;
 
564
 
 
565
                ctx->rec.uid += uid_diff;
 
566
                ret = mail_index_strmap_read_rec_first(ctx, crc32_r);
 
567
        } while (ret == 0);
 
568
        return ret;
 
569
}
 
570
 
 
571
static int
 
572
strmap_read_block_deinit(struct mail_index_strmap_read_context *ctx, int ret,
 
573
                         bool update_block_offset)
 
574
{
 
575
        struct mail_index_strmap_view *view = ctx->view;
 
576
        struct mail_index_strmap *strmap = view->strmap;
 
577
 
 
578
        if (ctx->highest_str_idx > view->total_ref_count) {
 
579
                /* if all string indexes are unique, highest_str_index equals
 
580
                   total_ref_count. otherwise it's always lower. */
 
581
                mail_index_set_error(strmap->index,
 
582
                                     "Corrupted strmap index file %s: "
 
583
                                     "String indexes too high "
 
584
                                     "(highest=%u max=%u)",
 
585
                                     strmap->path, ctx->highest_str_idx,
 
586
                                     view->total_ref_count);
 
587
                mail_index_strmap_view_set_corrupted(view);
 
588
                return -1;
 
589
        }
 
590
        if (ctx->lost_expunged_uid != 0) {
 
591
                /* our view contained a message that had since been expunged. */
 
592
                i_assert(ret < 0);
 
593
                view->lost_expunged_uid = ctx->lost_expunged_uid;
 
594
        } else if (ret < 0) {
 
595
                if (strmap->input->stream_errno != 0)
 
596
                        mail_index_strmap_set_syscall_error(strmap, "read()");
 
597
                else
 
598
                        mail_index_strmap_view_set_corrupted(view);
 
599
                return -1;
 
600
        } else if (update_block_offset && !ctx->too_large_uids) {
 
601
                view->last_read_block_offset = strmap->input->v_offset;
 
602
                view->last_read_uid = ctx->rec.uid;
 
603
        }
 
604
        if (view->next_str_idx <= ctx->highest_str_idx)
 
605
                view->next_str_idx = ctx->highest_str_idx + 1;
 
606
        return ret;
 
607
}
 
608
 
 
609
static bool
 
610
strmap_view_sync_handle_conflict(struct mail_index_strmap_read_context *ctx,
 
611
                                 const struct mail_index_strmap_rec *hash_rec,
 
612
                                 struct hash2_iter *iter)
 
613
{
 
614
        uint32_t seq;
 
615
 
 
616
        /* hopefully it's a message that has since been expunged */
 
617
        if (!mail_index_lookup_seq(ctx->view->view, hash_rec->uid, &seq)) {
 
618
                /* message is no longer in our view. remove it completely. */
 
619
                hash2_remove_iter(ctx->view->hash, iter);
 
620
                return TRUE;
 
621
        }
 
622
        if (mail_index_is_expunged(ctx->view->view, seq)) {
 
623
                /* it's quite likely a conflict. we may not be able to verify
 
624
                   it, so just assume it is. nothing breaks even if we guess
 
625
                   wrong, the performance just suffers a bit. */
 
626
                return FALSE;
 
627
        }
 
628
 
 
629
        /* 0 means "doesn't match", which is the only acceptable case */
 
630
        return ctx->view->rec_compare(&ctx->rec, hash_rec,
 
631
                                      ctx->view->cb_context) == 0;
 
632
}
 
633
 
 
634
static int
 
635
strmap_view_sync_block_check_conflicts(struct mail_index_strmap_read_context *ctx,
 
636
                                       uint32_t crc32)
 
637
{
 
638
        struct mail_index_strmap_rec *hash_rec;
 
639
        struct hash2_iter iter;
 
640
 
 
641
        if (crc32 == 0) {
 
642
                /* unique string - there are no conflicts */
 
643
                return 0;
 
644
        }
 
645
 
 
646
        /* check for conflicting string indexes. they may happen if
 
647
 
 
648
        1) msgid exists only for a message X that has been expunged
 
649
        2) another process doesn't see X, but sees msgid for another
 
650
           message and writes it using a new string index
 
651
        3) if we still see X, we now see the same msgid with two
 
652
           string indexes.
 
653
 
 
654
        if we detect such a conflict, we can't continue using the
 
655
        strmap index until X has been expunged. */
 
656
        memset(&iter, 0, sizeof(iter));
 
657
        while ((hash_rec = hash2_iterate(ctx->view->hash,
 
658
                                         crc32, &iter)) != NULL &&
 
659
               hash_rec->str_idx != ctx->rec.str_idx) {
 
660
                /* CRC32 matches, but string index doesn't */
 
661
                if (!strmap_view_sync_handle_conflict(ctx, hash_rec, &iter)) {
 
662
                        ctx->lost_expunged_uid = hash_rec->uid;
 
663
                        return -1;
 
664
                }
 
665
        }
 
666
        return 0;
 
667
}
 
668
 
 
669
static int
 
670
mail_index_strmap_view_sync_block(struct mail_index_strmap_read_context *ctx)
 
671
{
 
672
        struct mail_index_strmap_rec *hash_rec;
 
673
        uint32_t crc32, prev_uid = 0;
 
674
        int ret;
 
675
 
 
676
        while ((ret = strmap_read_block_next(ctx, &crc32)) > 0) {
 
677
                if (ctx->rec.uid <= ctx->view->last_added_uid) {
 
678
                        if (ctx->rec.uid < ctx->view->last_added_uid ||
 
679
                            prev_uid != ctx->rec.uid) {
 
680
                                /* we've already added this */
 
681
                                continue;
 
682
                        }
 
683
                }
 
684
                prev_uid = ctx->rec.uid;
 
685
 
 
686
                if (strmap_view_sync_block_check_conflicts(ctx, crc32) < 0) {
 
687
                        ret = -1;
 
688
                        break;
 
689
                }
 
690
                ctx->view->last_added_uid = ctx->rec.uid;
 
691
 
 
692
                /* add the record to records array */
 
693
                array_append(&ctx->view->recs, &ctx->rec, 1);
 
694
                array_append(&ctx->view->recs_crc32, &crc32, 1);
 
695
 
 
696
                /* add a separate copy of the record to hash */
 
697
                hash_rec = hash2_insert_hash(ctx->view->hash, crc32);
 
698
                memcpy(hash_rec, &ctx->rec, sizeof(*hash_rec));
 
699
        }
 
700
        return strmap_read_block_deinit(ctx, ret, TRUE);
 
701
}
 
702
 
 
703
struct mail_index_strmap_view_sync *
 
704
mail_index_strmap_view_sync_init(struct mail_index_strmap_view *view,
 
705
                                 uint32_t *last_uid_r)
 
706
{
 
707
        struct mail_index_strmap_view_sync *sync;
 
708
        struct mail_index_strmap_read_context ctx;
 
709
        int ret;
 
710
 
 
711
        sync = i_new(struct mail_index_strmap_view_sync, 1);
 
712
        sync->view = view;
 
713
 
 
714
        if (mail_index_strmap_refresh(view) < 0) {
 
715
                /* reading the strmap failed - just ignore and do
 
716
                   this in-memory based on whatever we knew last */
 
717
        } else if (view->strmap->input != NULL) {
 
718
                i_stream_seek(view->strmap->input,
 
719
                              view->last_read_block_offset);
 
720
                while ((ret = strmap_read_block_init(view, &ctx)) > 0) {
 
721
                        if (mail_index_strmap_view_sync_block(&ctx) < 0) {
 
722
                                ret = -1;
 
723
                                break;
 
724
                        }
 
725
                        if (ctx.too_large_uids)
 
726
                                break;
 
727
                }
 
728
 
 
729
                if (ret < 0) {
 
730
                        /* something failed - we can still use the strmap as far
 
731
                           as we managed to read it, but our view is now out
 
732
                           of sync */
 
733
                        view->desynced = TRUE;
 
734
                } else {
 
735
                        i_assert(view->lost_expunged_uid == 0);
 
736
                }
 
737
        }
 
738
        *last_uid_r = view->last_added_uid;
 
739
        return sync;
 
740
}
 
741
 
 
742
static inline uint32_t crc32_str_nonzero(const char *str)
 
743
{
 
744
        uint32_t value = crc32_str(str);
 
745
        return value == 0 ? 1 : value;
 
746
}
 
747
 
 
748
void mail_index_strmap_view_sync_add(struct mail_index_strmap_view_sync *sync,
 
749
                                     uint32_t uid, uint32_t ref_index,
 
750
                                     const char *key)
 
751
{
 
752
        struct mail_index_strmap_view *view = sync->view;
 
753
        struct mail_index_strmap_rec *rec, *old_rec;
 
754
        struct mail_index_strmap_hash_key hash_key;
 
755
        uint32_t str_idx;
 
756
 
 
757
        i_assert(uid > view->last_added_uid ||
 
758
                 (uid == view->last_added_uid &&
 
759
                  ref_index > view->last_ref_index));
 
760
 
 
761
        hash_key.str = key;
 
762
        hash_key.crc32 = crc32_str_nonzero(key);
 
763
 
 
764
        old_rec = hash2_lookup(view->hash, &hash_key);
 
765
        if (old_rec != NULL) {
 
766
                /* The string already exists, use the same unique idx */
 
767
                str_idx = old_rec->str_idx;
 
768
        } else {
 
769
                /* Newly seen string, assign a new unique idx to it */
 
770
                str_idx = view->next_str_idx++;
 
771
        }
 
772
        i_assert(str_idx != 0);
 
773
 
 
774
        rec = hash2_insert(view->hash, &hash_key);
 
775
        rec->uid = uid;
 
776
        rec->ref_index = ref_index;
 
777
        rec->str_idx = str_idx;
 
778
        array_append(&view->recs, rec, 1);
 
779
        array_append(&view->recs_crc32, &hash_key.crc32, 1);
 
780
 
 
781
        view->last_added_uid = uid;
 
782
        view->last_ref_index = ref_index;
 
783
}
 
784
 
 
785
void mail_index_strmap_view_sync_add_unique(struct mail_index_strmap_view_sync *sync,
 
786
                                            uint32_t uid, uint32_t ref_index)
 
787
{
 
788
        struct mail_index_strmap_view *view = sync->view;
 
789
        struct mail_index_strmap_rec rec;
 
790
 
 
791
        i_assert(uid > view->last_added_uid ||
 
792
                 (uid == view->last_added_uid &&
 
793
                  ref_index > view->last_ref_index));
 
794
 
 
795
        memset(&rec, 0, sizeof(rec));
 
796
        rec.uid = uid;
 
797
        rec.ref_index = ref_index;
 
798
        rec.str_idx = view->next_str_idx++;
 
799
        array_append(&view->recs, &rec, 1);
 
800
        (void)array_append_space(&view->recs_crc32);
 
801
 
 
802
        view->last_added_uid = uid;
 
803
        view->last_ref_index = ref_index;
 
804
}
 
805
 
 
806
static void
 
807
mail_index_strmap_zero_terminate(struct mail_index_strmap_view *view)
 
808
{
 
809
        /* zero-terminate the records array */
 
810
        (void)array_append_space(&view->recs);
 
811
        array_delete(&view->recs, array_count(&view->recs)-1, 1);
 
812
}
 
813
 
 
814
static void mail_index_strmap_view_renumber(struct mail_index_strmap_view *view)
 
815
{
 
816
        struct mail_index_strmap_read_context ctx;
 
817
        struct mail_index_strmap_rec *recs, *hash_rec;
 
818
        uint32_t prev_uid, str_idx, *recs_crc32, *renumber_map;
 
819
        unsigned int i, dest, count, count2;
 
820
        int ret;
 
821
 
 
822
        memset(&ctx, 0, sizeof(ctx));
 
823
        ctx.view = view;
 
824
 
 
825
        /* create a map of old -> new index and remove records of
 
826
           expunged messages */
 
827
        renumber_map = i_new(uint32_t, view->next_str_idx);
 
828
        str_idx = 0; prev_uid = 0;
 
829
        recs = array_get_modifiable(&view->recs, &count);
 
830
        recs_crc32 = array_get_modifiable(&view->recs_crc32, &count2);
 
831
        i_assert(count == count2);
 
832
 
 
833
        for (i = dest = 0; i < count; ) {
 
834
                if (prev_uid != recs[i].uid) {
 
835
                        /* see if this record should be removed */
 
836
                        prev_uid = recs[i].uid;
 
837
                        ret = mail_index_strmap_uid_exists(&ctx, prev_uid);
 
838
                        i_assert(ret >= 0);
 
839
                        if (ret == 0) {
 
840
                                /* message expunged */
 
841
                                do {
 
842
                                        i++;
 
843
                                } while (i < count && recs[i].uid == prev_uid);
 
844
                                continue;
 
845
                        }
 
846
                }
 
847
 
 
848
                i_assert(recs[i].str_idx < view->next_str_idx);
 
849
                if (renumber_map[recs[i].str_idx] == 0)
 
850
                        renumber_map[recs[i].str_idx] = ++str_idx;
 
851
                if (i != dest) {
 
852
                        recs[dest] = recs[i];
 
853
                        recs_crc32[dest] = recs_crc32[i];
 
854
                }
 
855
                i++; dest++;
 
856
        }
 
857
        i_assert(renumber_map[0] == 0);
 
858
        array_delete(&view->recs, dest, i-dest);
 
859
        array_delete(&view->recs_crc32, dest, i-dest);
 
860
        mail_index_strmap_zero_terminate(view);
 
861
 
 
862
        /* notify caller of the renumbering */
 
863
        i_assert(str_idx <= view->next_str_idx);
 
864
        view->remap_cb(renumber_map, view->next_str_idx, str_idx + 1,
 
865
                       view->cb_context);
 
866
 
 
867
        /* renumber the indexes in-place and recreate the hash */
 
868
        recs = array_get_modifiable(&view->recs, &count);
 
869
        hash2_clear(view->hash);
 
870
        for (i = 0; i < count; i++) {
 
871
                recs[i].str_idx = renumber_map[recs[i].str_idx];
 
872
                hash_rec = hash2_insert_hash(view->hash, recs_crc32[i]);
 
873
                memcpy(hash_rec, &recs[i], sizeof(*hash_rec));
 
874
        }
 
875
 
 
876
        /* update the new next_str_idx only after remapping */
 
877
        view->next_str_idx = str_idx + 1;
 
878
        i_free(renumber_map);
 
879
}
 
880
 
 
881
static int mail_index_strmap_write_block(struct mail_index_strmap_view *view,
 
882
                                         struct ostream *output,
 
883
                                         unsigned int i, uint32_t base_uid)
 
884
{
 
885
        const struct mail_index_strmap_rec *recs;
 
886
        const uint32_t *crc32;
 
887
        unsigned int j, n, count, count2, uid_rec_count;
 
888
        uint32_t block_size;
 
889
        uint8_t *p, packed[MAIL_INDEX_PACK_MAX_SIZE*2];
 
890
        uoff_t block_offset, end_offset;
 
891
 
 
892
        /* skip over the block size for now, we don't know it yet */
 
893
        block_offset = output->offset;
 
894
        block_size = 0;
 
895
        o_stream_send(output, &block_size, sizeof(block_size));
 
896
 
 
897
        /* write records */
 
898
        recs = array_get(&view->recs, &count);
 
899
        crc32 = array_get(&view->recs_crc32, &count2);
 
900
        i_assert(count == count2);
 
901
        while (i < count) {
 
902
                /* @UNSAFE: <uid diff> */
 
903
                p = packed;
 
904
                mail_index_pack_num(&p, recs[i].uid - base_uid);
 
905
                base_uid = recs[i].uid;
 
906
 
 
907
                /* find how many records belong to this UID */
 
908
                uid_rec_count = 1;
 
909
                for (j = i + 1; j < count; j++) {
 
910
                        if (recs[j].uid != base_uid)
 
911
                                break;
 
912
                        uid_rec_count++;
 
913
                }
 
914
                view->total_ref_count += uid_rec_count;
 
915
 
 
916
                /* <n> <crc32>*count <str_idx>*count -
 
917
                   FIXME: thread index specific code */
 
918
                i_assert(recs[i].ref_index == 0);
 
919
                if (uid_rec_count == 1) {
 
920
                        /* Only Message-ID: header */
 
921
                        n = 0;
 
922
                } else if (recs[i+1].ref_index == 1) {
 
923
                        /* In-Reply-To: header */
 
924
                        n = 1;
 
925
                        i_assert(uid_rec_count == 2);
 
926
                } else {
 
927
                        /* References: header */
 
928
                        n = uid_rec_count;
 
929
                        i_assert(recs[i+1].ref_index == 2);
 
930
                }
 
931
 
 
932
                mail_index_pack_num(&p, n);
 
933
                o_stream_send(output, packed, p-packed);
 
934
                for (j = 0; j < uid_rec_count; j++)
 
935
                        o_stream_send(output, &crc32[i+j], sizeof(crc32[i+j]));
 
936
                for (j = 0; j < uid_rec_count; j++) {
 
937
                        i_assert(j < 2 || recs[i+j].ref_index == j+1);
 
938
                        o_stream_send(output, &recs[i+j].str_idx,
 
939
                                      sizeof(recs[i+j].str_idx));
 
940
                }
 
941
                i += uid_rec_count;
 
942
        }
 
943
 
 
944
        /* we know the block size now - write it */
 
945
        block_size = output->offset - (block_offset + sizeof(block_size));
 
946
        block_size = mail_index_uint32_to_offset(block_size << 2);
 
947
        i_assert(block_size != 0);
 
948
 
 
949
        end_offset = output->offset;
 
950
        o_stream_seek(output, block_offset);
 
951
        o_stream_send(output, &block_size, sizeof(block_size));
 
952
        o_stream_seek(output, end_offset);
 
953
 
 
954
        if (output->last_failed_errno != 0)
 
955
                return -1;
 
956
        else {
 
957
                i_assert(view->last_added_uid == recs[count-1].uid);
 
958
                view->last_read_uid = recs[count-1].uid;
 
959
                view->last_read_block_offset = output->offset;
 
960
                return 0;
 
961
        }
 
962
}
 
963
 
 
964
static void
 
965
mail_index_strmap_recreate_write(struct mail_index_strmap_view *view,
 
966
                                 struct ostream *output)
 
967
{
 
968
        const struct mail_index_header *idx_hdr;
 
969
        struct mail_index_strmap_header hdr;
 
970
 
 
971
        idx_hdr = mail_index_get_header(view->view);
 
972
 
 
973
        /* write header */
 
974
        memset(&hdr, 0, sizeof(hdr));
 
975
        hdr.version = MAIL_INDEX_STRMAP_VERSION;
 
976
        hdr.uid_validity = idx_hdr->uid_validity;
 
977
        o_stream_send(output, &hdr, sizeof(hdr));
 
978
 
 
979
        view->total_ref_count = 0;
 
980
        (void)mail_index_strmap_write_block(view, output, 0, 1);
 
981
}
 
982
 
 
983
static int mail_index_strmap_recreate(struct mail_index_strmap_view *view)
 
984
{
 
985
        struct mail_index_strmap *strmap = view->strmap;
 
986
        string_t *str;
 
987
        struct ostream *output;
 
988
        const char *temp_path;
 
989
        int fd, ret = 0;
 
990
 
 
991
        if (array_count(&view->recs) == 0) {
 
992
                /* everything expunged - just unlink the existing index */
 
993
                if (unlink(strmap->path) < 0 && errno != ENOENT)
 
994
                        mail_index_strmap_set_syscall_error(strmap, "unlink()");
 
995
                return 0;
 
996
        }
 
997
 
 
998
        str = t_str_new(256);
 
999
        str_append(str, strmap->path);
 
1000
        fd = safe_mkstemp_hostpid_group(str, view->view->index->mode,
 
1001
                                        view->view->index->gid,
 
1002
                                        view->view->index->gid_origin);
 
1003
        temp_path = str_c(str);
 
1004
 
 
1005
        if (fd == -1) {
 
1006
                mail_index_set_error(strmap->index,
 
1007
                                     "safe_mkstemp_hostpid(%s) failed: %m",
 
1008
                                     temp_path);
 
1009
                return -1;
 
1010
        }
 
1011
        output = o_stream_create_fd(fd, 0, FALSE);
 
1012
        o_stream_cork(output);
 
1013
        mail_index_strmap_recreate_write(view, output);
 
1014
        if (output->last_failed_errno != 0) {
 
1015
                errno = output->last_failed_errno;
 
1016
                mail_index_set_error(strmap->index,
 
1017
                                     "write(%s) failed: %m", temp_path);
 
1018
                ret = -1;
 
1019
        }
 
1020
        o_stream_destroy(&output);
 
1021
        if (close(fd) < 0) {
 
1022
                mail_index_set_error(strmap->index,
 
1023
                                     "close(%s) failed: %m", temp_path);
 
1024
                ret = -1;
 
1025
        } else if (ret == 0 && rename(temp_path, strmap->path) < 0) {
 
1026
                mail_index_set_error(strmap->index,
 
1027
                                     "rename(%s, %s) failed: %m",
 
1028
                                     temp_path, strmap->path);
 
1029
                ret = -1;
 
1030
        }
 
1031
        if (ret < 0)
 
1032
                (void)unlink(temp_path);
 
1033
        return ret;
 
1034
}
 
1035
 
 
1036
static int mail_index_strmap_lock(struct mail_index_strmap *strmap)
 
1037
{
 
1038
        int ret;
 
1039
 
 
1040
        i_assert(strmap->fd != -1);
 
1041
 
 
1042
        if (strmap->index->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
 
1043
                i_assert(strmap->file_lock == NULL);
 
1044
 
 
1045
                ret = file_wait_lock(strmap->fd, strmap->path, F_WRLCK,
 
1046
                                     strmap->index->lock_method,
 
1047
                                     MAIL_INDEX_STRMAP_TIMEOUT_SECS,
 
1048
                                     &strmap->file_lock);
 
1049
                if (ret <= 0) {
 
1050
                        mail_index_strmap_set_syscall_error(strmap,
 
1051
                                                            "file_wait_lock()");
 
1052
                }
 
1053
        } else {
 
1054
                i_assert(strmap->dotlock == NULL);
 
1055
 
 
1056
                ret = file_dotlock_create(&strmap->dotlock_settings,
 
1057
                                          strmap->path, 0, &strmap->dotlock);
 
1058
                if (ret <= 0) {
 
1059
                        mail_index_strmap_set_syscall_error(strmap,
 
1060
                                "file_dotlock_create()");
 
1061
                }
 
1062
        }
 
1063
        return ret;
 
1064
}
 
1065
 
 
1066
static void mail_index_strmap_unlock(struct mail_index_strmap *strmap)
 
1067
{
 
1068
        if (strmap->file_lock != NULL)
 
1069
                file_unlock(&strmap->file_lock);
 
1070
        else if (strmap->dotlock != NULL)
 
1071
                file_dotlock_delete(&strmap->dotlock);
 
1072
}
 
1073
 
 
1074
static int strmap_rec_cmp(const void *key, const void *value)
 
1075
{
 
1076
        const uint32_t *uid = key;
 
1077
        const struct mail_index_strmap_rec *rec = value;
 
1078
 
 
1079
        return *uid < rec->uid ? -1 :
 
1080
                (*uid > rec->uid ? 1 : 0);
 
1081
}
 
1082
 
 
1083
static int
 
1084
mail_index_strmap_write_append(struct mail_index_strmap_view *view)
 
1085
{
 
1086
        struct mail_index_strmap_read_context ctx;
 
1087
        const struct mail_index_strmap_rec *old_recs;
 
1088
        unsigned int i, old_count;
 
1089
        struct ostream *output;
 
1090
        uint32_t crc32, next_uid;
 
1091
        bool full_block;
 
1092
        int ret;
 
1093
 
 
1094
        /* Check first if another process had written new records to the file.
 
1095
           If there are any, hopefully they're the same as what we would be
 
1096
           writing. There are two problematic cases when messages have been
 
1097
           expunged recently:
 
1098
 
 
1099
           1) The file contains UIDs that we don't have. This means the string
 
1100
           indexes won't be compatible anymore, so we'll have to renumber ours
 
1101
           to match the ones in the strmap file.
 
1102
 
 
1103
           Currently we don't bother handling 1) case. If indexes don't match
 
1104
           what we have, we just don't write anything.
 
1105
 
 
1106
           2) We have UIDs that don't exist in the file. We can't simply skip
 
1107
           those records, because other records may have pointers to them using
 
1108
           different string indexes than we have. Even if we renumbered those,
 
1109
           future appends by other processes might cause the same problem (they
 
1110
           see the string for the first time and assign it a new index, but we
 
1111
           already have internally given it another index). So the only
 
1112
           sensible choice is to write nothing and hope that the message goes
 
1113
           away soon. */
 
1114
        old_recs = array_get(&view->recs, &old_count);
 
1115
        next_uid = view->last_read_uid + 1;
 
1116
        (void)bsearch_insert_pos(&next_uid, old_recs, old_count,
 
1117
                                 sizeof(*old_recs), strmap_rec_cmp, &i);
 
1118
        if (i < old_count) {
 
1119
                while (i > 0 && old_recs[i-1].uid == old_recs[i].uid)
 
1120
                        i--;
 
1121
        }
 
1122
 
 
1123
        i_stream_sync(view->strmap->input);
 
1124
        i_stream_seek(view->strmap->input, view->last_read_block_offset);
 
1125
        full_block = TRUE; ret = 0;
 
1126
        while (i < old_count &&
 
1127
               (ret = strmap_read_block_init(view, &ctx)) > 0) {
 
1128
                while ((ret = strmap_read_block_next(&ctx, &crc32)) > 0) {
 
1129
                        if (ctx.rec.uid != old_recs[i].uid ||
 
1130
                            ctx.rec.str_idx != old_recs[i].str_idx) {
 
1131
                                /* mismatch */
 
1132
                                if (ctx.rec.uid > old_recs[i].uid) {
 
1133
                                        /* 1) case */
 
1134
                                        ctx.lost_expunged_uid = ctx.rec.uid;
 
1135
                                } else if (ctx.rec.uid < old_recs[i].uid) {
 
1136
                                        /* 2) case */
 
1137
                                        ctx.lost_expunged_uid = old_recs[i].uid;
 
1138
                                } else {
 
1139
                                        /* string index mismatch,
 
1140
                                           shouldn't happen */
 
1141
                                }
 
1142
                                ret = -1;
 
1143
                                break;
 
1144
                        }
 
1145
                        if (++i == old_count) {
 
1146
                                full_block = FALSE;
 
1147
                                break;
 
1148
                        }
 
1149
                }
 
1150
                if (strmap_read_block_deinit(&ctx, ret, full_block) < 0) {
 
1151
                        ret = -1;
 
1152
                        break;
 
1153
                }
 
1154
        }
 
1155
        if (ret < 0)
 
1156
                return -1;
 
1157
        if (i == old_count) {
 
1158
                /* nothing new to write */
 
1159
                return 0;
 
1160
        }
 
1161
        i_assert(full_block);
 
1162
        i_assert(old_recs[i].uid > view->last_read_uid);
 
1163
 
 
1164
        /* write the new records */
 
1165
        output = o_stream_create_fd(view->strmap->fd, 0, FALSE);
 
1166
        o_stream_seek(output, view->last_read_block_offset);
 
1167
        o_stream_cork(output);
 
1168
        if (mail_index_strmap_write_block(view, output, i,
 
1169
                                          view->last_read_uid + 1) < 0) {
 
1170
                errno = output->last_failed_errno;
 
1171
                mail_index_strmap_set_syscall_error(view->strmap, "write()");
 
1172
                ret = -1;
 
1173
        }
 
1174
        o_stream_destroy(&output);
 
1175
        return ret;
 
1176
}
 
1177
 
 
1178
static int mail_index_strmap_write(struct mail_index_strmap_view *view)
 
1179
{
 
1180
        int ret;
 
1181
 
 
1182
        /* FIXME: this renumbering doesn't work well when running for a long
 
1183
           time since records aren't removed from hash often enough */
 
1184
        if (STRIDX_MUST_RENUMBER(view->next_str_idx - 1,
 
1185
                                 hash2_count(view->hash))) {
 
1186
                mail_index_strmap_view_renumber(view);
 
1187
                if (!MAIL_INDEX_IS_IN_MEMORY(view->strmap->index)) {
 
1188
                        if (mail_index_strmap_recreate(view) < 0) {
 
1189
                                view->desynced = TRUE;
 
1190
                                return -1;
 
1191
                        }
 
1192
                }
 
1193
                return 0;
 
1194
        }
 
1195
 
 
1196
        if (MAIL_INDEX_IS_IN_MEMORY(view->strmap->index) || view->desynced)
 
1197
                return 0;
 
1198
 
 
1199
        if (view->strmap->fd == -1) {
 
1200
                /* initial file creation */
 
1201
                if (mail_index_strmap_recreate(view) < 0) {
 
1202
                        view->desynced = TRUE;
 
1203
                        return -1;
 
1204
                }
 
1205
                return 0;
 
1206
        }
 
1207
 
 
1208
        /* append the new records to the strmap file */
 
1209
        if (mail_index_strmap_lock(view->strmap) <= 0) {
 
1210
                /* timeout / error */
 
1211
                ret = -1;
 
1212
        } else if (mail_index_strmap_need_reopen(view->strmap)) {
 
1213
                /* the file was already recreated - leave the syncing as it is
 
1214
                   for now and let the next sync re-read the file. */
 
1215
                ret = 0;
 
1216
        } else {
 
1217
                ret = mail_index_strmap_write_append(view);
 
1218
        }
 
1219
        mail_index_strmap_unlock(view->strmap);
 
1220
        if (ret < 0)
 
1221
                view->desynced = TRUE;
 
1222
        return ret;
 
1223
}
 
1224
 
 
1225
void mail_index_strmap_view_sync_commit(struct mail_index_strmap_view_sync **_sync)
 
1226
{
 
1227
        struct mail_index_strmap_view_sync *sync = *_sync;
 
1228
        struct mail_index_strmap_view *view = sync->view;
 
1229
 
 
1230
        *_sync = NULL;
 
1231
        i_free(sync);
 
1232
 
 
1233
        (void)mail_index_strmap_write(view);
 
1234
        mail_index_strmap_zero_terminate(view);
 
1235
 
 
1236
        /* zero-terminate the records array */
 
1237
        (void)array_append_space(&view->recs);
 
1238
        array_delete(&view->recs, array_count(&view->recs)-1, 1);
 
1239
}
 
1240
 
 
1241
void mail_index_strmap_view_sync_rollback(struct mail_index_strmap_view_sync **_sync)
 
1242
{
 
1243
        struct mail_index_strmap_view_sync *sync = *_sync;
 
1244
 
 
1245
        *_sync = NULL;
 
1246
 
 
1247
        mail_index_strmap_view_reset(sync->view);
 
1248
        mail_index_strmap_zero_terminate(sync->view);
 
1249
        i_free(sync);
 
1250
}