maildir-sync.c revision 9ef0f38972e7f42ff8d1795f47f6a3e6d621be9c
e59faf65ce864fe95dc00f5d52b8323cdbd0608aTimo Sirainen/* Copyright (c) 2004-2009 Dovecot authors, see the included COPYING file */
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen Here's a description of how we handle Maildir synchronization and
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen it's problems:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen We want to be as efficient as we can. The most efficient way to
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen check if changes have occurred is to stat() the new/ and cur/
0371406d952fe51367c7be91703e5634b7d9d225Timo Sirainen directories and uidlist file - if their mtimes haven't changed,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen there's no changes and we don't need to do anything.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Problem 1: Multiple changes can happen within a single second -
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen nothing guarantees that once we synced it, someone else didn't just
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen then make a modification. Such modifications wouldn't get noticed
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen until a new modification occurred later.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Problem 2: Syncing cur/ directory is much more costly than syncing
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen new/. Moving mails from new/ to cur/ will always change mtime of
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen cur/ causing us to sync it as well.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Problem 3: We may not be able to move mail from new/ to cur/
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen because we're out of quota, or simply because we're accessing a
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen read-only mailbox.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen MAILDIR_SYNC_SECS
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen -----------------
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen Several checks below use MAILDIR_SYNC_SECS, which should be maximum
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen clock drift between all computers accessing the maildir (eg. via
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen NFS), rounded up to next second. Our default is 1 second, since
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen everyone should be using NTP.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Note that setting it to 0 works only if there's only one computer
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen accessing the maildir. It's practically impossible to make two
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen clocks _exactly_ synchronized.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen It might be possible to only use file server's clock by looking at
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen the atime field, but I don't know how well that would actually work.
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen cur directory
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen -------------
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen We have dirty_cur_time variable which is set to cur/ directory's
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen mtime when it's >= time() - MAILDIR_SYNC_SECS and we _think_ we have
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen synchronized the directory.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen When dirty_cur_time is non-zero, we don't synchronize the cur/
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen directory until
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen a) cur/'s mtime changes
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen b) opening a mail fails with ENOENT
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen c) time() > dirty_cur_time + MAILDIR_SYNC_SECS
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen This allows us to modify the maildir multiple times without having
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen to sync it at every change. The sync will eventually be done to
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen make sure we didn't miss any external changes.
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen The dirty_cur_time is set when:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - we change message flags
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - we expunge messages
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - we move mail from new/ to cur/
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - we sync cur/ directory and it's mtime is >= time() - MAILDIR_SYNC_SECS
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen It's unset when we do the final syncing, ie. when mtime is
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen older than time() - MAILDIR_SYNC_SECS.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen new directory
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen -------------
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen If new/'s mtime is >= time() - MAILDIR_SYNC_SECS, always synchronize
9c2b0eb659540b9db8dd3a8a6a2515921fbe8eebTimo Sirainen it. dirty_cur_time-like feature might save us a few syncs, but
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen that might break a client which saves a mail in one connection and
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen tries to fetch it in another one. new/ directory is almost always
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen empty, so syncing it should be very fast anyway. Actually this can
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen still happen if we sync only new/ dir while another client is also
892b3cbf0eba9ba455448adcf71864a409345c6dTimo Sirainen moving mails from it to cur/ - it takes us a while to see them.
892b3cbf0eba9ba455448adcf71864a409345c6dTimo Sirainen That's pretty unlikely to happen however, and only way to fix it
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen would be to always synchronize cur/ after new/.
f4c0b1874b0533bcf2df1d28d584ff02cfdae3faTimo Sirainen Normally we move all mails from new/ to cur/ whenever we sync it. If
f4c0b1874b0533bcf2df1d28d584ff02cfdae3faTimo Sirainen it's not possible for some reason, we mark the mail with "probably
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen exists in new/ directory" flag.
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen If rename() still fails because of ENOSPC or EDQUOT, we still save
892b3cbf0eba9ba455448adcf71864a409345c6dTimo Sirainen the flag changes in index with dirty-flag on. When moving the mail
892b3cbf0eba9ba455448adcf71864a409345c6dTimo Sirainen to cur/ directory, or when we notice it's already moved there, we
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen apply the flag changes to the filename, rename it and remove the
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen dirty flag. If there's dirty flags, this should be tried every time
892b3cbf0eba9ba455448adcf71864a409345c6dTimo Sirainen after expunge or when closing the mailbox.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen This file contains UID <-> filename mappings. It's updated only when
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen new mail arrives, so it may contain filenames that have already been
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen deleted. Updating is done by getting uidlist.lock file, writing the
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen whole uidlist into it and rename()ing it over the old uidlist. This
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen means there's no need to lock the file for reading.
d16b506f5540e3407d256bda35624b38a5ecf88fTimo Sirainen Whenever uidlist is rewritten, it's mtime must be larger than the old
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen one's. Use utime() before rename() if needed. Note that inode checking
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen wouldn't have been sufficient as inode numbers can be reused.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen This file is usually read the first time you need to know filename for
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen given UID. After that it's not re-read unless new mails come that we
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen don't know about.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen broken clients
a54fa00087ba926a3d966a8449d8d7579e89911cTimo Sirainen --------------
a54fa00087ba926a3d966a8449d8d7579e89911cTimo Sirainen Originally the middle identifier in Maildir filename was specified
a54fa00087ba926a3d966a8449d8d7579e89911cTimo Sirainen only as <process id>_<delivery counter>. That however created a
a54fa00087ba926a3d966a8449d8d7579e89911cTimo Sirainen problem with randomized PIDs which made it possible that the same
a54fa00087ba926a3d966a8449d8d7579e89911cTimo Sirainen PID was reused within one second.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen So if within one second a mail was delivered, MUA moved it to cur/
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen and another mail was delivered by a new process using same PID as
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen the first one, we likely ended up overwriting the first mail when
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen the second mail was moved over it.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Nowadays everyone should be giving a bit more specific identifier,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for example include microseconds in it which Dovecot does.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen There's a simple way to prevent this from happening in some cases:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Don't move the mail from new/ to cur/ if it's mtime is >= time() -
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen MAILDIR_SYNC_SECS. The second delivery's link() call then fails
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen because the file is already in new/, and it will then use a
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen different filename. There's a few problems with this however:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - it requires extra stat() call which is unneeded extra I/O
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - another MUA might still move the mail to cur/
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen - if first file's flags are modified by either Dovecot or another
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen MUA, it's moved to cur/ (you _could_ just do the dirty-flagging
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen but that'd be ugly)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Because this is useful only for very few people and it requires
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen extra I/O, I decided not to implement this. It should be however
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen quite easy to do since we need to be able to deal with files in new/
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen It's also possible to never accidentally overwrite a mail by using
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen link() + unlink() rather than rename(). This however isn't very
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen good idea as it introduces potential race conditions when multiple
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen clients are accessing the mailbox:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Trying to move the same mail from new/ to cur/ at the same time:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen a) Client 1 uses slightly different filename than client 2,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for example one sets read-flag on but the other doesn't.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen You have the same mail duplicated now.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen b) Client 3 sees the mail between Client 1's and 2's link() calls
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen and changes it's flag. You have the same mail duplicated now.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen And it gets worse when they're unlink()ing in cur/ directory:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen c) Client 1 changes mails's flag and client 2 changes it back
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen between 1's link() and unlink(). The mail is now expunged.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen d) If you try to deal with the duplicates by unlink()ing another
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen one of them, you might end up unlinking both of them.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen So, what should we do then if we notice a duplicate? First of all,
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen it might not be a duplicate at all, readdir() might have just
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen returned it twice because it was just renamed. What we should do is
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen create a completely new base name for it and rename() it to that.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen If the call fails with ENOENT, it only means that it wasn't a
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen duplicate after all.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen/* When rename()ing many files from new/ to cur/, it's possible that next
a0b89f3b1df99b3a32f44623f13ad1893118825bTimo Sirainen readdir() skips some files. we don't of course wish to lose them, so we
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen go and rescan the new/ directory again from beginning until no files are
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen left. This value is just an optimization to avoid checking the directory
e9a0dc3e1e63f0f3affbcea7a0f5ffc112f34427Timo Sirainen twice unneededly. usually only NFS is the problem case. 1 is the safest
e9a0dc3e1e63f0f3affbcea7a0f5ffc112f34427Timo Sirainen bet here, but I guess 5 will do just fine too. */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen/* This is mostly to avoid infinite looping when rename() destination already
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen exists as the hard link of the file itself. */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct maildir_index_sync_context *index_sync_ctx;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenvoid maildir_sync_notify(struct maildir_sync_context *ctx)
d16b506f5540e3407d256bda35624b38a5ecf88fTimo Sirainen /* we got here from maildir-save.c. it has no
d16b506f5540e3407d256bda35624b38a5ecf88fTimo Sirainen maildir_sync_context, */
d16b506f5540e3407d256bda35624b38a5ecf88fTimo Sirainen if (now - ctx->last_touch > MAILDIR_LOCK_TOUCH_SECS && ctx->locked) {
d16b506f5540e3407d256bda35624b38a5ecf88fTimo Sirainen (void)maildir_uidlist_lock_touch(ctx->mbox->uidlist);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (now - ctx->last_notify > MAIL_STORAGE_STAYALIVE_SECS) {
3e28b527dd6048a40684afd29cff0ee008fc0014Timo Sirainen if (box->storage->callbacks->notify_ok != NULL) {
2cfe9983ce7a6280636ee12beccc2e865111967bTimo Sirainenmaildir_sync_context_new(struct maildir_mailbox *mbox,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->new_dir = t_strconcat(mbox->path, "/new", NULL);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->cur_dir = t_strconcat(mbox->path, "/cur", NULL);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void maildir_sync_deinit(struct maildir_sync_context *ctx)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen (void)maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen (void)maildir_sync_index_finish(&ctx->index_sync_ctx,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenstatic int maildir_fix_duplicate(struct maildir_sync_context *ctx,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen fname1 = maildir_uidlist_sync_get_full_filename(ctx->uidlist_sync_ctx,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (stat(path1, &st1) < 0 || stat(path2, &st2) < 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* most likely the files just don't exist anymore.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen don't really care about other errors much. */
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen /* Files are the same. this means either a race condition
e20e638805c4bd54e039891a3e92760b1dfa189aTimo Sirainen between stat() calls, or that the files were link()ed. */
e20e638805c4bd54e039891a3e92760b1dfa189aTimo Sirainen if (st1.st_nlink > 1 && st2.st_nlink == st1.st_nlink &&
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen st1.st_ctime < ioloop_time - DUPE_LINKS_DELETE_SECS) {
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen /* The file has hard links and it hasn't had any
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen changes (such as renames) for a while, so this
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen isn't a race condition.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen rename()ing one file on top of the other would fix
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen this safely, except POSIX decided that rename()
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen doesn't work that way. So we'll have unlink() one
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen and hope that another process didn't just decide to
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unlink() the other (uidlist lock prevents this from
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen happening) */
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen new_path = t_strconcat(ctx->mbox->path, "/new/", new_fname, NULL);
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen i_warning("Fixed a duplicate: %s -> %s", path2, new_fname);
19e8adccba16ff419f5675b1575358c2956dce83Timo Sirainen mail_storage_set_critical(&ctx->mbox->storage->storage,
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen "Couldn't fix a duplicate: rename(%s, %s) failed: %m",
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainenmaildir_stat(struct maildir_mailbox *mbox, const char *path, struct stat *st_r)
e958a3c4573058f17999f0083a34080ca35e34d8Timo Sirainen for (i = 0;; i++) {
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT)
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen /* try again */
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen mail_storage_set_critical(mbox->ibox.box.storage,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic int maildir_scan_dir(struct maildir_sync_context *ctx, bool new_dir)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int i = 0, move_count = 0;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen bool move_new, check_touch, dir_changed = FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for (i = 0;; i++) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* try again */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->mbox->maildir_hdr.new_mtime = st.st_mtime;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->mbox->maildir_hdr.new_mtime_nsecs = ST_MTIME_NSEC(st);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->mbox->maildir_hdr.cur_mtime = st.st_mtime;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->mbox->maildir_hdr.cur_mtime_nsecs = ST_MTIME_NSEC(st);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen move_new = new_dir && !mailbox_is_readonly(&ctx->mbox->ibox.box) &&
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for (; (dp = readdir(dirp)) != NULL; errno = 0) {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen str_printfa(src, "%s/%s", ctx->new_dir, dp->d_name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen str_printfa(dest, "%s/%s", ctx->cur_dir, dp->d_name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (strchr(dp->d_name, MAILDIR_INFO_SEP) == NULL) {
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen /* we moved it - it's \Recent for us */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* someone else moved it already */
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen } else if (ENOSPACE(errno) || errno == EACCES) {
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen /* not enough disk space / read-only maildir,
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen leave here */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "rename(%s, %s) failed: %m",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if ((move_count % MAILDIR_SLOW_MOVE_COUNT) == 0)
61dca057fe86fd5ae57f5106f8f049b7287d78cdTimo Sirainen } else if (new_dir) {
1412a091183dc0e5d6ea4f403a5cd4f4cd5c7301Timo Sirainen ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
1412a091183dc0e5d6ea4f403a5cd4f4cd5c7301Timo Sirainen /* possibly duplicate - try fixing it */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen (move_count <= MAILDIR_RENAME_RESCAN_COUNT ? 0 : 1);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenint maildir_sync_header_refresh(struct maildir_mailbox *mbox)
dd171dccbe98fc63ca737e6e4e8edbeb601e5cbdTimo Sirainen if (mail_index_refresh(mbox->ibox.index) < 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen mail_index_get_header_ext(mbox->ibox.view, mbox->maildir_ext_id,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* doesn't exist */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic int maildir_sync_quick_check(struct maildir_mailbox *mbox, bool undirty,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen (hdr)->name ## _mtime + MAILDIR_SYNC_SECS && \
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen (time_t)(hdr)->name ## _check_time < ioloop_time - MAILDIR_SYNC_SECS))
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ((st).st_mtime != (time_t)(hdr)->name ## _mtime || \
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen !ST_NTIMES_EQUAL(ST_MTIME_NSEC(st), (hdr)->name ## _mtime_nsecs))
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct maildir_index_header *hdr = &mbox->maildir_hdr;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen bool refreshed = FALSE, check_new = FALSE, check_cur = FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* first sync */
24ce0c343cefe54af841871fa39dbc3464028b06Timo Sirainen /* try to avoid stat()ing by first checking delayed changes */
9fcf7b79236b0045f7709718f7b65ada516565e7Timo Sirainen /* refresh index and try again */
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen *new_changed_r = DIR_MTIME_CHANGED(new_st, hdr, new);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen *cur_changed_r = DIR_MTIME_CHANGED(cur_st, hdr, cur);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if ((!*new_changed_r && !*cur_changed_r) || refreshed)
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen /* refresh index and try again */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void maildir_sync_update_next_uid(struct maildir_mailbox *mbox)
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen next_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (uid_validity == hdr->uid_validity || uid_validity == 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* make sure uidlist's next_uid is at least as large as
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen index file's. typically this happens only if uidlist gets
68f0dfb4b2815ecbc1bd8d8a68adcfd577ec55aeTimo Sirainen maildir_uidlist_set_uid_validity(mbox->uidlist,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenstatic bool move_recent_messages(struct maildir_sync_context *ctx)
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen (void)maildir_uidlist_refresh(ctx->mbox->uidlist);
06f537a8e0b399222cc2a7755015ef3963525fd2Timo Sirainen /* if there are files in new/, we'll need to move them. we'll check
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen this by checking if we have any recent messages */
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen hdr = mail_index_get_header(ctx->mbox->ibox.view);
5fe06fea9fee0f5e4e9cb49f6866877223f78b85Timo Sirainen maildir_uidlist_get_next_uid(ctx->mbox->uidlist);
5fe06fea9fee0f5e4e9cb49f6866877223f78b85Timo Sirainenstatic int maildir_sync_get_changes(struct maildir_sync_context *ctx,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen bool undirty = (ctx->flags & MAILBOX_SYNC_FLAG_FULL_READ) != 0;
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen if (maildir_sync_quick_check(ctx->mbox, undirty,
840a3701b7a0f7fadd17738998c33790a8dfad2dTimo Sirainen return mail_index_sync_have_any(ctx->mbox->ibox.index, flags) ? 1 : 0;
840a3701b7a0f7fadd17738998c33790a8dfad2dTimo Sirainenstatic int maildir_sync_context(struct maildir_sync_context *ctx, bool forced,
06f537a8e0b399222cc2a7755015ef3963525fd2Timo Sirainen ret = maildir_sync_get_changes(ctx, &new_changed, &cur_changed);
06f537a8e0b399222cc2a7755015ef3963525fd2Timo Sirainen Locking, locking, locking.. Wasn't maildir supposed to be lockless?
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen We can get here either as beginning a real maildir sync, or when
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen committing changes to maildir but a file was lost (maybe renamed).
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen So, we're going to need two locks. One for index and one for
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen uidlist. To avoid deadlocking do the uidlist lock always first.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen uidlist is needed only for figuring out UIDs for newly seen files,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen so theoretically we wouldn't need to lock it unless there are new
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen files. It has a few problems though, assuming the index lock didn't
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen already protect it (eg. in-memory indexes):
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen 1. Just because you see a new file which doesn't exist in uidlist
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen file, doesn't mean that the file really exists anymore, or that
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen your readdir() lists all new files. Meaning that this is possible:
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen A: opendir(), readdir() -> new file ...
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen -- new files are written to the maildir --
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen B: opendir(), readdir() -> new file, lock uidlist,
26cdaf7097427fa90343260fa236af12ab93cca3Timo Sirainen readdir() -> another new file, rewrite uidlist, unlock
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen A: ... lock uidlist, readdir() -> nothing left, rewrite uidlist,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen The second time running A didn't see the two new files. To handle
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen this correctly, it must not remove the new unseen files from
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen uidlist. This is possible to do, but adds extra complexity.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen 2. If another process is rename()ing files while we are
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen readdir()ing, it's possible that readdir() never lists some files,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen causing Dovecot to assume they were expunged. In next sync they
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen would show up again, but client could have already been notified of
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen that and they would show up under new UIDs, so the damage is
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen already done.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Both of the problems can be avoided if we simply lock the uidlist
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen before syncing and keep it until sync is finished. Typically this
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen would happen in any case, as there is the index lock..
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen The second case is still a problem with external changes though,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen because maildir doesn't require any kind of locking. Luckily this
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen problem rarely happens except under high amount of modifications.
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if ((ctx->flags & MAILBOX_SYNC_FLAG_FAST) != 0)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ret = maildir_uidlist_sync_init(ctx->mbox->uidlist, sync_flags,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mail_storage *storage = ctx->mbox->ibox.box.storage;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* timeout */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* locking failed. sync anyway without locking so that it's
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen possible to expunge messages when out of quota. */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* we're already forcing a sync, we're trying to find
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen a message that was probably already expunged, don't
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen loop for a long time trying to find it. */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ret = maildir_uidlist_sync_init(ctx->mbox->uidlist, sync_flags |
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen storage->callbacks->notify_no(&ctx->mbox->ibox.box,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "Internal mailbox synchronization failure, "
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "showing only old mails.",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx->locked = maildir_uidlist_is_locked(ctx->mbox->uidlist);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (!ctx->mbox->syncing_commit && (ctx->locked || lock_failure)) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* if we're going to check cur/ dir our current logic requires
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen that new/ dir is checked as well. it's a good idea anyway. */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int count = 0;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen while ((ret = maildir_scan_dir(ctx, TRUE)) > 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* rename()d at least some files, which might have
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen caused some other files to be missed. check again
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen (see MAILDIR_RENAME_RESCAN_COUNT). */
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen /* finish uidlist syncing, but keep it still locked */
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen /* make sure we sync the maildir later */
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen /* NOTE: index syncing here might cause a re-sync due to
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen files getting lost, so this function might be called
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen re-entrantly. */
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen ret = maildir_sync_index(ctx->index_sync_ctx, ctx->partial);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (maildir_sync_index_finish(&ctx->index_sync_ctx,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen i_assert(maildir_uidlist_is_locked(ctx->mbox->uidlist) ||
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (maildir_uidlist_lookup_nosync(ctx->mbox->uidlist, *find_uid,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* UID is expunged */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen } else if ((flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) == 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* we didn't find it, possibly expunged? */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenint maildir_storage_sync_force(struct maildir_mailbox *mbox, uint32_t uid)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ctx = maildir_sync_context_new(mbox, MAILBOX_SYNC_FLAG_FAST);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen ret = maildir_sync_context(ctx, TRUE, &uid, &lost_files);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen /* maybe it's expunged. check again. */
9672bb2a11c37c275d695451accd824da5c9e485Timo Sirainenmaildir_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen if (index_mailbox_want_full_sync(&mbox->ibox, flags)) {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen i_assert(!maildir_uidlist_is_locked(mbox->uidlist) ||
9c2b0eb659540b9db8dd3a8a6a2515921fbe8eebTimo Sirainen /* lost some files from new/, see if thery're in cur/ */
9c2b0eb659540b9db8dd3a8a6a2515921fbe8eebTimo Sirainen return index_mailbox_sync_init(box, flags, ret < 0);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenint maildir_sync_is_synced(struct maildir_mailbox *mbox)
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen new_dir = t_strconcat(mbox->path, "/new", NULL);
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen cur_dir = t_strconcat(mbox->path, "/cur", NULL);
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen ret = maildir_sync_quick_check(mbox, FALSE, new_dir, cur_dir,
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen return ret < 0 ? -1 : (!new_changed && !cur_changed);