~ubuntu-branches/ubuntu/vivid/dovecot/vivid

« back to all changes in this revision

Viewing changes to src/lib-index/maildir/maildir-sync.c

  • Committer: Bazaar Package Importer
  • Author(s): Jaldhar H. Vyas
  • Date: 2005-11-05 23:19:19 UTC
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20051105231919-ydujs4y7687fpor2
Tags: upstream-1.0.alpha4
ImportĀ upstreamĀ versionĀ 1.0.alpha4

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Copyright (C) 2002-2003 Timo Sirainen */
2
 
 
3
 
/*
4
 
   Here's a description of how we handle Maildir synchronization and
5
 
   it's problems:
6
 
 
7
 
   We want to be as efficient as we can. The most efficient way to
8
 
   check if changes have occured is to stat() the new/ and cur/
9
 
   directories and uidlist file - if their mtimes haven't changed,
10
 
   there's no changes and we don't need to do anything.
11
 
 
12
 
   Problem 1: Multiple changes can happen within a single second -
13
 
   nothing guarantees that once we synced it, someone else didn't just
14
 
   then make a modification. Such modifications wouldn't get noticed
15
 
   until a new modification occured later.
16
 
 
17
 
   Problem 2: Syncing cur/ directory is much more costly than syncing
18
 
   new/. Moving mails from new/ to cur/ will always change mtime of
19
 
   cur/ causing us to sync it as well.
20
 
 
21
 
   Problem 3: We may not be able to move mail from new/ to cur/
22
 
   because we're out of quota, or simply because we're accessing a
23
 
   read-only mailbox.
24
 
 
25
 
 
26
 
   MAILDIR_SYNC_SECS
27
 
   -----------------
28
 
 
29
 
   Several checks below use MAILDIR_SYNC_SECS, which should be maximum
30
 
   clock drift between all computers accessing the maildir (eg. via
31
 
   NFS), rounded up to next second. Our default is 1 second, since
32
 
   everyone should be using NTP.
33
 
 
34
 
   Note that setting it to 0 works only if there's only one computer
35
 
   accessing the maildir. It's practically impossible to make two
36
 
   clocks _exactly_ synchronized.
37
 
 
38
 
   It might be possible to only use file server's clock by looking at
39
 
   the atime field, but I don't know how well that would actually work.
40
 
 
41
 
   cur directory
42
 
   -------------
43
 
 
44
 
   We have maildir_cur_dirty variable which is set to cur/ directory's
45
 
   mtime when it's >= time() - MAILDIR_SYNC_SECS and we _think_ we have
46
 
   synchronized the directory.
47
 
 
48
 
   When maildir_cur_dirty is non-zero, we don't synchronize the cur/
49
 
   directory until
50
 
 
51
 
      a) cur/'s mtime changes
52
 
      b) opening a mail fails with ENOENT
53
 
      c) time() > maildir_cur_dirty + MAILDIR_SYNC_SECS
54
 
 
55
 
   This allows us to modify the maildir multiple times without having
56
 
   to sync it at every change. The sync will eventually be done to
57
 
   make sure we didn't miss any external changes.
58
 
 
59
 
   The maildir_cur_dirty is set when:
60
 
 
61
 
      - we change message flags
62
 
      - we expunge messages
63
 
      - we move mail from new/ to cur/
64
 
      - we sync cur/ directory and it's mtime is
65
 
        >= time() - MAILDIR_SYNC_SECS
66
 
 
67
 
   It's unset when we do the final syncing, ie. when mtime is
68
 
   older than time() - MAILDIR_SYNC_SECS.
69
 
 
70
 
   new directory
71
 
   -------------
72
 
 
73
 
   If new/'s mtime is >= time() - MAILDIR_SYNC_SECS, always synchronize
74
 
   it. maildir_cur_dirty-like feature might save us a few syncs, but
75
 
   that might break a client which saves a mail in one connection and
76
 
   tries to fetch it in another one. new/ directory is almost always
77
 
   empty, so syncing it should be very fast anyway. Actually this can
78
 
   still happen if we sync only new/ dir while another client is also
79
 
   moving mails from it to cur/ - it takes us a while to see them.
80
 
   That's pretty unlikely to happen however, and only way to fix it
81
 
   would be to always synchronize cur/ after new/.
82
 
 
83
 
   Normally we move all mails from new/ to cur/ whenever we sync it. If
84
 
   it's not possible for some reason, we set maildir_have_new flag on
85
 
   which instructs synchronization to check files in new/ directory as
86
 
   well. maildir_keep_new flag is also set which instructs syncing to
87
 
   not even try to move mails to cur/ anymore.
88
 
 
89
 
   If client tries to change a flag for message in new/, we try to
90
 
   rename() it into cur/. If it's successful, we clear the
91
 
   maildir_keep_new flag so at next sync we'll try to move all of them
92
 
   to cur/. When all of them have been moved, maildir_have_new flag is
93
 
   cleared as well. Expunges will also clear maildir_keep_new flag.
94
 
 
95
 
   If rename() still fails because of ENOSPC or EDQUOT, we still save
96
 
   the flag changes in index with dirty-flag on. When moving the mail
97
 
   to cur/ directory, or when we notice it's already moved there, we
98
 
   apply the flag changes to the filename, rename it and remove the
99
 
   dirty flag. If there's dirty flags, this should be tried every time
100
 
   after expunge or when closing the mailbox.
101
 
 
102
 
   uidlist
103
 
   -------
104
 
 
105
 
   This file contains UID <-> filename mappings. It's updated only when
106
 
   new mail arrives, so it may contain filenames that have already been
107
 
   deleted. Updating is done by getting uidlist.lock file, writing the
108
 
   whole uidlist into it and rename()ing it over the old uidlist. This
109
 
   means there's no need to lock the file for reading.
110
 
 
111
 
   Whenever uidlist is rewritten, it's mtime must be larger than the old
112
 
   one's. Use utime() before rename() if needed.
113
 
 
114
 
   Only time you have to read this file is when assigning new UIDs for
115
 
   messages, to see if they already have UIDs. If file's mtime hasn't
116
 
   changed, you don't have to do even that.
117
 
 
118
 
   broken clients
119
 
   --------------
120
 
 
121
 
   Originally the middle identifier in Maildir filename was specified
122
 
   only as <process id>_<delivery counter>. That however created a
123
 
   problem with randomized PIDs which made it possible that the same
124
 
   PID was reused within one second.
125
 
 
126
 
   So if within one second a mail was delivered, MUA moved it to cur/
127
 
   and another mail was delivered by a new process using same PID as
128
 
   the first one, we likely ended up overwriting the first mail when
129
 
   the second mail was moved over it.
130
 
 
131
 
   Nowadays everyone should be giving a bit more specific identifier,
132
 
   for example include microseconds in it which Dovecot does.
133
 
 
134
 
   There's a simple way to prevent this from happening in some cases:
135
 
   Don't move the mail from new/ to cur/ if it's mtime is >= time() -
136
 
   MAILDIR_SYNC_SECS. The second delivery's link() call then fails
137
 
   because the file is already in new/, and it will then use a
138
 
   different filename. There's a few problems with this however:
139
 
 
140
 
      - it requires extra stat() call which is unneeded extra I/O
141
 
      - another MUA might still move the mail to cur/
142
 
      - if first file's flags are modified by either Dovecot or another
143
 
        MUA, it's moved to cur/ (you _could_ just do the dirty-flagging
144
 
        but that'd be ugly)
145
 
 
146
 
   Because this is useful only for very few people and it requires
147
 
   extra I/O, I decided not to implement this. It should be however
148
 
   quite easy to do since we need to be able to deal with files in new/
149
 
   in any case.
150
 
 
151
 
   It's also possible to never accidentally overwrite a mail by using
152
 
   link() + unlink() rather than rename(). This however isn't very
153
 
   good idea as it introduces potential race conditions when multiple
154
 
   clients are accessing the mailbox:
155
 
 
156
 
   Trying to move the same mail from new/ to cur/ at the same time:
157
 
 
158
 
      a) Client 1 uses slightly different filename than client 2,
159
 
         for example one sets read-flag on but the other doesn't.
160
 
         You have the same mail duplicated now.
161
 
 
162
 
      b) Client 3 sees the mail between Client 1's and 2's link() calls
163
 
         and changes it's flag. You have the same mail duplicated now.
164
 
 
165
 
   And it gets worse when they're unlink()ing in cur/ directory:
166
 
 
167
 
      c) Client 1 changes mails's flag and client 2 changes it back
168
 
         between 1's link() and unlink(). The mail is now expunged.
169
 
 
170
 
      d) If you try to deal with the duplicates by unlink()ing another
171
 
         one of them, you might end up unlinking both of them.
172
 
 
173
 
   So, what should we do then if we notice a duplicate? First of all,
174
 
   it might not be a duplicate at all, readdir() might have just
175
 
   returned it twice because it was just renamed. What we should do is
176
 
   create a completely new base name for it and rename() it to that.
177
 
   If the call fails with ENOENT, it only means that it wasn't a
178
 
   duplicate after all.
179
 
*/
180
 
 
181
 
