Bläddra i källkod

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.
Oswald Buddenhagen 13 år sedan
förälder
incheckning
233f563569
4 ändrade filer med 43 tillägg och 1 borttagningar
  1. 38 0
      src/drv_maildir.c
  2. 1 0
      src/isync.h
  3. 3 0
      src/main.c
  4. 1 1
      src/run-tests.pl

+ 38 - 0
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 )))

+ 1 - 0
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;
 

+ 3 - 0
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':

+ 1 - 1
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;