commit 233f563569700bee5d9f92347d74d22016feff10
parent 338628520543da79fbb8d78851e08a565e45357a
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date: Sat, 25 Aug 2012 15:29:16 +0200
deal with concurrent maildir modifications during listing
files may be renamed (due to new -> cur transition or flag changes),
which may lead to two effects if ignored:
- we see both the old and the new name, so we report a spurious
duplicate UID
- we see neither name, so we report a spurious deletion
as countermeasure, record and compare directory modification times. upon
mismatch, we just start over - as usual.
Diffstat:
4 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/src/drv_maildir.c b/src/drv_maildir.c
@@ -507,6 +507,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
#endif /* USE_DB */
msg_t *entry;
int i, j, uid, bl, fnl, ret;
+ time_t now, stamps[2];
struct stat st;
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
@@ -536,11 +537,32 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
}
#endif /* USE_DB */
bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", ctx->gen.path );
+ restat:
+ now = time( 0 );
+ for (i = 0; i < 2; i++) {
+ memcpy( buf + bl, subdirs[i], 4 );
+ if (stat( buf, &st )) {
+ sys_error( "Maildir error: cannot stat %s", buf );
+ goto dfail;
+ }
+ if (st.st_mtime == now && !(DFlags & ZERODELAY)) {
+ /* If the modification happened during this second, we wouldn't be able to
+ * tell if there were further modifications during this second. So wait.
+ * This has the nice side effect that we wait for "batches" of changes to
+ * complete. On the downside, it can potentially block indefinitely. */
+ info( "Maildir notice: sleeping due to recent directory modification.\n" );
+ sleep( 1 ); /* FIXME: should make this async */
+ goto restat;
+ }
+ stamps[i] = st.st_mtime;
+ }
for (i = 0; i < 2; i++) {
memcpy( buf + bl, subdirs[i], 4 );
if (!(d = opendir( buf ))) {
sys_error( "Maildir error: cannot list %s", buf );
+ rfail:
maildir_free_scan( msglist );
+ dfail:
#ifdef USE_DB
if (ctx->db)
tdb->close( tdb, 0 );
@@ -600,6 +622,22 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
}
closedir( d );
}
+ for (i = 0; i < 2; i++) {
+ memcpy( buf + bl, subdirs[i], 4 );
+ if (stat( buf, &st )) {
+ sys_error( "Maildir error: cannot re-stat %s", buf );
+ goto rfail;
+ }
+ if (st.st_mtime != stamps[i]) {
+ /* Somebody messed with the mailbox since we started listing it. */
+#ifdef USE_DB
+ if (ctx->db)
+ tdb->close( tdb, 0 );
+#endif /* USE_DB */
+ maildir_free_scan( msglist );
+ goto again;
+ }
+ }
#ifdef USE_DB
if (ctx->db) {
if ((ret = ctx->db->cursor( ctx->db, 0, &dbc, 0 )))
diff --git a/src/isync.h b/src/isync.h
@@ -395,6 +395,7 @@ void cram( const char *challenge, const char *user, const char *pass,
#define QUIET 8
#define VERYQUIET 16
#define KEEPJOURNAL 32
+#define ZERODELAY 64
extern int DFlags;
diff --git a/src/main.c b/src/main.c
@@ -437,6 +437,9 @@ main( int argc, char **argv )
case 'J':
DFlags |= KEEPJOURNAL;
break;
+ case 'Z':
+ DFlags |= ZERODELAY;
+ break;
case 'v':
version();
case 'h':
diff --git a/src/run-tests.pl b/src/run-tests.pl
@@ -282,7 +282,7 @@ sub killcfg()
sub runsync($)
{
# open FILE, "valgrind -q --log-fd=3 ../mbsync ".shift()." -c .mbsyncrc test 3>&2 2>&1 |";
- open FILE, "../mbsync -D ".shift()." -c .mbsyncrc test 2>&1 |";
+ open FILE, "../mbsync -D -Z ".shift()." -c .mbsyncrc test 2>&1 |";
my @out = <FILE>;
close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n");
return $?, @out;