#include "lib.h"
182
 
#include "buffer.h"
183
 
#include "istream.h"
184
 
#include "hash.h"
185
 
#include "ioloop.h"
186
 
#include "str.h"
187
 
#include "maildir-index.h"
188
 
#include "maildir-uidlist.h"
189
 
#include "mail-index-data.h"
190
 
#include "mail-index-util.h"
191
 
 
192
 
#include <stdio.h>
193
 
#include <stdlib.h>
194
 
#include <unistd.h>
195
 
#include <fcntl.h>
196
 
#include <dirent.h>
197
 
#include <utime.h>
198
 
#include <sys/stat.h>
199
 
 
200
 
#define MAILDIR_SYNC_SECS 1
201
 
 
202
 
enum maildir_file_action {
203
 
        MAILDIR_FILE_ACTION_EXPUNGE,
204
 
        MAILDIR_FILE_ACTION_UPDATE_FLAGS,
205
 
        MAILDIR_FILE_ACTION_UPDATE_CONTENT,
206
 
        MAILDIR_FILE_ACTION_NEW,
207
 
        MAILDIR_FILE_ACTION_NONE,
208
 
 
209
 
        MAILDIR_FILE_FLAG_NEWDIR        = 0x1000,
210
 
        MAILDIR_FILE_FLAG_ALLOCED       = 0x2000,
211
 
        MAILDIR_FILE_FLAGS              = 0x3000
212
 
};
213
 
 
214
 
struct maildir_hash_context {
215
 
        struct mail_index *index;
216
 
        struct mail_index_record *new_mail;
217
 
 
218
 
        int failed;
219
 
};
220
 
 
221
 
struct maildir_hash_rec {
222
 
        struct mail_index_record *rec;
223
 
        enum maildir_file_action action;
224
 
};
225
 
#define ACTION(hash) ((hash)->action & ~MAILDIR_FILE_FLAGS)
226
 
 
227
 
struct maildir_sync_context {
228
 
        struct mail_index *index;
229
 
        const char *new_dir, *cur_dir;
230
 
 
231
 
        pool_t pool;
232
 
        struct hash_table *files;
233
 
        unsigned int new_count;
234
 
 
235
 
        DIR *new_dirp;
236
 
        struct dirent *new_dent;
237
 
 
238
 
        struct maildir_uidlist *uidlist;
239
 
        unsigned int readonly_check:1;
240
 
        unsigned int flag_updates:1;
241
 
        unsigned int uidlist_rewrite:1;
242
 
        unsigned int new_mails_new:1;
243
 
        unsigned int new_mails_cur:1;
244
 
};
245
 
 
246
 
static int maildir_sync_cur_dir(struct maildir_sync_context *ctx);
247
 
 
248
 
/* a char* hash function from ASU -- from glib */
249
 
static unsigned int maildir_hash(const void *p)
250
 
{
251
 
        const unsigned char *s = p;
252
 
        unsigned int g, h = 0;
253
 
 
254
 
        while (*s != ':' && *s != '\0') {
255
 
                h = (h << 4) + *s;
256
 
                if ((g = h & 0xf0000000UL)) {
257
 
                        h = h ^ (g >> 24);
258
 
                        h = h ^ g;
259
 
                }
260
 
                s++;
261
 
        }
262
 
 
263
 
        return h;
264
 
}
265
 
 
266
 
static int maildir_cmp(const void *p1, const void *p2)
267
 
{
268
 
        const char *s1 = p1, *s2 = p2;
269
 
 
270
 
        while (*s1 == *s2 && *s1 != ':' && *s1 != '\0') {
271
 
                s1++; s2++;
272
 
        }
273
 
        if ((*s1 == '\0' || *s1 == ':') &&
274
 
            (*s2 == '\0' || *s2 == ':'))
275
 
                return 0;
276
 
        return *s1 - *s2;
277
 
}
278
 
 
279
 
static void maildir_update_filename_memory(struct mail_index *index,
280
 
                                           const char *fname)
281
 
{
282
 
        char *new_fname;
283
 
 
284
 
        if (index->new_filename_pool == NULL) {
285
 
                index->new_filename_pool =
286
 
                        pool_alloconly_create("Maildir fname", 10240);
287
 
        }
288
 
        if (index->new_filenames == NULL) {
289
 
                index->new_filenames =
290
 
                        hash_create(system_pool, index->new_filename_pool, 0,
291
 
                                    maildir_hash, maildir_cmp);
292
 
        }
293
 
 
294
 
        new_fname = p_strdup(index->new_filename_pool, fname);
295
 
        hash_insert(index->new_filenames, new_fname, new_fname);
296
 
}
297
 
 
298
 
static int maildir_update_filename(struct maildir_sync_context *ctx,
299
 
                                   struct mail_index_record *rec,
300
 
                                   const char *new_fname)
301
 
{
302
 
        struct mail_index_update *update;
303
 
 
304
 
        if (ctx->index->lock_type != MAIL_LOCK_EXCLUSIVE) {
305
 
                maildir_update_filename_memory(ctx->index, new_fname);
306
 
                return TRUE;
307
 
        }
308
 
 
309
 
        update = ctx->index->update_begin(ctx->index, rec);
310
 
        ctx->index->update_field(update, DATA_FIELD_LOCATION, new_fname, 0);
311
 
        return ctx->index->update_end(update);
312
 
}
313
 
 
314
 
static int maildir_update_flags(struct maildir_sync_context *ctx,
315
 
                                struct mail_index_record *rec,
316
 
                                unsigned int seq, const char *new_fname)
317
 
{
318
 
        enum mail_flags flags;
319
 
 
320
 
        if (ctx->index->lock_type != MAIL_LOCK_EXCLUSIVE)
321
 
                return TRUE;
322
 
 
323
 
        flags = maildir_filename_get_flags(new_fname, rec->msg_flags);
324
 
        if (flags != rec->msg_flags) {
325
 
                if (!ctx->index->update_flags(ctx->index, rec,
326
 
                                              seq, flags, TRUE))
327
 
                        return FALSE;
328
 
        }
329
 
 
330
 
        return TRUE;
331
 
}
332
 
 
333
 
