~ubuntu-branches/ubuntu/trusty/dovecot/trusty

« back to all changes in this revision

Viewing changes to src/plugins/fts/fts-expunge-log.c

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2012-06-11 11:11:54 UTC
  • mfrom: (1.15.2) (4.1.27 sid)
  • Revision ID: package-import@ubuntu.com-20120611111154-678cwbdj6ktgsv1h
Tags: 1:2.1.7-1ubuntu1
* Merge from Debian unstable, remaining changes:
  + Add mail-stack-delivery package:
    - Update d/rules
    - d/control: convert existing dovecot-postfix package to a dummy
      package and add new mail-stack-delivery package.
    - Update maintainer scripts.
    - Rename d/dovecot-postfix.* to debian/mail-stack-delivery.*
    - d/mail-stack-delivery.preinst: Move previously installed backups and
      config files to a new package namespace.
    - d/mail-stack-delivery.prerm: Added to handle downgrades.
  + Use Snakeoil SSL certificates by default:
    - d/control: Depend on ssl-cert.
    - d/dovecot-core.postinst: Relax grep for SSL_* a bit.
  + Add autopkgtest to debian/tests/*.
  + Add ufw integration:
    - d/dovecot-core.ufw.profile: new ufw profile.
    - d/rules: install profile in dovecot-core.
    - d/control: dovecot-core - suggest ufw.
  + d/{control,rules}: enable PIE hardening.
  + d/dovecot-core.dirs: Added usr/share/doc/dovecot-core
  + Add apport hook:
    - d/rules, d/source_dovecot.py
  + Add upstart job:
    - d/rules, d/dovecot-core.dovecot.upstart, d/control,
      d/dovecot-core.dirs, dovecot-imapd.{postrm, postinst, prerm},
      d/dovecot-pop3d.{postinst, postrm, prerm}.
      d/mail-stack-deliver.postinst: Convert init script to upstart.
  + d/control: Added Pre-Depends: dpkg (>= 1.15.6) to dovecot-dbg to support
    xz compression in Ubuntu.
  + d/control: Demote dovecot-common Recommends: to Suggests: to prevent
    install of extra packages on upgrade.
  + d/patches/dovecot-drac.patch: Updated with version for dovecot >= 2.0.0.
  + d/control: Drop B-D on systemd.
* Dropped changes:
  + d/patches/fix-racey-restart.patch: part of 2.1.x, no longer required.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (c) 2011-2012 Dovecot authors, see the included COPYING file */
 
2
 
 
3
#include "lib.h"
 
4
#include "array.h"
 
5
#include "crc32.h"
 
6
#include "hash.h"
 
7
#include "istream.h"
 
8
#include "write-full.h"
 
9
#include "seq-range-array.h"
 
10
#include "mail-storage.h"
 
11
#include "fts-expunge-log.h"
 
12
 
 
13
#include <sys/stat.h>
 
14
#include <unistd.h>
 
15
#include <fcntl.h>
 
16
 
 
17
struct fts_expunge_log_record {
 
18
        /* CRC32 of this entire record (except this checksum) */
 
19
        uint32_t checksum;
 
20
        /* Size of this entire record */
 
21
        uint32_t record_size;
 
22
 
 
23
        /* Mailbox GUID */
 
24
        guid_128_t guid;
 
25
        /* { uid1, uid2 } pairs */
 
26
        /* uint32_t expunge_uid_ranges[]; */
 
27
 
 
28
        /* Total number of messages expunged so far in this log */
 
29
        /* uint32_t expunge_count; */
 
30
};
 
31
 
 
32
struct fts_expunge_log {
 
33
        char *path;
 
34
 
 
35
        int fd;
 
36
        struct stat st;
 
37
};
 
38
 
 
39
struct fts_expunge_log_mailbox {
 
40
        guid_128_t guid;
 
41
        ARRAY_TYPE(seq_range) uids;
 
42
        unsigned uids_count;
 
43
};
 
44
 
 
45
struct fts_expunge_log_append_ctx {
 
46
        struct fts_expunge_log *log;
 
47
        pool_t pool;
 
48
 
 
49
        struct hash_table *mailboxes;
 
50
        struct fts_expunge_log_mailbox *prev_mailbox;
 
51
 
 
52
        bool failed;
 
53
};
 
54
 
 
55
struct fts_expunge_log_read_ctx {
 
56
        struct fts_expunge_log *log;
 
57
 
 
58
        struct istream *input;
 
59
        buffer_t buffer;
 
60
        struct fts_expunge_log_read_record read_rec;
 
61
 
 
62
        bool failed;
 
63
        bool corrupted;
 
64
};
 
