maildir-sync.c revision bde6382cf65fba6165dc3603f5419e194d87f404
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (C) 2004 Timo Sirainen */
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen/*
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Here's a description of how we handle Maildir synchronization and
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen it's problems:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen We want to be as efficient as we can. The most efficient way to
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen check if changes have occured is to stat() the new/ and cur/
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen directories and uidlist file - if their mtimes haven't changed,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen there's no changes and we don't need to do anything.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Problem 1: Multiple changes can happen within a single second -
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen nothing guarantees that once we synced it, someone else didn't just
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen then make a modification. Such modifications wouldn't get noticed
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen until a new modification occured later.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Problem 2: Syncing cur/ directory is much more costly than syncing
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen new/. Moving mails from new/ to cur/ will always change mtime of
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen cur/ causing us to sync it as well.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Problem 3: We may not be able to move mail from new/ to cur/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen because we're out of quota, or simply because we're accessing a
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen read-only mailbox.
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen MAILDIR_SYNC_SECS
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen -----------------
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
2303ad68175883aaebd1f3b18e69593c2422c7bbTimo Sirainen Several checks below use MAILDIR_SYNC_SECS, which should be maximum
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen clock drift between all computers accessing the maildir (eg. via
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen NFS), rounded up to next second. Our default is 1 second, since
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen everyone should be using NTP.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Note that setting it to 0 works only if there's only one computer
2303ad68175883aaebd1f3b18e69593c2422c7bbTimo Sirainen accessing the maildir. It's practically impossible to make two
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen clocks _exactly_ synchronized.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen It might be possible to only use file server's clock by looking at
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen the atime field, but I don't know how well that would actually work.
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen
2303ad68175883aaebd1f3b18e69593c2422c7bbTimo Sirainen cur directory
2303ad68175883aaebd1f3b18e69593c2422c7bbTimo Sirainen -------------
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen We have dirty_cur_time variable which is set to cur/ directory's
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen mtime when it's >= time() - MAILDIR_SYNC_SECS and we _think_ we have
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen synchronized the directory.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen When dirty_cur_time is non-zero, we don't synchronize the cur/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen directory until
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen a) cur/'s mtime changes
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen b) opening a mail fails with ENOENT
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen c) time() > dirty_cur_time + MAILDIR_SYNC_SECS
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen This allows us to modify the maildir multiple times without having
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen to sync it at every change. The sync will eventually be done to
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen make sure we didn't miss any external changes.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen The dirty_cur_time is set when:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - we change message flags
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - we expunge messages
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - we move mail from new/ to cur/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - we sync cur/ directory and it's mtime is >= time() - MAILDIR_SYNC_SECS
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen It's unset when we do the final syncing, ie. when mtime is
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen older than time() - MAILDIR_SYNC_SECS.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen new directory
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen -------------
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen If new/'s mtime is >= time() - MAILDIR_SYNC_SECS, always synchronize
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen it. dirty_cur_time-like feature might save us a few syncs, but
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen that might break a client which saves a mail in one connection and
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen tries to fetch it in another one. new/ directory is almost always
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen empty, so syncing it should be very fast anyway. Actually this can
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen still happen if we sync only new/ dir while another client is also
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen moving mails from it to cur/ - it takes us a while to see them.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen That's pretty unlikely to happen however, and only way to fix it
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen would be to always synchronize cur/ after new/.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Normally we move all mails from new/ to cur/ whenever we sync it. If
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen it's not possible for some reason, we mark the mail with "probably
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen exists in new/ directory" flag.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen If rename() still fails because of ENOSPC or EDQUOT, we still save
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen the flag changes in index with dirty-flag on. When moving the mail
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen to cur/ directory, or when we notice it's already moved there, we
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen apply the flag changes to the filename, rename it and remove the
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen dirty flag. If there's dirty flags, this should be tried every time
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen after expunge or when closing the mailbox.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen uidlist
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen -------
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen This file contains UID <-> filename mappings. It's updated only when
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen new mail arrives, so it may contain filenames that have already been
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen deleted. Updating is done by getting uidlist.lock file, writing the
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen whole uidlist into it and rename()ing it over the old uidlist. This
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen means there's no need to lock the file for reading.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Whenever uidlist is rewritten, it's mtime must be larger than the old
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen one's. Use utime() before rename() if needed. Note that inode checking
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen wouldn't have been sufficient as inode numbers can be reused.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen This file is usually read the first time you need to know filename for
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen given UID. After that it's not re-read unless new mails come that we
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen don't know about.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen broken clients
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen --------------
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Originally the middle identifier in Maildir filename was specified
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen only as <process id>_<delivery counter>. That however created a
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen problem with randomized PIDs which made it possible that the same
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen PID was reused within one second.
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen So if within one second a mail was delivered, MUA moved it to cur/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen and another mail was delivered by a new process using same PID as
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen the first one, we likely ended up overwriting the first mail when
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen the second mail was moved over it.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Nowadays everyone should be giving a bit more specific identifier,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen for example include microseconds in it which Dovecot does.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen There's a simple way to prevent this from happening in some cases:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Don't move the mail from new/ to cur/ if it's mtime is >= time() -
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen MAILDIR_SYNC_SECS. The second delivery's link() call then fails
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen because the file is already in new/, and it will then use a
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen different filename. There's a few problems with this however:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - it requires extra stat() call which is unneeded extra I/O
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - another MUA might still move the mail to cur/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen - if first file's flags are modified by either Dovecot or another
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen MUA, it's moved to cur/ (you _could_ just do the dirty-flagging
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen but that'd be ugly)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Because this is useful only for very few people and it requires
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen extra I/O, I decided not to implement this. It should be however
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen quite easy to do since we need to be able to deal with files in new/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen in any case.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen It's also possible to never accidentally overwrite a mail by using
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen link() + unlink() rather than rename(). This however isn't very
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen good idea as it introduces potential race conditions when multiple
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen clients are accessing the mailbox:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen Trying to move the same mail from new/ to cur/ at the same time:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen a) Client 1 uses slightly different filename than client 2,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen for example one sets read-flag on but the other doesn't.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen You have the same mail duplicated now.
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen b) Client 3 sees the mail between Client 1's and 2's link() calls
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen and changes it's flag. You have the same mail duplicated now.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen And it gets worse when they're unlink()ing in cur/ directory:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen c) Client 1 changes mails's flag and client 2 changes it back
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen between 1's link() and unlink(). The mail is now expunged.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen d) If you try to deal with the duplicates by unlink()ing another
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen one of them, you might end up unlinking both of them.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen So, what should we do then if we notice a duplicate? First of all,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen it might not be a duplicate at all, readdir() might have just
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen returned it twice because it was just renamed. What we should do is
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen create a completely new base name for it and rename() it to that.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen If the call fails with ENOENT, it only means that it wasn't a
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen duplicate after all.
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen*/
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "lib.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "ioloop.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "buffer.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "hash.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "str.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "maildir-storage.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include "maildir-uidlist.h"
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
67d86acc16b837a01d0967b65fc9a81ccf54ef0bTimo Sirainen#include <stdio.h>
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include <stddef.h>
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include <unistd.h>
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include <dirent.h>
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#include <sys/stat.h>
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#define MAILDIR_SYNC_SECS 1
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen#define MAILDIR_FILENAME_FLAG_FOUND 128
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainenstruct maildir_sync_context {
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct index_mailbox *ibox;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen const char *new_dir, *cur_dir;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen int partial;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen};
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainenstruct maildir_index_sync_context {
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct index_mailbox *ibox;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct mail_index_view *view;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct mail_index_sync_ctx *sync_ctx;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct mail_index_transaction *trans;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct mail_index_sync_rec sync_rec;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen uint32_t seq;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen int have_dirty, last_dirty;
0beef9bf818accfb629a92ef53ff0f6a15005941Timo Sirainen};
0beef9bf818accfb629a92ef53ff0f6a15005941Timo Sirainen
0beef9bf818accfb629a92ef53ff0f6a15005941Timo Sirainenstatic int maildir_expunge(struct index_mailbox *ibox, const char *path,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen void *context __attr_unused__)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen{
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen if (unlink(path) == 0) {
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ibox->dirty_cur_time = ioloop_time;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen return 1;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen }
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen if (errno == ENOENT)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen return 0;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen mail_storage_set_critical(ibox->box.storage,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen "unlink(%s) failed: %m", path);
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen return -1;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen}
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainenstatic int maildir_sync_flags(struct index_mailbox *ibox, const char *path,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen void *context)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen{
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct maildir_index_sync_context *ctx = context;
4338c7b02e15779efaee5cedd4a355c946d9d4c2Timo Sirainen const char *newpath;
4338c7b02e15779efaee5cedd4a355c946d9d4c2Timo Sirainen enum mail_flags flags;
4338c7b02e15779efaee5cedd4a355c946d9d4c2Timo Sirainen uint8_t flags8;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen keywords_mask_t keywords;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ctx->last_dirty = FALSE;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen (void)maildir_filename_get_flags(path, &flags, keywords);
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen flags8 = flags;
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen mail_index_sync_flags_apply(&ctx->sync_rec, &flags8, keywords);
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen newpath = maildir_filename_set_flags(path, flags8, keywords);
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen if (rename(path, newpath) == 0) {
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ibox->dirty_cur_time = ioloop_time;
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen return 1;
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen }
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen if (errno == ENOENT)
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen return 0;
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen if (ENOSPACE(errno) || errno == EACCES) {
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen memset(keywords, 0, sizeof(keywords));
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_ADD,
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen MAIL_INDEX_MAIL_FLAG_DIRTY, keywords);
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen ctx->last_dirty = TRUE;
deb06d37292d9112d74bdf80cfebb92ab5151679Timo Sirainen return 1;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen }
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen mail_storage_set_critical(ibox->box.storage,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen "rename(%s, %s) failed: %m", path, newpath);
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen return -1;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen}
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainenstatic int maildir_sync_record(struct index_mailbox *ibox,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct maildir_index_sync_context *ctx)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen{
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct mail_index_sync_rec *sync_rec = &ctx->sync_rec;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen struct mail_index_view *view = ctx->view;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen const struct mail_index_record *rec;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen uint32_t seq, seq1, seq2, uid;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen switch (sync_rec->type) {
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen case MAIL_INDEX_SYNC_TYPE_APPEND:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen break;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen /* make it go through sequences to avoid looping through huge
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen holes in UID range */
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen if (mail_index_lookup_uid_range(view, sync_rec->uid1,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen sync_rec->uid2,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen &seq1, &seq2) < 0)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen return -1;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen if (seq1 == 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen break;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen for (seq = seq1; seq <= seq2; seq++) {
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (mail_index_lookup_uid(view, seq, &uid) < 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return -1;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (maildir_file_do(ibox, uid, maildir_expunge,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen NULL) < 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return -1;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen }
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen break;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen case MAIL_INDEX_SYNC_TYPE_FLAGS:
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (mail_index_lookup_uid_range(view, sync_rec->uid1,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen sync_rec->uid2,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen &seq1, &seq2) < 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return -1;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (seq1 == 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen break;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen for (ctx->seq = seq1; ctx->seq <= seq2; ctx->seq++) {
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (mail_index_lookup_uid(view, ctx->seq, &uid) < 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return -1;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (maildir_file_do(ibox, uid,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen maildir_sync_flags, ctx) < 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return -1;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (!ctx->last_dirty) {
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen /* if this flag was dirty, drop it */
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (mail_index_lookup(view, ctx->seq, &rec) < 0)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return -1;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) {
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen keywords_mask_t keywords;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen memset(keywords, 0, sizeof(keywords));
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen mail_index_update_flags(ctx->trans,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen ctx->seq, MODIFY_REMOVE,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen MAIL_INDEX_MAIL_FLAG_DIRTY,
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen keywords);
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen }
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen }
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen }
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen break;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen }
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen return 0;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen}
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainenint maildir_sync_last_commit(struct index_mailbox *ibox)
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen{
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen struct maildir_index_sync_context ctx;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen const struct mail_index_header *hdr;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen uint32_t seq;
bde78a7bf5f9000f1ae4dc7ce6cabd012e1f8b79Pascal Volk uoff_t offset;
bde78a7bf5f9000f1ae4dc7ce6cabd012e1f8b79Pascal Volk int ret;
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen
acba68a69cdd6f3f00faa18cccef356d95048e46Timo Sirainen if (ibox->commit_log_file_seq == 0)
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen return 0;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen memset(&ctx, 0, sizeof(ctx));
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ctx.ibox = ibox;
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ret = mail_index_sync_begin(ibox->index, &ctx.sync_ctx, &ctx.view,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ibox->commit_log_file_seq,
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen ibox->commit_log_file_offset);
bf333c7645b8ddb6eedd6834db2fd908888793e1Timo Sirainen if (ret > 0) {
if (mail_index_get_header(ctx.view, &hdr) == 0 &&
(hdr->flags & MAIL_INDEX_HDR_FLAG_HAVE_DIRTY) != 0)
ctx.have_dirty = TRUE;
ctx.trans = mail_index_transaction_begin(ctx.view, FALSE);
while ((ret = mail_index_sync_next(ctx.sync_ctx,
&ctx.sync_rec)) > 0) {
if (maildir_sync_record(ibox, &ctx) < 0) {
ret = -1;
break;
}
}
if (mail_index_transaction_commit(ctx.trans, &seq, &offset) < 0)
ret = -1;
if (mail_index_sync_end(ctx.sync_ctx) < 0)
ret = -1;
}
if (ret == 0) {
ibox->commit_log_file_seq = 0;
ibox->commit_log_file_offset = 0;
} else {
mail_storage_set_index_error(ibox);
}
return ret;
}
static struct maildir_sync_context *
maildir_sync_context_new(struct index_mailbox *ibox)
{
struct maildir_sync_context *ctx;
ctx = t_new(struct maildir_sync_context, 1);
ctx->ibox = ibox;
ctx->new_dir = t_strconcat(ibox->path, "/new", NULL);
ctx->cur_dir = t_strconcat(ibox->path, "/cur", NULL);
return ctx;
}
static void maildir_sync_deinit(struct maildir_sync_context *ctx)
{
if (ctx->uidlist_sync_ctx != NULL)
(void)maildir_uidlist_sync_deinit(ctx->uidlist_sync_ctx);
}
static int maildir_fix_duplicate(struct index_mailbox *ibox, const char *dir,
const char *old_fname)
{
const char *new_fname, *old_path, *new_path;
int ret = 0;
t_push();
old_path = t_strconcat(dir, "/", old_fname, NULL);
new_fname = maildir_generate_tmp_filename(&ioloop_timeval);
new_path = t_strconcat(ibox->path, "/new/", new_fname, NULL);
if (rename(old_path, new_path) == 0) {
i_warning("Fixed duplicate in %s: %s -> %s",
ibox->path, old_fname, new_fname);
} else if (errno != ENOENT) {
mail_storage_set_critical(ibox->box.storage,
"rename(%s, %s) failed: %m", old_path, new_path);
ret = -1;
}
t_pop();
return ret;
}
static int maildir_scan_dir(struct maildir_sync_context *ctx, int new_dir)
{
struct mail_storage *storage = ctx->ibox->box.storage;
const char *dir;
DIR *dirp;
string_t *src, *dest;
struct dirent *dp;
enum maildir_uidlist_rec_flag flags;
int move_new, ret = 1;
src = t_str_new(1024);
dest = t_str_new(1024);
dir = new_dir ? ctx->new_dir : ctx->cur_dir;
dirp = opendir(dir);
if (dirp == NULL) {
mail_storage_set_critical(storage,
"opendir(%s) failed: %m", dir);
return -1;
}
move_new = new_dir && !mailbox_is_readonly(&ctx->ibox->box) &&
!ctx->ibox->keep_recent;
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_name[0] == '.')
continue;
ret = maildir_uidlist_sync_next_pre(ctx->uidlist_sync_ctx,
dp->d_name);
if (ret == 0) {
/* new file and we couldn't lock uidlist, check this
later in next sync. */
if (new_dir)
ctx->ibox->last_new_mtime = 0;
else
ctx->ibox->dirty_cur_time = ioloop_time;
continue;
}
if (ret < 0)
break;
flags = 0;
if (move_new) {
str_truncate(src, 0);
str_truncate(dest, 0);
str_printfa(src, "%s/%s", ctx->new_dir, dp->d_name);
str_printfa(dest, "%s/%s", ctx->cur_dir, dp->d_name);
if (strchr(dp->d_name, ':') == NULL)
str_append(dest, ":2,");
if (rename(str_c(src), str_c(dest)) == 0) {
/* we moved it - it's \Recent for us */
ctx->ibox->dirty_cur_time = ioloop_time;
flags |= MAILDIR_UIDLIST_REC_FLAG_MOVED |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
} else if (ENOTFOUND(errno)) {
/* someone else moved it already */
flags |= MAILDIR_UIDLIST_REC_FLAG_MOVED;
} else if (ENOSPACE(errno)) {
/* not enough disk space, leave here */
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
move_new = FALSE;
} else {
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
mail_storage_set_critical(storage,
"rename(%s, %s) failed: %m",
str_c(src), str_c(dest));
}
} else if (new_dir) {
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
}
ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
dp->d_name, flags);
if (ret <= 0) {
if (ret < 0)
break;
/* possibly duplicate - try fixing it */
if (maildir_fix_duplicate(ctx->ibox,
dir, dp->d_name) < 0) {
ret = -1;
break;
}
}
}
if (closedir(dirp) < 0) {
mail_storage_set_critical(storage,
"closedir(%s) failed: %m", dir);
}
return ret < 0 ? -1 : 0;
}
static int maildir_sync_quick_check(struct maildir_sync_context *ctx,
int *new_changed_r, int *cur_changed_r)
{
struct index_mailbox *ibox = ctx->ibox;
struct stat st;
time_t new_mtime, cur_mtime;
*new_changed_r = *cur_changed_r = FALSE;
if (stat(ctx->new_dir, &st) < 0) {
mail_storage_set_critical(ibox->box.storage,
"stat(%s) failed: %m", ctx->new_dir);
return -1;
}
new_mtime = st.st_mtime;
if (stat(ctx->cur_dir, &st) < 0) {
mail_storage_set_critical(ibox->box.storage,
"stat(%s) failed: %m", ctx->cur_dir);
return -1;
}
cur_mtime = st.st_mtime;
if (ibox->dirty_cur_time == 0) {
/* cur stamp is kept in index, we don't have to sync if
someone else has done it and updated the index. make sure
we have a fresh index with latest sync_stamp. */
struct mail_index_view *view;
const struct mail_index_header *hdr;
if (mail_index_refresh(ibox->index) < 0) {
mail_storage_set_index_error(ibox);
return -1;
}
view = mail_index_view_open(ibox->index);
if (mail_index_get_header(view, &hdr) < 0) {
mail_index_view_close(view);
mail_storage_set_index_error(ibox);
return -1;
}
ibox->last_cur_mtime = hdr->sync_stamp;
mail_index_view_close(view);
}
if (new_mtime != ibox->last_new_mtime ||
new_mtime >= ibox->last_new_sync_time - MAILDIR_SYNC_SECS) {
*new_changed_r = TRUE;
ibox->last_new_mtime = new_mtime;
ibox->last_new_sync_time = ioloop_time;
}
if (cur_mtime != ibox->last_cur_mtime ||
(ibox->dirty_cur_time != 0 &&
ioloop_time - ibox->dirty_cur_time > MAILDIR_SYNC_SECS)) {
/* cur/ changed, or delayed cur/ check */
*cur_changed_r = TRUE;
ibox->last_cur_mtime = cur_mtime;
ibox->dirty_cur_time =
cur_mtime >= ioloop_time - MAILDIR_SYNC_SECS ?
cur_mtime : 0;
}
return 0;
}
static int maildir_sync_index(struct maildir_sync_context *ctx)
{
struct index_mailbox *ibox = ctx->ibox;
struct maildir_index_sync_context sync_ctx;
struct maildir_uidlist_iter_ctx *iter;
struct mail_index_transaction *trans;
struct mail_index_view *view;
const struct mail_index_header *hdr;
const struct mail_index_record *rec;
uint32_t seq, uid;
enum maildir_uidlist_rec_flag uflags;
const char *filename;
enum mail_flags flags;
keywords_mask_t keywords;
uint32_t uid_validity, next_uid;
int ret;
memset(&sync_ctx, 0, sizeof(sync_ctx));
sync_ctx.ibox = ibox;
if (mail_index_sync_begin(ibox->index, &sync_ctx.sync_ctx, &view,
(uint32_t)-1, (uoff_t)-1) <= 0) {
mail_storage_set_index_error(ibox);
return -1;
}
sync_ctx.view = view;
ret = mail_index_get_header(view, &hdr);
i_assert(ret == 0); /* view is locked, can't happen */
uid_validity = maildir_uidlist_get_uid_validity(ibox->uidlist);
if (uid_validity != hdr->uid_validity &&
uid_validity != 0 && hdr->uid_validity != 0) {
/* uidvalidity changed and mailbox isn't being initialized,
index must be rebuilt */
mail_storage_set_critical(ibox->box.storage,
"Maildir sync: UIDVALIDITY changed (%u -> %u)",
hdr->uid_validity, uid_validity);
mail_index_mark_corrupted(ibox->index);
(void)mail_index_sync_end(sync_ctx.sync_ctx);
return -1;
}
trans = mail_index_transaction_begin(view, FALSE);
sync_ctx.trans = trans;
seq = 0;
iter = maildir_uidlist_iter_init(ibox->uidlist);
while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
maildir_filename_get_flags(filename, &flags, keywords);
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0 &&
(uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0 &&
(uflags & MAILDIR_UIDLIST_REC_FLAG_MOVED) == 0) {
/* mail is recent for next session as well */
flags |= MAIL_RECENT;
}
__again:
seq++;
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
continue;
}
if (seq > hdr->messages_count) {
if (uid < hdr->next_uid) {
/* most likely a race condition: we read the
maildir, then someone else expunged messages
and committed changes to index. so, this
message shouldn't actually exist. mark it
racy and check in next sync.
the difference between this and the later
check is that this one happens when messages
are expunged from the end */
if ((uflags &
MAILDIR_UIDLIST_REC_FLAG_RACING) != 0) {
mail_storage_set_critical(
ibox->box.storage,
"Maildir sync: UID < next_uid "
"(%u < %u, file = %s)",
uid, hdr->next_uid, filename);
mail_index_mark_corrupted(ibox->index);
ret = -1;
break;
}
ibox->dirty_cur_time = ioloop_time;
maildir_uidlist_add_flags(ibox->uidlist,
filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
seq--;
continue;
}
mail_index_append(trans, uid, &seq);
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
flags, keywords);
continue;
}
if (mail_index_lookup(view, seq, &rec) < 0) {
ret = -1;
break;
}
if (rec->uid < uid) {
/* expunged */
mail_index_expunge(trans, seq);
goto __again;
}
if (rec->uid > uid) {
/* most likely a race condition: we read the
maildir, then someone else expunged messages and
committed changes to index. so, this message
shouldn't actually exist. mark it racy and check
in next sync. */
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) != 0) {
mail_storage_set_critical(ibox->box.storage,
"Maildir sync: UID inserted in the "
"middle of mailbox "
"(%u > %u, file = %s)",
rec->uid, uid, filename);
mail_index_mark_corrupted(ibox->index);
ret = -1;
break;
}
ibox->dirty_cur_time = ioloop_time;
maildir_uidlist_add_flags(ibox->uidlist, filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
seq--;
continue;
}
if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
/* we haven't been able to update maildir with this
record's flag changes. don't sync them. */
continue;
}
if (((uint8_t)flags & ~MAIL_RECENT) !=
(rec->flags & (MAIL_FLAGS_MASK^MAIL_RECENT)) ||
memcmp(keywords, rec->keywords,
INDEX_KEYWORDS_BYTE_COUNT) != 0) {
/* FIXME: this is wrong if there's pending changes in
transaction log already. it gets fixed in next sync
however.. */
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
flags, keywords);
} else if ((flags & MAIL_RECENT) == 0 &&
(rec->flags & MAIL_RECENT) != 0) {
/* just remove recent flag */
memset(keywords, 0, sizeof(keywords));
mail_index_update_flags(trans, seq, MODIFY_REMOVE,
MAIL_RECENT, keywords);
}
}
maildir_uidlist_iter_deinit(iter);
if (!ctx->partial) {
/* expunge the rest */
for (seq++; seq <= hdr->messages_count; seq++)
mail_index_expunge(trans, seq);
}
/* now, sync the index */
while ((ret = mail_index_sync_next(sync_ctx.sync_ctx,
&sync_ctx.sync_rec)) > 0) {
if (maildir_sync_record(ibox, &sync_ctx) < 0) {
ret = -1;
break;
}
}
if (ibox->dirty_cur_time == 0 &&
ibox->last_cur_mtime != (time_t)hdr->sync_stamp) {
uint32_t sync_stamp = ibox->last_cur_mtime;
mail_index_update_header(trans,
offsetof(struct mail_index_header, sync_stamp),
&sync_stamp, sizeof(sync_stamp));
}
if (hdr->uid_validity == 0) {
/* get the initial uidvalidity */
if (maildir_uidlist_update(ibox->uidlist) < 0)
ret = -1;
uid_validity = maildir_uidlist_get_uid_validity(ibox->uidlist);
if (uid_validity == 0) {
uid_validity = ioloop_time;
maildir_uidlist_set_uid_validity(ibox->uidlist,
uid_validity);
}
} else if (uid_validity == 0) {
maildir_uidlist_set_uid_validity(ibox->uidlist,
hdr->uid_validity);
}
if (uid_validity != hdr->uid_validity && uid_validity != 0) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, uid_validity),
&uid_validity, sizeof(uid_validity));
}
next_uid = maildir_uidlist_get_next_uid(ibox->uidlist);
if (next_uid != 0 && hdr->next_uid != next_uid) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, next_uid),
&next_uid, sizeof(next_uid));
}
if (ret < 0)
mail_index_transaction_rollback(trans);
else {
uint32_t seq;
uoff_t offset;
if (mail_index_transaction_commit(trans, &seq, &offset) < 0)
ret = -1;
else if (seq != 0) {
ibox->commit_log_file_seq = seq;
ibox->commit_log_file_offset = offset;
}
}
if (mail_index_sync_end(sync_ctx.sync_ctx) < 0)
ret = -1;
if (ret == 0) {
ibox->commit_log_file_seq = 0;
ibox->commit_log_file_offset = 0;
} else {
mail_storage_set_index_error(ibox);
}
return ret;
}
static int maildir_sync_context(struct maildir_sync_context *ctx)
{
int ret, new_changed, cur_changed;
if (maildir_sync_quick_check(ctx, &new_changed, &cur_changed) < 0)
return -1;
if (!new_changed && !cur_changed)
return 0;
ctx->partial = !cur_changed;
ctx->uidlist_sync_ctx =
maildir_uidlist_sync_init(ctx->ibox->uidlist, ctx->partial);
if (maildir_scan_dir(ctx, TRUE) < 0)
return -1;
if (cur_changed) {
if (maildir_scan_dir(ctx, FALSE) < 0)
return -1;
}
/* finish uidlist syncing, but keep it still locked */
if (maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx)) {
if (maildir_sync_index(ctx) < 0)
return -1;
}
ret = maildir_uidlist_sync_deinit(ctx->uidlist_sync_ctx);
ctx->uidlist_sync_ctx = NULL;
return ret;
}
static int maildir_sync_context_readonly(struct maildir_sync_context *ctx)
{
int ret;
ctx->uidlist_sync_ctx =
maildir_uidlist_sync_init(ctx->ibox->uidlist, FALSE);
if (maildir_scan_dir(ctx, TRUE) < 0)
return -1;
if (maildir_scan_dir(ctx, FALSE) < 0)
return -1;
ret = maildir_uidlist_sync_deinit(ctx->uidlist_sync_ctx);
ctx->uidlist_sync_ctx = NULL;
return ret;
}
int maildir_storage_sync_readonly(struct index_mailbox *ibox)
{
struct maildir_sync_context *ctx;
int ret;
ctx = maildir_sync_context_new(ibox);
ret = maildir_sync_context_readonly(ctx);
maildir_sync_deinit(ctx);
return ret;
}
int maildir_storage_sync(struct mailbox *box, enum mailbox_sync_flags flags)
{
struct index_mailbox *ibox = (struct index_mailbox *)box;
struct maildir_sync_context *ctx;
int ret;
if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 ||
ibox->sync_last_check + MAILBOX_FULL_SYNC_INTERVAL <= ioloop_time) {
ibox->sync_last_check = ioloop_time;
ctx = maildir_sync_context_new(ibox);
ret = maildir_sync_context(ctx);
maildir_sync_deinit(ctx);
if (ret < 0)
return -1;
}
return index_storage_sync(box, flags);
}