static int maildir_sync_open_uidlist(struct maildir_sync_context *ctx)
334
 
{
335
 
        struct mail_index *index = ctx->index;
336
 
        struct stat st;
337
 
        const char *path;
338
 
 
339
 
        if (ctx->uidlist != NULL)
340
 
                return TRUE;
341
 
 
342
 
        /* open it only if it's changed since we last synced it. */
343
 
        path = t_strconcat(index->control_dir, "/" MAILDIR_UIDLIST_NAME, NULL);
344
 
        if (stat(path, &st) < 0) {
345
 
                if (errno == ENOENT) {
346
 
                        /* doesn't exist yet, create it */
347
 
                        switch (maildir_uidlist_try_lock(ctx->index)) {
348
 
                        case -1:
349
 
                                return FALSE;
350
 
                        case 1:
351
 
                                ctx->uidlist_rewrite = TRUE;
352
 
                                break;
353
 
                        }
354
 
 
355
 
                        return TRUE;
356
 
                }
357
 
                return index_file_set_syscall_error(index, path, "stat()");
358
 
        }
359
 
 
360
 
        /* FIXME: last_uidlist_mtime should be in index headers */
361
 
        if (st.st_mtime == index->last_uidlist_mtime)
362
 
                return TRUE;
363
 
 
364
 
        ctx->uidlist = maildir_uidlist_open(index);
365
 
        if (ctx->uidlist == NULL)
366
 
                return TRUE;
367
 
 
368
 
        if (ctx->uidlist->uid_validity != index->header->uid_validity) {
369
 
                /* uidvalidity changed */
370
 
                if (!index->rebuilding && index->opened) {
371
 
                        index_set_corrupted(index,
372
 
                                            "UIDVALIDITY changed in uidlist");
373
 
                        return FALSE;
374
 
                }
375
 
 
376
 
                if (!index->rebuilding) {
377
 
                        index->set_flags |= MAIL_INDEX_FLAG_REBUILD;
378
 
                        return FALSE;
379
 
                }
380
 
 
381
 
                index->header->uid_validity = ctx->uidlist->uid_validity;
382
 
                i_assert(index->header->next_uid == 1);
383
 
        }
384
 
 
385
 
        if (index->header->next_uid > ctx->uidlist->next_uid) {
386
 
                index_set_corrupted(index, "index.next_uid (%u) > "
387
 
                                    "uidlist.next_uid (%u)",
388
 
                                    index->header->next_uid,
389
 
                                    ctx->uidlist->next_uid);
390
 
                return FALSE;
391
 
        }
392
 
 
393
 
        return TRUE;
394
 
}
395
 
 
396
 
static int is_file_content_changed(struct mail_index *index,
397
 
                                   struct mail_index_record *rec,
398
 
                                   const char *dir, const char *fname)
399
 
{
400
 
#define DATA_HDR_SIZE (DATA_HDR_HEADER_SIZE | DATA_HDR_BODY_SIZE)
401
 
        struct mail_index_data_record_header *data_hdr;
402
 
        struct stat st;
403
 
        const char *path;
404
 
 
405
 
        if ((rec->data_fields & DATA_HDR_INTERNAL_DATE) == 0 &&
406
 
            (rec->data_fields & DATA_HDR_SIZE) != DATA_HDR_SIZE) {
407
 
                /* nothing in cache, we can't know if it's changed */
408
 
                return FALSE;
409
 
        }
410
 
 
411
 
        t_push();
412
 
        path = t_strdup_printf("%s/%s", dir, fname);
413
 
 
414
 
        if (stat(path, &st) < 0) {
415
 
                if (errno != ENOENT)
416
 
                        index_file_set_syscall_error(index, path, "stat()");
417
 
                t_pop();
418
 
                return FALSE;
419
 
        }
420
 
        t_pop();
421
 
 
422
 
        data_hdr = mail_index_data_lookup_header(index->data, rec);
423
 
        if (data_hdr == NULL)
424
 
                return FALSE;
425
 
 
426
 
        if ((rec->data_fields & DATA_HDR_INTERNAL_DATE) != 0 &&
427
 
            st.st_mtime != data_hdr->internal_date)
428
 
                return TRUE;
429
 
 
430
 
        if ((rec->data_fields & DATA_HDR_SIZE) == DATA_HDR_SIZE &&
431
 
            (uoff_t)st.st_size != data_hdr->body_size + data_hdr->header_size)
432
 
                return TRUE;
433
 
 
434
 
        return FALSE;
435
 
}
436
 
 
437
 
static void uidlist_hash_get_filenames(void *key, void *value, void *context)
438
 
{
439
 
        buffer_t *buf = context;
440
 
        struct maildir_hash_rec *hash_rec = value;
441
 
 
442
 
        if (ACTION(hash_rec) == MAILDIR_FILE_ACTION_NEW)
443
 
                buffer_append(buf, (const void *) &key, sizeof(const char *));
444
 
}
445
 
 
446
 
static int maildir_time_cmp(const void *p1, const void *p2)
447
 
{
448
 
        const char *s1 = *((const char **) p1);
449
 
        const char *s2 = *((const char **) p2);
450
 
        time_t t1 = 0, t2 = 0;
451
 
 
452
 
        /* we have to do numeric comparision, strcmp() will break when
453
 
           there's different amount of digits (mostly the 999999999 ->
454
 
           1000000000 change in Sep 9 2001) */
455
 
        while (*s1 >= '0' && *s1 <= '9') {
456
 
                t1 = t1*10 + (*s1 - '0');
457
 
                s1++;
458
 
        }
459
 
        while (*s2 >= '0' && *s2 <= '9') {
460
 
                t2 = t2*10 + (*s2 - '0');
461
 
                s2++;
462
 
        }
463
 
 
464
 
        return t1 < t2 ? -1 : t1 > t2 ? 1 : 0;
465
 
}
466
 
 
467
 
static int maildir_full_sync_finish(struct maildir_sync_context *ctx)
468
 