65
 
 
66
struct fts_expunge_log *fts_expunge_log_init(const char *path)
 
67
{
 
68
        struct fts_expunge_log *log;
 
69
 
 
70
        log = i_new(struct fts_expunge_log, 1);
 
71
        log->path = i_strdup(path);
 
72
        log->fd = -1;
 
73
        return log;
 
74
}
 
75
 
 
76
void fts_expunge_log_deinit(struct fts_expunge_log **_log)
 
77
{
 
78
        struct fts_expunge_log *log = *_log;
 
79
 
 
80
        *_log = NULL;
 
81
        i_free(log->path);
 
82
        i_free(log);
 
83
}
 
84
 
 
85
static int fts_expunge_log_open(struct fts_expunge_log *log, bool create)
 
86
{
 
87
        int fd;
 
88
 
 
89
        i_assert(log->fd == -1);
 
90
 
 
91
        /* FIXME: use proper permissions */
 
92
        fd = open(log->path, O_RDWR | O_APPEND | (create ? O_CREAT : 0), 0600);
 
93
        if (fd == -1) {
 
94
                if (errno == ENOENT && !create)
 
95
                        return 0;
 
96
 
 
97
                i_error("open(%s) failed: %m", log->path);
 
98
                return -1;
 
99
        }
 
100
        if (fstat(fd, &log->st) < 0) {
 
101
                i_error("fstat(%s) failed: %m", log->path);
 
102
                (void)close(fd);
 
103
                return -1;
 
104
        }
 
105
        log->fd = fd;
 
106
        return 1;
 
107
}
 
108
 
 
109
static int
 
110
fts_expunge_log_reopen_if_needed(struct fts_expunge_log *log, bool create)
 
111
{
 
112
        struct stat st;
 
113
 
 
114
        if (log->fd == -1)
 
115
                return fts_expunge_log_open(log, create);
 
116
 
 
117
        if (stat(log->path, &st) == 0) {
 
118
                if (st.st_ino == log->st.st_ino &&
 
119
                    CMP_DEV_T(st.st_dev, log->st.st_dev)) {
 
120
                        /* same file */
 
121
                        return 0;
 
122
                }
 
123
                /* file changed */
 
124
        } else if (errno == ENOENT) {
 
125
                /* recreate the file */
 
126
        } else {
 
127
                i_error("stat(%s) failed: %m", log->path);
 
128
                return -1;
 
129
        }
 
130
        if (close(log->fd) < 0)
 
131
                i_error("close(%s) failed: %m", log->path);
 
132
        log->fd = -1;
 
133
        return fts_expunge_log_open(log, create);
 
134
}
 
135
 
 
136
static int
 
137
fts_expunge_log_read_expunge_count(struct fts_expunge_log *log,
 
138
                                   uint32_t *expunge_count_r)
 
139
{
 
140
        ssize_t ret;
 
141
 
 
142
        i_assert(log->fd != -1);
 
143
 
 
144
        if (fstat(log->fd, &log->st) < 0) {
 
145
                i_error("fstat(%s) failed: %m", log->path);
 
146
                return -1;
 
147
        }
 
148
        if ((uoff_t)log->st.st_size < sizeof(*expunge_count_r)) {
 
149
                *expunge_count_r = 0;
 
150
                return 0;
 
151
        }
 
152
        /* we'll assume that write()s atomically grow the file size, as
 
153
           O_APPEND almost guarantees. even if not, having a race condition
 
154
           isn't the end of the world. the expunge count is simply read wrong
 
155
           and fts optimize is performed earlier or later than intended. */
 
156
        ret = pread(log->fd, expunge_count_r, sizeof(*expunge_count_r),
 
157
                    log->st.st_size - 4);
 
158
        if (ret < 0) {
 
159
                i_error("pread(%s) failed: %m", log->path);
 
160
                return -1;
 
161
        }
 
162
        if (ret != sizeof(*expunge_count_r)) {
 
163
                i_error("pread(%s) read only %d of %d bytes", log->path,
 
164
                        (int)ret, (int)sizeof(*expunge_count_r));
 
165
                return -1;
 
166
        }
 
167
        return 0;
 
168
}
 
169
 
 
170
struct fts_expunge_log_append_ctx *
 
171
fts_expunge_log_append_begin(struct fts_expunge_log *log)
 
