maildir-sync.c revision 0ed9ccd0047f75df54a49bc117ca301eb398e447
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* Copyright (c) 2004-2010 Dovecot authors, see the included COPYING file */
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen Here's a description of how we handle Maildir synchronization and
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen it's problems:
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen We want to be as efficient as we can. The most efficient way to
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen check if changes have occurred is to stat() the new/ and cur/
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen directories and uidlist file - if their mtimes haven't changed,
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen there's no changes and we don't need to do anything.
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen Problem 1: Multiple changes can happen within a single second -
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen nothing guarantees that once we synced it, someone else didn't just
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen then make a modification. Such modifications wouldn't get noticed
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen until a new modification occurred later.
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen Problem 2: Syncing cur/ directory is much more costly than syncing
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen new/. Moving mails from new/ to cur/ will always change mtime of
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen cur/ causing us to sync it as well.
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen Problem 3: We may not be able to move mail from new/ to cur/
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen because we're out of quota, or simply because we're accessing a
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen read-only mailbox.
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen MAILDIR_SYNC_SECS
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen -----------------
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Several checks below use MAILDIR_SYNC_SECS, which should be maximum
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen clock drift between all computers accessing the maildir (eg. via
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen NFS), rounded up to next second. Our default is 1 second, since
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen everyone should be using NTP.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Note that setting it to 0 works only if there's only one computer
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen accessing the maildir. It's practically impossible to make two
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen clocks _exactly_ synchronized.
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainen It might be possible to only use file server's clock by looking at
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen the atime field, but I don't know how well that would actually work.
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen cur directory
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen -------------
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen We have dirty_cur_time variable which is set to cur/ directory's
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen mtime when it's >= time() - MAILDIR_SYNC_SECS and we _think_ we have
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen synchronized the directory.
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen When dirty_cur_time is non-zero, we don't synchronize the cur/
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen directory until
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen a) cur/'s mtime changes
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen b) opening a mail fails with ENOENT
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen c) time() > dirty_cur_time + MAILDIR_SYNC_SECS
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen This allows us to modify the maildir multiple times without having
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen to sync it at every change. The sync will eventually be done to
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen make sure we didn't miss any external changes.
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen The dirty_cur_time is set when:
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen - we change message flags
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen - we expunge messages
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen - we move mail from new/ to cur/
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen - we sync cur/ directory and it's mtime is >= time() - MAILDIR_SYNC_SECS
687bb904e1bb76c21a6e392f60c990486b298ea4Timo Sirainen It's unset when we do the final syncing, ie. when mtime is
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen older than time() - MAILDIR_SYNC_SECS.
7c95b03620a03a43dd72d39608cea5fc77393ad6Timo Sirainen new directory
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen -------------
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen If new/'s mtime is >= time() - MAILDIR_SYNC_SECS, always synchronize
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen it. dirty_cur_time-like feature might save us a few syncs, but
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen that might break a client which saves a mail in one connection and
73e7998716853b5b7621c06aea0022dccda70ad1Timo Sirainen tries to fetch it in another one. new/ directory is almost always
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen empty, so syncing it should be very fast anyway. Actually this can
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen still happen if we sync only new/ dir while another client is also
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen moving mails from it to cur/ - it takes us a while to see them.
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen That's pretty unlikely to happen however, and only way to fix it
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen would be to always synchronize cur/ after new/.
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen Normally we move all mails from new/ to cur/ whenever we sync it. If
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen it's not possible for some reason, we mark the mail with "probably
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen exists in new/ directory" flag.
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen If rename() still fails because of ENOSPC or EDQUOT, we still save
985fa802913c96ce6f2e25bbc788ee39c416a7e0Timo Sirainen the flag changes in index with dirty-flag on. When moving the mail
985fa802913c96ce6f2e25bbc788ee39c416a7e0Timo Sirainen to cur/ directory, or when we notice it's already moved there, we
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen apply the flag changes to the filename, rename it and remove the
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen dirty flag. If there's dirty flags, this should be tried every time
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen after expunge or when closing the mailbox.
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "buffer.h"
#include "hash.h"
#include "str.h"
#include "eacces-error.h"
#include "nfs-workarounds.h"
#include "maildir-storage.h"
#include "maildir-uidlist.h"
#include "maildir-filename.h"
#include "maildir-sync.h"
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
struct maildir_sync_context {
/* we got here from maildir-save.c. it has no
static struct maildir_sync_context *
return ctx;
fname2);
const char *path;
#ifdef HAVE_DIRFD
if (new_dir) {
errno = 0;
flags = 0;
if (move_new) {
move_count++;
move_count++;
} else if (new_dir) {
if (ret <= 0) {
if (ret < 0)
T_BEGIN {
} T_END;
if (ret < 0)
#ifdef __APPLE__
if (errno != 0) {
if (dir_changed) {
const void *data;
if (data_size == 0) {
(undirty || \
if (!*new_changed_r) {
if (!*cur_changed_r) {
if (check_new)
if (check_cur)
if (!seen_changes) {
if (!*new_changed_r)
} else if (*new_changed_r) {
if (!*cur_changed_r)
const char *fname;
int ret;
if (forced)
if (ret <= 0)
return ret;
if (!cur_changed) {
sync_flags = 0;
if (forced)
if (ret <= 0) {
if (ret == 0) {
if (forced) {
if (ret <= 0) {
unsigned int count = 0;
if (ret < 0)
if (cur_changed) {
if (ret < 0)
if (ret < 0)
if (ret == 0)
if (ret < 0)
if (ret == 0) {
*find_uid = 0;
*find_uid = 0;
const char **fname_r)
int ret;
if (ret <= 0) {
if (ret < 0)
return ret;
bool lost_files;
int ret;
T_BEGIN {
} T_END;
if (uid != 0) {
T_BEGIN {
&lost_files);
} T_END;
return ret;
struct mailbox_sync_context *
int ret = 0;
T_BEGIN {
&lost_files);
} T_END;
if (lost_files) {
int ret;
T_BEGIN {
} T_END;