{
469
 
        struct mail_index *index = ctx->index;
470
 
        struct maildir_uidlist *uidlist;
471
 
        struct mail_index_record *rec;
472
 
        struct maildir_hash_rec *hash_rec;
473
 
        struct maildir_uidlist_rec uid_rec;
474
 
        enum maildir_file_action action;
475
 
        const char *fname, **new_files, *dir;
476
 
        void *orig_key, *orig_value;
477
 
        unsigned int seq, uid, last_uid, i, new_flag;
478
 
        int new_dir;
479
 
        buffer_t *buf;
480
 
 
481
 
        if (ctx->new_count > 0) {
482
 
                /* new mails, either they're already in uidlist or we have
483
 
                   to add them there. If we want to add them, we'll need to
484
 
                   sync it locked. */
485
 
                if (maildir_uidlist_try_lock(ctx->index) < 0)
486
 
                        return FALSE;
487
 
 
488
 
                if (!maildir_sync_open_uidlist(ctx))
489
 
                        return FALSE;
490
 
        }
491
 
 
492
 
        seq = 0;
493
 
        rec = index->lookup(index, 1);
494
 
        uidlist = ctx->uidlist;
495
 
 
496
 
        if (uidlist == NULL)
497
 
                memset(&uid_rec, 0, sizeof(uid_rec));
498
 
        else {
499
 
                if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
500
 
                        return FALSE;
501
 
        }
502
 
 
503
 
        while (rec != NULL) {
504
 
                seq++; uid = rec->uid;
505
 
 
506
 
                /* skip over the expunged records in uidlist */
507
 
                while (uid_rec.uid != 0 && uid_rec.uid < uid) {
508
 
                        if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
509
 
                                return FALSE;
510
 
                }
511
 
 
512
 
                fname = maildir_get_location(index, rec);
513
 
                if (fname == NULL)
514
 
                        return FALSE;
515
 
 
516
 
                if (!hash_lookup_full(ctx->files, fname,
517
 
                                      &orig_key, &orig_value)) {
518
 
                        /* none action */
519
 
                        hash_rec = NULL;
520
 
                } else {
521
 
                        hash_rec = orig_value;
522
 
                }
523
 
 
524
 
                if (uid_rec.uid == uid &&
525
 
                    maildir_cmp(fname, uid_rec.filename) != 0) {
526
 
                        index_set_corrupted(index,
527
 
                                "Filename mismatch for UID %u: %s vs %s",
528
 
                                uid, fname, uid_rec.filename);
529
 
                        return FALSE;
530
 
                }
531
 
 
532
 
                if (uid_rec.uid > uid && hash_rec != NULL &&
533
 
                    (ACTION(hash_rec) == MAILDIR_FILE_ACTION_UPDATE_FLAGS ||
534
 
                     ACTION(hash_rec) == MAILDIR_FILE_ACTION_NONE)) {
535
 
                        /* it's UID has changed. shouldn't happen. */
536
 
                        i_warning("UID changed for %s/%s: %u -> %u",
537
 
                                  index->mailbox_path, fname, uid, uid_rec.uid);
538
 
                        hash_rec->action = MAILDIR_FILE_ACTION_UPDATE_CONTENT |
539
 
                                (hash_rec->action & MAILDIR_FILE_FLAGS);
540
 
                }
541
 
 
542
 
                action = hash_rec != NULL ?
543
 
                        ACTION(hash_rec) : MAILDIR_FILE_ACTION_NONE;
544
 
                switch (action) {
545
 
                case MAILDIR_FILE_ACTION_EXPUNGE:
546
 
                        if (!index->expunge(index, rec, seq, TRUE))
547
 
                                return FALSE;
548
 
                        seq--;
549
 
                        break;
550
 
                case MAILDIR_FILE_ACTION_UPDATE_FLAGS:
551
 
                        if (!maildir_update_filename(ctx, rec, orig_key))
552
 
                                return FALSE;
553
 
                        if (!maildir_update_flags(ctx, rec, seq, orig_key))
554
 
                                return FALSE;
555
 
                        break;
556
 
                case MAILDIR_FILE_ACTION_UPDATE_CONTENT:
557
 
                        if (!index->expunge(index, rec, seq, TRUE))
558
 
                                return FALSE;
559
 
                        seq--;
560
 
                        hash_rec->action = MAILDIR_FILE_ACTION_NEW |
561
 
                                (hash_rec->action & MAILDIR_FILE_FLAGS);
562
 
                        ctx->new_count++;
563
 
                        break;
564
 
                case MAILDIR_FILE_ACTION_NONE:
565
 
                        break;
566
 
                default:
567
 
                        i_panic("BUG: %s/%s suddenly appeared as UID %u",
568
 
                                index->mailbox_path, (char *) orig_key, uid);
569
 
                }
570
 
 
571
 
                if (uid_rec.uid == uid) {
572
 
                        if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
573
 
                                return FALSE;
574
 
                }
575
 
                rec = index->next(index, rec);
576
 
        }
577
 
 
578
 
        if (seq != index->header->messages_count) {
579
 
                index_set_corrupted(index, "Wrong messages_count in header "
580
 
                                    "(%u != %u)", seq,
581
 
                                    index->header->messages_count);
582
 
                return FALSE;
583
 
        }
584
 
 
585
 
        /* if there's new mails which are already in uidlist, get them */
586
 
        last_uid = 0;
587
 
        while (uid_rec.uid != 0) {
588
 
                if (hash_lookup_full(ctx->files, uid_rec.filename,
589
 
                                     &orig_key, &orig_value))
590
 
                        hash_rec = orig_value;
591
 
                else
592
 
                        hash_rec = NULL;
593
 
 
594
 
                if (hash_rec != NULL &&
595
 
                    ACTION(hash_rec) == MAILDIR_FILE_ACTION_NONE) {
596
 
                        /* it's a duplicate, shouldn't happen */
597
 
                        i_error("%s: Found duplicate filename %s, rebuilding",
598
 
                                ctx->uidlist->fname, uid_rec.filename);
599
 
                        (void)unlink(ctx->uidlist->fname);
600
 
 
601
 
                        if (INDEX_IS_UIDLIST_LOCKED(index))
602
 
                                ctx->uidlist_rewrite = TRUE;
603
 
                        hash_rec = NULL;
604
 
                }
605
 
 
606
 
                if (hash_rec != NULL) {
607
 
                        i_assert(ACTION(hash_rec) == MAILDIR_FILE_ACTION_NEW);
608
 
 
609
 
                        /* make sure we set the same UID for it. */
610
 
                        if (index->header->next_uid > uid_rec.uid) {
611
 
                                index_set_corrupted(index,
612
 
                                                    "index.next_uid (%u) > "
613
 
                                                    "uid_rec.uid (%u)",
614
 
                                                    index->header->next_uid,
615
 
                                                    uid_rec.uid);
616
 
                                return FALSE;
617
 
                        }
618
 
                        index->header->next_uid = uid_rec.uid;
619
 
 
620
 
                        new_flag = hash_rec->action & MAILDIR_FILE_FLAG_NEWDIR;
621
 
                        hash_rec->action = MAILDIR_FILE_ACTION_NONE | new_flag;
622
 
                        ctx->new_count--;
623
 
 
624
 
                        if (new_flag != 0)
625
 
                                ctx->index->maildir_have_new = TRUE;
626
 
                        dir = new_flag != 0 ? ctx->new_dir : ctx->cur_dir;
627
 
 
628
 
                        if (!maildir_index_append_file(index, dir, orig_key,
629
 
                                                       new_flag != 0))
630
 
                                return FALSE;
631
 
                }
632
 
 
633
 
                if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
634
 
                        return FALSE;
635
 
        }
636
 
 
637
 
        if (ctx->uidlist != NULL) {
638
 
                /* update our next_uid. it should have been checked for
639
 
                   sanity already. */
640
 
                struct stat st;
641
 
 
642
 
                i_assert(index->header->next_uid <= ctx->uidlist->next_uid);
643
 
                index->header->next_uid = ctx->uidlist->next_uid;
644
 
 
645
 
                /* uidlist is now synced, remember that. */
646
 
                if (fstat(i_stream_get_fd(ctx->uidlist->input), &st) < 0) {
647
 
                        return index_file_set_syscall_error(index,
648
 
                                                            ctx->uidlist->fname,
649
 
                                                            "fstat()");
650
 
                }
651
 
                index->last_uidlist_mtime = st.st_mtime;
652
 
        }
653
 
 
654
 
        if (ctx->new_count == 0 || !INDEX_IS_UIDLIST_LOCKED(index)) {
655
 
                /* all done (or can't do it since we don't have lock) */
656
 
                return TRUE;
657
 
        }
658
 
 
659
 
        ctx->uidlist_rewrite = TRUE;
660
 
 
661
 
        /* then there's the completely new mails. sort them by the filename
662
 
           so we should get them to same order as they were created. */
663
 
        buf = buffer_create_static_hard(ctx->pool,
664
 
                                        ctx->new_count * sizeof(const char *));
665
 
        hash_foreach(ctx->files, uidlist_hash_get_filenames, buf);
666
 
        i_assert(buffer_get_used_size(buf) ==
667
 
                 ctx->new_count * sizeof(const char *));
668
 
 
669
 
        new_files = buffer_get_modifyable_data(buf, NULL);
670
 
        qsort(new_files, ctx->new_count, sizeof(const char *),
671
 
              maildir_time_cmp);
672
 
 
673
 
        if (!index->maildir_keep_new) {
674
 
                dir = ctx->cur_dir;
675
 
                new_dir = FALSE;
676
 
        } else {
677
 
                /* this is actually slightly wrong, because we don't really
678
 
                   know if some of the new messages are in cur/ already.
679
 
                   we could know that by saving it into buffer, but that'd
680
 
                   require extra memory. luckily it doesn't really matter if
681
 
                   we say it's in new/, but it's actually in cur/. we have
682
 
                   to deal with such case anyway since another client might
683
 
                   have just moved it. */
684
 
                dir = ctx->new_dir;
685
 
                new_dir = TRUE;
686
 
                ctx->index->maildir_have_new = TRUE;
687
 
        }
688
 
 
689
 
        for (i = 0; i < ctx->new_count; i++) {
690
 
                if (!maildir_index_append_file(index, dir,
691
 
                                               new_files[i], new_dir))
692
 
                        return FALSE;
693
 
        }
694
 
 
695
 
        ctx->new_count = 0;
696
 
        return TRUE;
697
 
}
698
 
 
699
 