172
{
 
173
        struct fts_expunge_log_append_ctx *ctx;
 
174
        pool_t pool;
 
175
 
 
176
        pool = pool_alloconly_create("fts expunge log append", 1024);
 
177
        ctx = p_new(pool, struct fts_expunge_log_append_ctx, 1);
 
178
        ctx->log = log;
 
179
        ctx->pool = pool;
 
180
        ctx->mailboxes =
 
181
                hash_table_create(default_pool, pool, 0,
 
182
                                  guid_128_hash, guid_128_cmp);
 
183
 
 
184
        if (fts_expunge_log_reopen_if_needed(log, TRUE) < 0)
 
185
                ctx->failed = TRUE;
 
186
        return ctx;
 
187
}
 
188
 
 
189
static struct fts_expunge_log_mailbox *
 
190
fts_expunge_log_mailbox_alloc(struct fts_expunge_log_append_ctx *ctx,
 
191
                              const guid_128_t mailbox_guid)
 
192
{
 
193
        struct fts_expunge_log_mailbox *mailbox;
 
194
 
 
195
        mailbox = p_new(ctx->pool, struct fts_expunge_log_mailbox, 1);
 
196
        memcpy(mailbox->guid, mailbox_guid, sizeof(mailbox->guid));
 
197
        p_array_init(&mailbox->uids, ctx->pool, 16);
 
198
        hash_table_insert(ctx->mailboxes, mailbox->guid, mailbox);
 
199
        return mailbox;
 
200
}
 
201
 
 
202
void fts_expunge_log_append_next(struct fts_expunge_log_append_ctx *ctx,
 
203
                                 const guid_128_t mailbox_guid,
 
204
                                 uint32_t uid)
 
205
{
 
206
        struct fts_expunge_log_mailbox *mailbox;
 
207
 
 
208
        if (ctx->prev_mailbox != NULL &&
 
209
            memcmp(mailbox_guid, ctx->prev_mailbox->guid, GUID_128_SIZE) == 0)
 
210
                mailbox = ctx->prev_mailbox;
 
211
        else {
 
212
                mailbox = hash_table_lookup(ctx->mailboxes, mailbox_guid);
 
213
                if (mailbox == NULL)
 
214
                        mailbox = fts_expunge_log_mailbox_alloc(ctx, mailbox_guid);
 
215
                ctx->prev_mailbox = mailbox;
 
216
        }
 
217
        if (!seq_range_array_add(&mailbox->uids, 0, uid))
 
218
                mailbox->uids_count++;
 
219
}
 
220
 
 
221
static void
 
222
fts_expunge_log_export(struct fts_expunge_log_append_ctx *ctx,
 
223
                       uint32_t expunge_count, buffer_t *output)
 
224
{
 
225
        struct hash_iterate_context *iter;
 
226
        void *key, *value;
 
227
        struct fts_expunge_log_record *rec;
 
228
        size_t rec_offset;
 
229
 
 
230
        iter = hash_table_iterate_init(ctx->mailboxes);
 
231
        while (hash_table_iterate(iter, &key, &value)) {
 
232
                struct fts_expunge_log_mailbox *mailbox = value;
 
233
 
 
234
                rec_offset = output->used;
 
235
                rec = buffer_append_space_unsafe(output, sizeof(*rec));
 
236
                memcpy(rec->guid, mailbox->guid, sizeof(rec->guid));
 
237
 
 
238
                /* uint32_t expunge_uid_ranges[]; */
 
239
                buffer_append(output, array_idx(&mailbox->uids, 0),
 
240
                              array_count(&mailbox->uids) *
 
241
                              sizeof(struct seq_range));
 
242
                /* uint32_t expunge_count; */
 
243
                expunge_count += mailbox->uids_count;
 
244
                buffer_append(output, &expunge_count, sizeof(expunge_count));
 
245
 
 
246
                /* update the header now that we know the record contents */
 
247
                rec = buffer_get_space_unsafe(output, rec_offset,
 
248
                                              output->used - rec_offset);
 
249
                rec->record_size = output->used - rec_offset;
 
250
                rec->checksum = crc32_data(&rec->record_size,
 
251
                                           rec->record_size -
 
252
                                           sizeof(rec->checksum));
 
253
        }
 
254
        hash_table_iterate_deinit(&iter);
 
255
}
 
256
 
 
257
static int
 
258
fts_expunge_log_write(struct fts_expunge_log_append_ctx *ctx)
 
