From 233f563569700bee5d9f92347d74d22016feff10 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sat, 25 Aug 2012 15:29:16 +0200 Subject: [PATCH] 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. --- src/drv_maildir.c | 38 ++++++++++++++++++++++++++++++++++++++ src/isync.h | 1 + src/main.c | 3 +++ src/run-tests.pl | 2 +- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/drv_maildir.c b/src/drv_maildir.c index 9d4a00e..24aae2d 100644 --- 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 index fa7d082..a39ab21 100644 --- 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 index 85e3699..71470e6 100644 --- 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 index f2b4736..842e6e6 100755 --- 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 = ; close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n"); return $?, @out;