static int maildir_full_sync_init(struct maildir_sync_context *ctx,
700
 
                                  int only_new)
701
 
{
702
 
        struct mail_index *index = ctx->index;
703
 
        struct mail_index_record *rec;
704
 
        struct maildir_hash_rec *hash_rec;
705
 
        const char *fname;
706
 
        size_t size;
707
 
        int have_new;
708
 
 
709
 
        /* FIXME: kludge. we want to have pointers to data file, so we must
710
 
           make sure that it's base address doesn't change. this call makes
711
 
           sure it's fully mmaped in memory even when we begin */
712
 
        if (mail_index_data_get_mmaped(index->data, &size) == NULL)
713
 
                return FALSE;
714
 
 
715
 
        if (index->header->messages_count >= INT_MAX/32) {
716
 
                index_set_corrupted(index, "Header says %u messages",
717
 
                                    index->header->messages_count);
718
 
                return FALSE;
719
 
        }
720
 
 
721
 
        /* read current messages in index into hash */
722
 
        size = nearest_power(index->header->messages_count *
723
 
                             sizeof(struct maildir_hash_rec) + 1024);
724
 
        ctx->pool = pool_alloconly_create("maildir sync", I_MAX(size, 16384));
725
 
        ctx->files = hash_create(default_pool, ctx->pool,
726
 
                                 index->header->messages_count * 2,
727
 
                                 maildir_hash, maildir_cmp);
728
 
        ctx->new_count = 0;
729
 
 
730
 
        have_new = FALSE;
731
 
 
732
 
        rec = index->lookup(index, 1);
733
 
        while (rec != NULL) {
734
 
                fname = maildir_get_location(index, rec);
735
 
                if (fname == NULL)
736
 
                        return FALSE;
737
 
 
738
 
                if ((rec->index_flags & INDEX_MAIL_FLAG_MAILDIR_NEW) != 0)
739
 
                        have_new = TRUE;
740
 
 
741
 
                if (!only_new ||
742
 
                    (rec->index_flags & INDEX_MAIL_FLAG_MAILDIR_NEW) != 0) {
743
 
                        hash_rec = p_new(ctx->pool, struct maildir_hash_rec, 1);
744
 
                        hash_rec->rec = rec;
745
 
                        hash_rec->action = MAILDIR_FILE_ACTION_EXPUNGE;
746
 
 
747
 
                        if (hash_lookup(ctx->files, fname) != NULL) {
748
 
                                index_set_corrupted(index,
749
 
                                        "Duplicated message %s", fname);
750
 
                                return FALSE;
751
 
                        }
752
 
 
753
 
                        /* WARNING: index must not be modified as long as
754
 
                           these hash keys exist. Modifying might change the
755
 
                           mmap base address. */
756
 
                        hash_insert(ctx->files, (void *) fname, hash_rec);
757
 
                }
758
 
 
759
 
                rec = index->next(index, rec);
760
 
        }
761
 
 
762
 
        index->maildir_have_new = have_new;
763
 
        return TRUE;
764
 
}
765
 
 
766
 
static int maildir_fix_duplicate(struct mail_index *index,
767
 
                                 const char *old_fname, int new_dir)
768
 
{
769
 
        const char *new_fname, *old_path, *new_path;
770
 
        int ret = TRUE;
771
 
 
772
 
        t_push();
773
 
 
774
 
        old_path = t_strconcat(index->mailbox_path, new_dir ? "/new/" : "/cur/",
775
 
                               old_fname, NULL);
776
 
 
777
 
        new_fname = maildir_generate_tmp_filename(&ioloop_timeval);
778
 
        new_path = t_strconcat(index->mailbox_path, "/new/", new_fname, NULL);
779
 
 
780
 
        if (rename(old_path, new_path) == 0) {
781
 
                i_warning("Fixed duplicate in %s: %s -> %s",
782
 
                          index->mailbox_path, old_fname, new_fname);
783
 
        } else if (errno != ENOENT) {
784
 
                index_set_error(index, "rename(%s, %s) failed: %m",
785
 
                                old_path, new_path);
786
 
                ret = FALSE;
787
 
        }
788
 
        t_pop();
789
 
 
790
 
        return ret;
791
 
}
792
 
 
793
 
static void uidlist_hash_fix_allocs(void *key, void *value, void *context)
794
 
{
795
 
        struct maildir_sync_context *ctx = context;
796
 
        struct maildir_hash_rec *hash_rec = value;
797
 
 
798
 
        switch (ACTION(hash_rec)) {
799
 
        case MAILDIR_FILE_ACTION_NONE:
800
 
                hash_remove(ctx->files, key);
801
 
                break;
802
 
        case MAILDIR_FILE_ACTION_EXPUNGE:
803
 
                if (hash_rec->action & MAILDIR_FILE_FLAG_ALLOCED) {
804
 
                        /* we're getting here because our recently
805
 
                           inserted node is traversed as well */
806
 
                        break;
807
 
                }
808
 
 
809
 
                hash_rec->action |= MAILDIR_FILE_FLAG_ALLOCED;
810
 
                hash_insert(ctx->files, p_strdup(ctx->pool, key), value);
811
 
                break;
812
 
        default:
813
 
                break;
814
 
        }
815
 
}
816
 
 
817
 
static int maildir_full_sync_dir(struct maildir_sync_context *ctx,
818
 
                                 const char *dir, int new_dir,
819
 
                                 DIR *dirp, struct dirent *d)
820
 