259
{
 
260
        struct fts_expunge_log *log = ctx->log;
 
261
        buffer_t *buf;
 
262
        uint32_t expunge_count, *e;
 
263
        int ret;
 
264
 
 
265
        /* try to append to the latest file */
 
266
        if (fts_expunge_log_reopen_if_needed(log, TRUE) < 0)
 
267
                return -1;
 
268
 
 
269
        if (fts_expunge_log_read_expunge_count(log, &expunge_count) < 0)
 
270
                return -1;
 
271
 
 
272
        buf = buffer_create_dynamic(default_pool, 1024);
 
273
        fts_expunge_log_export(ctx, expunge_count, buf);
 
274
        /* the file was opened with O_APPEND, so this write() should be
 
275
           appended atomically without any need for locking. */
 
276
        for (;;) {
 
277
                if ((ret = write_full(log->fd, buf->data, buf->used)) < 0) {
 
278
                        i_error("write(%s) failed: %m", log->path);
 
279
                        if (ftruncate(log->fd, log->st.st_size) < 0)
 
280
                                i_error("ftruncate(%s) failed: %m", log->path);
 
281
                }
 
282
                if ((ret = fts_expunge_log_reopen_if_needed(log, TRUE)) <= 0)
 
283
                        break;
 
284
                /* the log was unlinked, so we'll need to write again to
 
285
                   the new file. the expunge_count needs to be reset to zero
 
286
                   from here. */
 
287
                e = buffer_get_space_unsafe(buf, buf->used - sizeof(uint32_t),
 
288
                                            sizeof(uint32_t));
 
289
                i_assert(*e > expunge_count);
 
290
                *e -= expunge_count;
 
291
                expunge_count = 0;
 
292
        }
 
293
        buffer_free(&buf);
 
294
 
 
295
        if (ret == 0) {
 
296
                /* finish by closing the log. this forces NFS to flush the
 
297
                   changes to disk without our having to explicitly play with
 
298
                   fsync() */
 
299
                if (close(log->fd) < 0) {
 
300
                        /* FIXME: we should ftruncate() in case there
 
301
                           were partial writes.. */
 
302
                        i_error("close(%s) failed: %m", log->path);
 
303
                        ret = -1;
 
304
                }
 
305
                log->fd = -1;
 
306
        }
 
307
        return ret;
 
308
}
 
309
 
 
310
int fts_expunge_log_append_commit(struct fts_expunge_log_append_ctx **_ctx)
 
311
{
 
312
        struct fts_expunge_log_append_ctx *ctx = *_ctx;
 
313
        int ret = ctx->failed ? -1 : 0;
 
314
 
 
315
        *_ctx = NULL;
 
316
        if (ret == 0)
 
317
                ret = fts_expunge_log_write(ctx);
 
318
 
 
319
        hash_table_destroy(&ctx->mailboxes);
 
320
        pool_unref(&ctx->pool);
 
321
        return ret;
 
322
}
 
323
 
 
324
int fts_expunge_log_uid_count(struct fts_expunge_log *log,
 
325
                              unsigned int *expunges_r)
 
326
{
 
327
        int ret;
 
328
 
 
329
        if ((ret = fts_expunge_log_reopen_if_needed(log, FALSE)) <= 0) {
 
330
                *expunges_r = 0;
 
331
                return ret;
 
332
        }
 
333
 
 
334
        return fts_expunge_log_read_expunge_count(log, expunges_r);
 
335
}
 
336
 
 
337
struct fts_expunge_log_read_ctx *
 
338
fts_expunge_log_read_begin(struct fts_expunge_log *log)
 
339
{
 
340
        struct fts_expunge_log_read_ctx *ctx;
 
341
 
 
342
        ctx = i_new(struct fts_expunge_log_read_ctx, 1);
 
343
        ctx->log = log;
 
344
        if (fts_expunge_log_reopen_if_needed(log, FALSE) < 0)
 
345
                ctx->failed = TRUE;
 
346
        else if (log->fd != -1)
 
347
                ctx->input = i_stream_create_fd(log->fd, (size_t)-1, FALSE);
 
348
        return ctx;
 
349
}
 
350
 
 
351
static bool
 
352
fts_expunge_log_record_size_is_valid(const struct fts_expunge_log_record *rec,
 
353
                                     unsigned int *uids_size_r)
 
354
{
 
355
        if (rec->record_size < sizeof(*rec) + sizeof(uint32_t)*3)
 
356
                return FALSE;
 
357
        *uids_size_r = rec->record_size - sizeof(*rec) - sizeof(uint32_t);
 
358
        return *uids_size_r % sizeof(uint32_t)*2 == 0;
 
359
}
 
360
 
 
361
static void
 
362
fts_expunge_log_read_failure(struct fts_expunge_log_read_ctx *ctx,
 
363
                             unsigned int wanted_size)
 