{
821
 
        struct maildir_hash_rec *hash_rec;
822
 
        void *orig_key, *orig_value;
823
 
        int check_content_changes, newflag;
824
 
 
825
 
        newflag = new_dir ? MAILDIR_FILE_FLAG_NEWDIR : 0;
826
 
 
827
 
        /* Do we want to check changes in file contents? This slows down
828
 
           things as we need to do extra stat() for all files. */
829
 
        check_content_changes = !ctx->readonly_check &&
830
 
                getenv("MAILDIR_CHECK_CONTENT_CHANGES") != NULL;
831
 
 
832
 
        do {
833
 
                if (d->d_name[0] == '.')
834
 
                        continue;
835
 
 
836
 
                if (!hash_lookup_full(ctx->files, d->d_name,
837
 
                                      &orig_key, &orig_value)) {
838
 
                        hash_rec = p_new(ctx->pool, struct maildir_hash_rec, 1);
839
 
                } else {
840
 
                        hash_rec = orig_value;
841
 
                        if (ACTION(hash_rec) != MAILDIR_FILE_ACTION_EXPUNGE) {
842
 
                                if (!maildir_fix_duplicate(ctx->index,
843
 
                                                           d->d_name, new_dir))
844
 
                                        return FALSE;
845
 
                                continue;
846
 
                        }
847
 
                }
848
 
 
849
 
                if (hash_rec->rec == NULL) {
850
 
                        /* new message */
851
 
                        if (ctx->readonly_check)
852
 
                                continue;
853
 
 
854
 
                        if (new_dir)
855
 
                                ctx->new_mails_new = TRUE;
856
 
                        else
857
 
                                ctx->new_mails_cur = TRUE;
858
 
 
859
 
                        ctx->new_count++;
860
 
                        hash_rec->action = MAILDIR_FILE_ACTION_NEW | newflag;
861
 
                        hash_insert(ctx->files, p_strdup(ctx->pool, d->d_name),
862
 
                                    hash_rec);
863
 
                        continue;
864
 
                }
865
 
 
866
 
                if (!new_dir && (hash_rec->rec->index_flags &
867
 
                                 INDEX_MAIL_FLAG_MAILDIR_NEW) != 0 &&
868
 
                    ctx->index->lock_type == MAIL_LOCK_EXCLUSIVE) {
869
 
                        /* mail was indexed in new/ but it has been
870
 
                           moved to cur/ later */
871
 
                        hash_rec->rec->index_flags &=
872
 
                                ~INDEX_MAIL_FLAG_MAILDIR_NEW;
873
 
                }
874
 
 
875
 
                if (check_content_changes &&
876
 
                    is_file_content_changed(ctx->index, hash_rec->rec,
877
 
                                            dir, d->d_name)) {
878
 
                        /* file content changed, treat it as new message */
879
 
                        hash_rec->action =
880
 
                                MAILDIR_FILE_ACTION_UPDATE_CONTENT | newflag;
881
 
 
882
 
                        hash_insert(ctx->files, p_strdup(ctx->pool, d->d_name),
883
 
                                    hash_rec);
884
 
                } else if (strcmp(orig_key, d->d_name) != 0) {
885
 
                        hash_rec->action =
886
 
                                MAILDIR_FILE_ACTION_UPDATE_FLAGS | newflag;
887
 
 
888
 
                        hash_insert(ctx->files, p_strdup(ctx->pool, d->d_name),
889
 
                                    hash_rec);
890
 
                        ctx->flag_updates = TRUE;
891
 
                } else {
892
 
                        hash_rec->action = MAILDIR_FILE_ACTION_NONE | newflag;
893
 
                }
894
 
        } while ((d = readdir(dirp)) != NULL);
895
 
 
896
 
        /* records that are left to hash must not have any (filename) pointers
897
 
           to index file. So remove none actions, and p_strdup() expunge
898
 
           actions. */
899
 
        hash_foreach(ctx->files, uidlist_hash_fix_allocs, ctx);
900
 
 
901
 
        return TRUE;
902
 
}
903
 
 
904
 
static int maildir_new_scan_first_file(struct maildir_sync_context *ctx)
905
 
{
906
 
        DIR *dirp;
907
 
        struct dirent *d;
908
 
 
909
 
        dirp = opendir(ctx->new_dir);
910
 
        if (dirp == NULL) {
911
 
                return index_file_set_syscall_error(ctx->index, ctx->new_dir,
912
 
                                                    "opendir()");
913
 
        }
914
 
 
915
 
        /* find first file */
916
 
        while ((d = readdir(dirp)) != NULL) {
917
 
                if (d->d_name[0] != '.')
918
 
                        break;
919
 
        }
920
 
 
921
 
        if (d == NULL) {
922
 
                if (closedir(dirp) < 0) {
923
 
                        index_file_set_syscall_error(ctx->index, ctx->new_dir,
924
 
                                                     "closedir()");
925
 
                }
926
 
        } else {
927
 
                ctx->new_dirp = dirp;
928
 
                ctx->new_dent = d;
929
 
        }
930
 
 
931
 
        return TRUE;
932
 
}
933
 
 
934
 
static int maildir_full_sync_dirs(struct maildir_sync_context *ctx)
935
 
{
936
 
        DIR *dirp;
937
 
        int failed;
938
 
 
939
 
        if (ctx->new_dirp == NULL &&
940
 
            (ctx->index->maildir_have_new || ctx->index->maildir_keep_new)) {
941
 
                if (!maildir_new_scan_first_file(ctx))
942
 
                        return FALSE;
943
 
        }
944
 
 
945
 
        if (ctx->new_dent != NULL) {
946
 
                if (!maildir_full_sync_dir(ctx, ctx->new_dir, TRUE,
947
 
                                           ctx->new_dirp, ctx->new_dent))
948
 
                        return FALSE;
949
 
                ctx->new_dent = NULL;
950
 
        }
951
 
 
952
 
        dirp = opendir(ctx->cur_dir);
953
 
        if (dirp == NULL) {
954
 
                return index_file_set_syscall_error(ctx->index, ctx->cur_dir,
955
 
                                                    "opendir()");
956
 
        }
957
 
 
958
 
        failed = !maildir_full_sync_dir(ctx, ctx->cur_dir, FALSE,
959
 
                                        dirp, readdir(dirp));
960
 
 
961
 
        if (closedir(dirp) < 0) {
962
 
                return index_file_set_syscall_error(ctx->index, ctx->cur_dir,
963
 
                                                    "closedir()");
964
 
        }
965
 
 
966
 
        return !failed;
967
 
}
968
 
 
969
 
static int maildir_sync_new_dir_full(struct maildir_sync_context *ctx)
970
 
{
971
 
        if (!ctx->index->set_lock(ctx->index, MAIL_LOCK_EXCLUSIVE))
972
 
                return FALSE;
973
 
 
974
 
        if (!maildir_full_sync_init(ctx, TRUE))
975
 
                return FALSE;
976
 
 
977
 
        if (!maildir_full_sync_dir(ctx, ctx->new_dir, TRUE,
978
 
                                   ctx->new_dirp, ctx->new_dent))
979
 
                return FALSE;
980
 
        ctx->new_dent = NULL;
981
 
 
982
 
        if (!maildir_full_sync_finish(ctx))
983
 
                return FALSE;
984
 
 
985
 
        return TRUE;
986
 
}
987
 
 
988
 
static int maildir_sync_new_dir(struct maildir_sync_context *ctx,
989
 
                                int move_to_cur, int append_index)
990
 
{
991
 
        struct dirent *d;
992
 
        string_t *sourcepath, *destpath;
993
 
        const char *final_dir;
994
 
 
995
 
        if (append_index) {
996
 
                if (ctx->index->maildir_have_new) {
997
 
                        /* some of the mails in new/ are already indexed.
998
 
                           we'll have to do a full sync. */
999
 
                        return maildir_sync_new_dir_full(ctx);
1000
 
                }
1001
 
 
1002
 
                if (!ctx->index->set_lock(ctx->index, MAIL_LOCK_EXCLUSIVE))
1003
 
                        return FALSE;
1004
 
 
1005
 
                switch (maildir_uidlist_try_lock(ctx->index)) {
1006
 
                case -1:
1007
 
                        return FALSE;
1008
 
                case 0:
1009
 
                        /* couldn't get a lock.
1010
 
                           no point in doing more. */
1011
 
                        return TRUE;
1012
 
                }
1013
 
 
1014
 
                /* make sure uidlist is up to date.
1015
 
                   if it's not, do a full sync. */
1016
 
                if (!maildir_sync_open_uidlist(ctx))
1017
 
                        return FALSE;
1018
 
 
1019
 
                if (ctx->uidlist != NULL)
1020
 
                        return maildir_sync_cur_dir(ctx);
1021
 
 
1022
 
                ctx->uidlist_rewrite = TRUE;
1023
 
        }
1024
 
 
1025
 
        d = ctx->new_dent;
1026
 
        ctx->new_dent = NULL;
1027
 
 
1028
 
        sourcepath = t_str_new(PATH_MAX);
1029
 
        destpath = t_str_new(PATH_MAX);
1030
 
 
1031
 
        final_dir = move_to_cur ? ctx->cur_dir : ctx->new_dir;
1032
 
 
1033
 
        do {
1034
 
                if (d->d_name[0] == '.')
1035
 
                        continue;
1036
 
 
1037
 
                str_truncate(sourcepath, 0);
1038
 
                str_printfa(sourcepath, "%s/%s", ctx->new_dir, d->d_name);
1039
 
 
1040
 
                if (move_to_cur) {
1041
 
                        str_truncate(destpath, 0);
1042
 
                        str_printfa(destpath, "%s/%s", ctx->cur_dir, d->d_name);
1043
 
                        if (strchr(d->d_name, ':') == NULL)
1044
 
                                str_append(destpath, ":2,");
1045
 
 
1046
 
                        if (rename(str_c(sourcepath), str_c(destpath)) < 0 &&
1047
 
                            errno != ENOENT) {
1048
 
                                if (ENOSPACE(errno))
1049
 
                                        ctx->index->nodiskspace = TRUE;
1050
 
                                else if (errno == EACCES)
1051
 
                                        ctx->index->mailbox_readonly = TRUE;
1052
 
                                else {
1053
 
                                        index_set_error(ctx->index,
1054
 
                                                "rename(%s, %s) failed: %m",
1055
 
                                                str_c(sourcepath),
1056
 
                                                str_c(destpath));
1057
 
                                        return FALSE;
1058
 
                                }
1059
 
 
1060
 
                                ctx->index->maildir_keep_new = TRUE;
1061
 
                                if (!append_index) {
1062
 
                                        ctx->new_dent = d;
1063
 
                                        return TRUE;
1064
 
                                }
1065
 
 
1066
 
                                /* continue by keeping them in new/ dir */
1067
 
                                final_dir = ctx->new_dir;
1068
 
                                move_to_cur = FALSE;
1069
 
                        }
1070
 
                }
1071
 
 
1072
 
                if (append_index) {
1073
 
                        if (!move_to_cur)
1074
 
                                ctx->index->maildir_have_new = TRUE;
1075
 
 
1076
 
                        t_push();
1077
 
                        if (!maildir_index_append_file(ctx->index, final_dir,
1078
 
                                                       d->d_name,
1079
 
                                                       !move_to_cur)) {
1080
 
                                t_pop();
1081
 
                                return FALSE;
1082
 
                        }
1083
 
                        t_pop();
1084
 
                }
1085
 
        } while ((d = readdir(ctx->new_dirp)) != NULL);
1086
 
 
1087
 
        return TRUE;
1088
 
}
1089
 
 
1090
 
static int maildir_sync_cur_dir(struct maildir_sync_context *ctx)
1091
 
{
1092
 
        struct mail_index *index = ctx->index;
1093
 
 
1094
 
        if (ctx->new_dent != NULL && !index->maildir_keep_new) {
1095
 
                /* there's also new mails. move them into cur/ first, if we
1096
 
                   can lock the uidlist */
1097
 
                switch (maildir_uidlist_try_lock(index)) {
1098
 
                case -1:
1099
 
                        return FALSE;
1100
 
                case 1:
1101
 
                        if (!maildir_sync_new_dir(ctx, TRUE, FALSE))
1102
 
                                return FALSE;
1103
 
                }
1104
 
        }
1105
 
 
1106
 
        if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
1107
 
                return FALSE;
1108
 
 
1109
 
        if (!maildir_full_sync_init(ctx, FALSE) ||
1110
 
            !maildir_full_sync_dirs(ctx) ||
1111
 
            !maildir_full_sync_finish(ctx))
1112
 
                return FALSE;
1113
 
 
1114
 
        return TRUE;
1115
 
}
1116
 
 
1117
 
static int maildir_index_sync_context(struct maildir_sync_context *ctx,
1118
 
                                      int *changes)
1119
 
 
1120
 
{
1121
 
        struct mail_index *index = ctx->index;
1122
 
        struct stat st;
1123
 
        time_t new_mtime, cur_mtime;
1124
 
 
1125
 
        if (!maildir_try_flush_dirty_flags(ctx->index, FALSE))
1126
 
                return FALSE;
1127
 
 
1128
 
        if (index->fd != -1) {
1129
 
                /* FIXME: file_sync_stamp should be in index file's headers.
1130
 
                   it should also contain maildir_cur_dirty. */
1131
 
                if (fstat(index->fd, &st) < 0)
1132
 
                        return index_set_syscall_error(index, "fstat()");
1133
 
                index->file_sync_stamp = st.st_mtime;
1134
 
        }
1135
 
 
1136
 
        if (stat(ctx->new_dir, &st) < 0) {
1137
 
                index_file_set_syscall_error(index, ctx->new_dir, "stat()");
1138
 
                return FALSE;
1139
 
        }
1140
 
        new_mtime = st.st_mtime;
1141
 
 
1142
 
        if (stat(ctx->cur_dir, &st) < 0) {
1143
 
                index_file_set_syscall_error(index, ctx->cur_dir, "stat()");
1144
 
                return FALSE;
1145
 
        }
1146
 
        cur_mtime = st.st_mtime;
1147
 
 
1148
 
        if (new_mtime != index->last_new_mtime ||
1149
 
            new_mtime >= ioloop_time - MAILDIR_SYNC_SECS) {
1150
 
                if (!maildir_new_scan_first_file(ctx))
1151
 
                        return FALSE;
1152
 
        }
1153
 
 
1154
 
        if (cur_mtime != index->file_sync_stamp ||
1155
 
            (index->maildir_cur_dirty != 0 &&
1156
 
             index->maildir_cur_dirty < ioloop_time - MAILDIR_SYNC_SECS)) {
1157
 
                /* cur/ changed, or delayed cur/ check */
1158
 
                if (changes != NULL)
1159
 
                        *changes = TRUE;
1160
 
 
1161
 
                if (!maildir_sync_cur_dir(ctx))
1162
 
                        return FALSE;
1163
 
        }
1164
 
 
1165
 
        if (ctx->new_dent != NULL) {
1166
 
                if (changes != NULL)
1167
 
                        *changes = TRUE;
1168
 
 
1169
 
                if (!maildir_sync_new_dir(ctx, !index->maildir_keep_new, TRUE))
1170
 
                        return FALSE;
1171
 
 
1172
 
                /* this will set maildir_cur_dirty. it may actually be
1173
 
                   different from cur/'s mtime if we're unlucky, but that
1174
 
                   doesn't really matter and it's not worth the extra stat() */
1175
 
                if (ctx->new_dent == NULL &&
1176
 
                    (ctx->new_count == 0 || !ctx->new_mails_new))
1177
 
                        cur_mtime = time(NULL);
1178
 
        }
1179
 
 
1180
 
        if (ctx->uidlist_rewrite) {
1181
 
                i_assert(INDEX_IS_UIDLIST_LOCKED(index));
1182
 
 
1183
 
                if (!maildir_uidlist_rewrite(index, &index->last_uidlist_mtime))
1184
 
                        return FALSE;
1185
 
        }
1186
 
 
1187
 
        if (index->lock_type == MAIL_LOCK_EXCLUSIVE) {
1188
 
                if (index->maildir_have_new)
1189
 
                        index->header->flags |= MAIL_INDEX_FLAG_MAILDIR_NEW;
1190
 
                else
1191
 
                        index->header->flags &= ~MAIL_INDEX_FLAG_MAILDIR_NEW;
1192
 
        }
1193
 
 
1194
 
        if (index->maildir_cur_dirty == 0 ||
1195
 
            index->maildir_cur_dirty < ioloop_time - MAILDIR_SYNC_SECS) {
1196
 
                if (cur_mtime >= ioloop_time - MAILDIR_SYNC_SECS)
1197
 
                        index->maildir_cur_dirty = cur_mtime;
1198
 
                else if (ctx->new_count == 0 || !ctx->new_mails_cur)
1199
 
                        index->maildir_cur_dirty = 0;
1200
 
                else {
1201
 
                        /* uidlist is locked, wait for a while before
1202
 
                           trying again */
1203
 
                        index->maildir_cur_dirty = ioloop_time;
1204
 
                }
1205
 
        }
1206
 
 
1207
 
        index->file_sync_stamp = cur_mtime;
1208
 
        if (ctx->new_dent == NULL &&
1209
 
            (ctx->new_count == 0 || !ctx->new_mails_new))
1210
 
                index->last_new_mtime = new_mtime;
1211
 
 
1212
 
        return TRUE;
1213
 
}
1214
 
 
1215
 