364
{
 
365
        size_t size;
 
366
 
 
367
        if (ctx->input->stream_errno != 0) {
 
368
                ctx->failed = TRUE;
 
369
                i_error("read(%s) failed: %m", ctx->log->path);
 
370
        } else {
 
371
                (void)i_stream_get_data(ctx->input, &size);
 
372
                ctx->corrupted = TRUE;
 
373
                i_error("Corrupted fts expunge log %s: "
 
374
                        "Unexpected EOF (read %"PRIuSIZE_T" / %u bytes)",
 
375
                        ctx->log->path, size, wanted_size);
 
376
        }
 
377
}
 
378
 
 
379
const struct fts_expunge_log_read_record *
 
380
fts_expunge_log_read_next(struct fts_expunge_log_read_ctx *ctx)
 
381
{
 
382
        const unsigned char *data;
 
383
        const struct fts_expunge_log_record *rec;
 
384
        unsigned int uids_size;
 
385
        size_t size;
 
386
        uint32_t checksum;
 
387
 
 
388
        if (ctx->input == NULL)
 
389
                return NULL;
 
390
 
 
391
        /* initial read to try to get the record */
 
392
        (void)i_stream_read_data(ctx->input, &data, &size, IO_BLOCK_SIZE);
 
393
        if (size == 0 && ctx->input->stream_errno == 0) {
 
394
                /* expected EOF - mark the file as read by unlinking it */
 
395
                if (unlink(ctx->log->path) < 0 && errno != ENOENT)
 
396
                        i_error("unlink(%s) failed: %m", ctx->log->path);
 
397
 
 
398
                /* try reading again, in case something new was written */
 
399
                i_stream_sync(ctx->input);
 
400
                (void)i_stream_read_data(ctx->input, &data, &size,
 
401
                                         IO_BLOCK_SIZE);
 
402
        }
 
403
        if (size < sizeof(*rec)) {
 
404
                if (size == 0 && ctx->input->stream_errno == 0) {
 
405
                        /* expected EOF */
 
406
                        return NULL;
 
407
                }
 
408
                fts_expunge_log_read_failure(ctx, sizeof(*rec));
 
409
                return NULL;
 
410
        }
 
411
        rec = (const void *)data;
 
412
 
 
413
        if (!fts_expunge_log_record_size_is_valid(rec, &uids_size)) {
 
414
                ctx->corrupted = TRUE;
 
415
                i_error("Corrupted fts expunge log %s: "
 
416
                        "Invalid record size: %u",
 
417
                        ctx->log->path, rec->record_size);
 
418
                return NULL;
 
419
        }
 
420
 
 
421
        /* read the entire record */
 
422
        while (size < rec->record_size) {
 
423
                if (i_stream_read_data(ctx->input, &data, &size,
 
424
                                       rec->record_size-1) < 0) {
 
425
                        fts_expunge_log_read_failure(ctx, rec->record_size);
 
426
                        return NULL;
 
427
                }
 
428
                rec = (const void *)data;
 
429
        }
 
430
 
 
431
        /* verify that the record checksum is valid */
 
432
        checksum = crc32_data(&rec->record_size,
 
433
                              rec->record_size - sizeof(rec->checksum));
 
434
        if (checksum != rec->checksum) {
 
435
                ctx->corrupted = TRUE;
 
436
                i_error("Corrupted fts expunge log %s: "
 
437
                        "Record checksum mismatch: %u != %u",
 
438
                        ctx->log->path, checksum, rec->checksum);
 
439
                return NULL;
 
440
        }
 
441
 
 
442
        memcpy(ctx->read_rec.mailbox_guid, rec->guid,
 
443
               sizeof(ctx->read_rec.mailbox_guid));
 
444
        /* create the UIDs array by pointing it directly into input
 
445
           stream's buffer */
 
446
        buffer_create_const_data(&ctx->buffer, rec + 1, uids_size);
 
447
        array_create_from_buffer(&ctx->read_rec.uids, &ctx->buffer,
 
448
                                 sizeof(struct seq_range));
 
449
 
 
450
        i_stream_skip(ctx->input, rec->record_size);
 
451
        return &ctx->read_rec;
 
452
}
 
453
 
 
454
int fts_expunge_log_read_end(struct fts_expunge_log_read_ctx **_ctx)
 
455
{
 
456
        struct fts_expunge_log_read_ctx *ctx = *_ctx;
 
457
        int ret = ctx->failed ? -1 : (ctx->corrupted ? 0 : 1);
 
458
 
 
459
        *_ctx = NULL;
 
460
 
 
461
        if (ctx->input != NULL)
 
462
                i_stream_unref(&ctx->input);
 
463
        i_free(ctx);
 
464
        return ret;
 
465
}