static int maildir_full_sync_finish_readonly(struct maildir_sync_context *ctx)
1216
 
{
1217
 
        struct mail_index *index = ctx->index;
1218
 
        struct mail_index_record *rec;
1219
 
        struct maildir_hash_rec *hash_rec;
1220
 
        void *orig_key, *orig_value;
1221
 
        const char *fname;
1222
 
        unsigned int seq;
1223
 
 
1224
 
        if (index->lock_type != MAIL_LOCK_EXCLUSIVE || !ctx->flag_updates)
1225
 
                return TRUE;
1226
 
 
1227
 
        rec = index->lookup(index, 1); seq = 1;
1228
 
        while (rec != NULL) {
1229
 
                fname = maildir_get_location(index, rec);
1230
 
                if (fname == NULL)
1231
 
                        return FALSE;
1232
 
 
1233
 
                if (hash_lookup_full(ctx->files, fname, &orig_key, &orig_value))
1234
 
                        hash_rec = orig_value;
1235
 
                else
1236
 
                        hash_rec = NULL;
1237
 
 
1238
 
                if (hash_rec != NULL &&
1239
 
                    ACTION(hash_rec) == MAILDIR_FILE_ACTION_UPDATE_FLAGS) {
1240
 
                        if (!maildir_update_filename(ctx, rec, orig_key))
1241
 
                                return FALSE;
1242
 
                        if (!maildir_update_flags(ctx, rec, seq, orig_key))
1243
 
                                return FALSE;
1244
 
                }
1245
 
 
1246
 
                rec = index->next(index, rec); seq++;
1247
 
        }
1248
 
 
1249
 
        return TRUE;
1250
 
}
1251
 
 
1252
 
static int maildir_index_sync_context_readonly(struct maildir_sync_context *ctx)
1253
 
{
1254
 
        struct mail_index *index = ctx->index;
1255
 
 
1256
 
        i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
1257
 
 
1258
 
        /* check only changes in file names. */
1259
 
 
1260
 
        /* if we can get exclusive lock, we can update the index
1261
 
           directly. but don't rely on it. */
1262
 
        (void)index->try_lock(index, MAIL_LOCK_EXCLUSIVE);
1263
 
 
1264
 
        if (!maildir_full_sync_init(ctx, FALSE) ||
1265
 
            !maildir_full_sync_dirs(ctx) ||
1266
 
            !maildir_full_sync_finish_readonly(ctx))
1267
 
                return FALSE;
1268
 
 
1269
 
        return TRUE;
1270
 
}
1271
 
 
1272
 
static void maildir_index_sync_deinit(struct maildir_sync_context *ctx)
1273
 
{
1274
 
        if (ctx->uidlist != NULL)
1275
 
                maildir_uidlist_close(ctx->uidlist);
1276
 
        if (ctx->files != NULL)
1277
 
                hash_destroy(ctx->files);
1278
 
        if (ctx->pool != NULL)
1279
 
                pool_unref(ctx->pool);
1280
 
 
1281
 
        if (ctx->new_dirp != NULL) {
1282
 
                if (closedir(ctx->new_dirp) < 0) {
1283
 
                        index_file_set_syscall_error(ctx->index, ctx->new_dir,
1284
 
                                                     "closedir()");
1285
 
                }
1286
 
        }
1287
 
 
1288
 
        maildir_uidlist_unlock(ctx->index);
1289
 
}
1290
 
 
1291
 
static struct maildir_sync_context *
1292
 
maildir_sync_context_new(struct mail_index *index)
1293
 
{
1294
 
        struct maildir_sync_context *ctx;
1295
 
 
1296
 
        if (index->new_filenames != NULL) {
1297
 
                hash_destroy(index->new_filenames);
1298
 
                index->new_filenames = NULL;
1299
 
        }
1300
 
 
1301
 
        if (index->new_filename_pool != NULL)
1302
 
                p_clear(index->new_filename_pool);
1303
 
 
1304
 
        ctx = t_new(struct maildir_sync_context, 1);
1305
 
        ctx->index = index;
1306
 
        ctx->new_dir = t_strconcat(index->mailbox_path, "/new", NULL);
1307
 
        ctx->cur_dir = t_strconcat(index->mailbox_path, "/cur", NULL);
1308
 
        return ctx;
1309
 
}
1310
 
 
1311
 
int maildir_index_sync_readonly(struct mail_index *index,
1312
 
                                const char *fname, int *found)
1313
 
{
1314
 
        struct maildir_sync_context *ctx;
1315
 
        struct maildir_hash_rec *hash_rec;
1316
 
        int ret;
1317
 
 
1318
 
        t_push();
1319
 
        /* fname may point into data file which syncing may break. */
1320
 
        fname = t_strdup(fname);
1321
 
 
1322
 
        ctx = maildir_sync_context_new(index);
1323
 
        ctx->readonly_check = TRUE;
1324
 
 
1325
 
        ret = maildir_index_sync_context_readonly(ctx);
1326
 
 
1327
 
        if (!ret)
1328
 
                *found = FALSE;
1329
 
        else {
1330
 
                hash_rec = hash_lookup(ctx->files, fname);
1331
 
                *found = hash_rec != NULL &&
1332
 
                        hash_rec->action != MAILDIR_FILE_ACTION_EXPUNGE;
1333
 
        }
1334
 
        maildir_index_sync_deinit(ctx);
1335
 
        t_pop();
1336
 
        return ret;
1337
 
}
1338
 
 
1339
 
int maildir_index_sync(struct mail_index *index, int minimal_sync,
1340
 
                       enum mail_lock_type data_lock_type __attr_unused__,
1341
 
                       int *changes)
1342
 
{
1343
 
        struct maildir_sync_context *ctx;
1344
 
        int ret;
1345
 
 
1346
 
        i_assert(index->lock_type != MAIL_LOCK_SHARED);
1347
 
 
1348
 
        if (changes != NULL)
1349
 
                *changes = FALSE;
1350
 
 
1351
 
        if (minimal_sync)
1352
 
                return TRUE;
1353
 
 
1354
 
        ctx = maildir_sync_context_new(index);
1355
 
        ret = maildir_index_sync_context(ctx, changes);
1356
 
        maildir_index_sync_deinit(ctx);
1357
 
        return ret;
1358
 
}