changeset 13875:4c827134997f

Merged dsync into "doveadm dsync". dsync symlink is installed for backwards compatibility.
author Timo Sirainen <tss@iki.fi>
date Thu, 29 Dec 2011 14:43:45 +0200
parents 3c7e01a5c7b7
children bc9217cb0193
files .hgignore configure.in src/Makefile.am src/doveadm/Makefile.am src/doveadm/client-connection.c src/doveadm/doveadm-mail.c src/doveadm/doveadm-mail.h src/doveadm/doveadm-settings.c src/doveadm/doveadm-settings.h src/doveadm/doveadm.c src/doveadm/dsync/Makefile.am src/doveadm/dsync/doveadm-dsync.c src/doveadm/dsync/doveadm-dsync.h src/doveadm/dsync/dsync-brain-msgs-new.c src/doveadm/dsync/dsync-brain-msgs.c src/doveadm/dsync/dsync-brain-private.h src/doveadm/dsync/dsync-brain.c src/doveadm/dsync/dsync-brain.h src/doveadm/dsync/dsync-data.c src/doveadm/dsync/dsync-data.h src/doveadm/dsync/dsync-proxy-client.c src/doveadm/dsync/dsync-proxy-server-cmd.c src/doveadm/dsync/dsync-proxy-server.c src/doveadm/dsync/dsync-proxy-server.h src/doveadm/dsync/dsync-proxy.c src/doveadm/dsync/dsync-proxy.h src/doveadm/dsync/dsync-worker-local.c src/doveadm/dsync/dsync-worker-private.h src/doveadm/dsync/dsync-worker.c src/doveadm/dsync/dsync-worker.h src/doveadm/dsync/test-dsync-brain-msgs.c src/doveadm/dsync/test-dsync-brain.c src/doveadm/dsync/test-dsync-common.c src/doveadm/dsync/test-dsync-common.h src/doveadm/dsync/test-dsync-proxy-server-cmd.c src/doveadm/dsync/test-dsync-proxy.c src/doveadm/dsync/test-dsync-worker.c src/doveadm/dsync/test-dsync-worker.h src/dsync/Makefile.am src/dsync/dsync-brain-msgs-new.c src/dsync/dsync-brain-msgs.c src/dsync/dsync-brain-private.h src/dsync/dsync-brain.c src/dsync/dsync-brain.h src/dsync/dsync-data.c src/dsync/dsync-data.h src/dsync/dsync-proxy-client.c src/dsync/dsync-proxy-server-cmd.c src/dsync/dsync-proxy-server.c src/dsync/dsync-proxy-server.h src/dsync/dsync-proxy.c src/dsync/dsync-proxy.h src/dsync/dsync-worker-local.c src/dsync/dsync-worker-private.h src/dsync/dsync-worker.c src/dsync/dsync-worker.h src/dsync/dsync.c src/dsync/test-dsync-brain-msgs.c src/dsync/test-dsync-brain.c src/dsync/test-dsync-common.c src/dsync/test-dsync-common.h src/dsync/test-dsync-proxy-server-cmd.c src/dsync/test-dsync-proxy.c src/dsync/test-dsync-worker.c src/dsync/test-dsync-worker.h
diffstat 65 files changed, 10173 insertions(+), 10024 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Dec 29 11:19:52 2011 +0200
+++ b/.hgignore	Thu Dec 29 14:43:45 2011 +0200
@@ -66,7 +66,6 @@
 src/dns/dns-client
 src/doveadm/doveadm
 src/doveadm/doveadm-server
-src/dsync/dsync
 src/imap-login/imap-login
 src/imap/imap
 src/indexer/indexer
--- a/configure.in	Thu Dec 29 11:19:52 2011 +0200
+++ b/configure.in	Thu Dec 29 14:43:45 2011 +0200
@@ -2750,7 +2750,7 @@
 src/auth/Makefile
 src/config/Makefile
 src/doveadm/Makefile
-src/dsync/Makefile
+src/doveadm/dsync/Makefile
 src/lda/Makefile
 src/log/Makefile
 src/lmtp/Makefile
--- a/src/Makefile.am	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/Makefile.am	Thu Dec 29 14:43:45 2011 +0200
@@ -41,7 +41,6 @@
 	director \
 	util \
 	doveadm \
-	dsync \
 	ssl-params \
 	stats \
 	plugins
--- a/src/doveadm/Makefile.am	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/Makefile.am	Thu Dec 29 14:43:45 2011 +0200
@@ -1,6 +1,8 @@
 doveadm_moduledir = $(moduledir)/doveadm
 pkglibexecdir = $(libexecdir)/dovecot
 
+SUBDIRS = dsync
+
 bin_PROGRAMS = doveadm
 pkglibexec_PROGRAMS = doveadm-server
 
@@ -35,6 +37,7 @@
 	../lib-otp/libotp.a
 
 libs = \
+	dsync/libdsync.a \
 	$(LIBDOVECOT_STORAGE) \
 	$(unused_objects)
 
@@ -122,3 +125,7 @@
 	doveadm-settings.h \
 	doveadm-util.h \
 	doveadm-who.h
+
+install-exec-local:
+	rm -f $(DESTDIR)$(bindir)/dsync
+	$(LN_S) doveadm $(DESTDIR)$(bindir)/dsync
--- a/src/doveadm/client-connection.c	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/client-connection.c	Thu Dec 29 14:43:45 2011 +0200
@@ -41,9 +41,6 @@
 			const struct mail_storage_service_input *input,
 			int argc, char *argv[])
 {
-	enum mail_storage_service_flags service_flags =
-		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
-		MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
 	struct doveadm_mail_cmd_context *ctx;
 	const struct doveadm_mail_cmd *cmd;
 	const char *getopt_args;
@@ -56,12 +53,15 @@
 		return FALSE;
 	}
 
-	if (doveadm_debug)
-		service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
-
 	ctx = doveadm_mail_cmd_init(cmd, set);
 	ctx->full_args = (const void *)(argv + 1);
 
+	ctx->service_flags |=
+		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+		MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+	if (doveadm_debug)
+		ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
+
 	getopt_args = t_strconcat("AS:u:", ctx->getopt_args, NULL);
 	while ((c = getopt(argc, argv, getopt_args)) > 0) {
 		switch (c) {
@@ -106,7 +106,10 @@
 	}
 
 	ctx->args = (const void *)argv;
-	doveadm_mail_single_user(ctx, input, service_flags);
+	if (ctx->v.preinit != NULL)
+		ctx->v.preinit(ctx);
+
+	doveadm_mail_single_user(ctx, input);
 	doveadm_mail_server_flush();
 	ctx->v.deinit(ctx);
 	doveadm_print_flush();
--- a/src/doveadm/doveadm-mail.c	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/doveadm-mail.c	Thu Dec 29 14:43:45 2011 +0200
@@ -19,6 +19,7 @@
 #include "doveadm.h"
 #include "doveadm-settings.h"
 #include "doveadm-print.h"
+#include "dsync/doveadm-dsync.h"
 #include "doveadm-mail.h"
 
 #include <stdio.h>
@@ -186,7 +187,6 @@
 		       const struct mail_storage_service_input *input,
 		       const char **error_r)
 {
-	struct mail_storage_service_user *service_user;
 	const char *error;
 	int ret;
 
@@ -199,7 +199,7 @@
 		return ret;
 
 	ret = mail_storage_service_lookup(ctx->storage_service, input,
-					  &service_user, &error);
+					  &ctx->cur_service_user, &error);
 	if (ret <= 0) {
 		if (ret < 0) {
 			*error_r = t_strdup_printf("User lookup failed: %s",
@@ -208,31 +208,32 @@
 		return ret;
 	}
 
-	ret = mail_storage_service_next(ctx->storage_service, service_user,
+	ret = mail_storage_service_next(ctx->storage_service,
+					ctx->cur_service_user,
 					&ctx->cur_mail_user);
 	if (ret < 0) {
 		*error_r = "User init failed";
-		mail_storage_service_user_free(&service_user);
+		mail_storage_service_user_free(&ctx->cur_service_user);
 		return ret;
 	}
 
 	ctx->v.run(ctx, ctx->cur_mail_user);
 	mail_user_unref(&ctx->cur_mail_user);
-	mail_storage_service_user_free(&service_user);
+	mail_storage_service_user_free(&ctx->cur_service_user);
 	return 1;
 }
 
 void doveadm_mail_single_user(struct doveadm_mail_cmd_context *ctx,
-			      const struct mail_storage_service_input *input,
-			      enum mail_storage_service_flags service_flags)
+			      const struct mail_storage_service_input *input)
 {
 	const char *error;
 	int ret;
 
 	i_assert(input->username != NULL);
 
+	ctx->cur_username = input->username;
 	ctx->storage_service = mail_storage_service_init(master_service, NULL,
-							 service_flags);
+							 ctx->service_flags);
 	ctx->v.init(ctx, ctx->args);
 	if (hook_doveadm_mail_init != NULL)
 		hook_doveadm_mail_init(ctx);
@@ -251,21 +252,20 @@
 
 static void
 doveadm_mail_all_users(struct doveadm_mail_cmd_context *ctx, char *argv[],
-		       const char *wildcard_user,
-		       enum mail_storage_service_flags service_flags)
+		       const char *wildcard_user)
 {
 	struct mail_storage_service_input input;
 	unsigned int user_idx, user_count, interval, n;
 	const char *user, *error;
 	int ret;
 
-	service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+	ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
 
 	memset(&input, 0, sizeof(input));
 	input.service = "doveadm";
 
 	ctx->storage_service = mail_storage_service_init(master_service, NULL,
-							 service_flags);
+							 ctx->service_flags);
         lib_signals_set_handler(SIGINT, 0, sig_die, NULL);
 	lib_signals_set_handler(SIGTERM, 0, sig_die, NULL);
 
@@ -355,20 +355,19 @@
 static void
 doveadm_mail_cmd(const struct doveadm_mail_cmd *cmd, int argc, char *argv[])
 {
-	enum mail_storage_service_flags service_flags =
-		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
 	struct doveadm_mail_cmd_context *ctx;
-	const char *getopt_args, *username, *wildcard_user;
+	const char *getopt_args, *wildcard_user;
 	int c;
 
-	if (doveadm_debug)
-		service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
-
 	ctx = doveadm_mail_cmd_init(cmd, doveadm_settings);
 	ctx->full_args = (const void *)(argv + 1);
 
+	ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
+	if (doveadm_debug)
+		ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
+
 	getopt_args = t_strconcat("AS:u:", ctx->getopt_args, NULL);
-	username = getenv("USER");
+	ctx->cur_username = getenv("USER");
 	wildcard_user = NULL;
 	while ((c = getopt(argc, argv, getopt_args)) > 0) {
 		switch (c) {
@@ -381,12 +380,14 @@
 				doveadm_settings->doveadm_worker_count = 1;
 			break;
 		case 'u':
-			service_flags |=
+			ctx->service_flags |=
 				MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
-			username = optarg;
-			if (strchr(username, '*') != NULL ||
-			    strchr(username, '?') != NULL)
-				wildcard_user = username;
+			ctx->cur_username = optarg;
+			if (strchr(ctx->cur_username, '*') != NULL ||
+			    strchr(ctx->cur_username, '?') != NULL) {
+				wildcard_user = ctx->cur_username;
+				ctx->cur_username = NULL;
+			}
 			break;
 		default:
 			if (ctx->v.parse_arg == NULL ||
@@ -400,6 +401,8 @@
 			cmd->name, argv[0]);
 	}
 	ctx->args = (const void *)argv;
+	if (ctx->v.preinit != NULL)
+		ctx->v.preinit(ctx);
 
 	ctx->iterate_single_user =
 		!ctx->iterate_all_users && wildcard_user == NULL;
@@ -412,16 +415,16 @@
 	if (ctx->iterate_single_user) {
 		struct mail_storage_service_input input;
 
-		if (username == NULL)
+		if (ctx->cur_username == NULL)
 			i_fatal("USER environment is missing and -u option not used");
 
 		memset(&input, 0, sizeof(input));
 		input.service = "doveadm";
-		input.username = username;
-		doveadm_mail_single_user(ctx, &input, service_flags);
+		input.username = ctx->cur_username;
+		doveadm_mail_single_user(ctx, &input);
 	} else {
-		service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP;
-		doveadm_mail_all_users(ctx, argv, wildcard_user, service_flags);
+		ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP;
+		doveadm_mail_all_users(ctx, argv, wildcard_user);
 	}
 	if (ctx->search_args != NULL)
 		mail_search_args_unref(&ctx->search_args);
@@ -432,6 +435,8 @@
 	/* service deinit unloads mail plugins, so do it late */
 	mail_storage_service_deinit(&ctx->storage_service);
 
+	if (ctx->exit_code != 0)
+		exit(ctx->exit_code);
 	if (ctx->failed)
 		exit(FATAL_DEFAULT);
 	pool_unref(&ctx->pool);
@@ -579,7 +584,10 @@
 	&cmd_mailbox_rename,
 	&cmd_mailbox_subscribe,
 	&cmd_mailbox_unsubscribe,
-	&cmd_mailbox_status
+	&cmd_mailbox_status,
+	&cmd_dsync_backup,
+	&cmd_dsync_mirror,
+	&cmd_dsync_server
 };
 
 void doveadm_mail_init(void)
--- a/src/doveadm/doveadm-mail.h	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/doveadm-mail.h	Thu Dec 29 14:43:45 2011 +0200
@@ -4,17 +4,15 @@
 #include <stdio.h>
 #include "doveadm-util.h"
 #include "module-context.h"
+#include "mail-storage-service.h"
 
-enum mail_storage_service_flags;
 struct mailbox;
 struct mail_user;
-struct mail_storage_service_ctx;
-struct mail_storage_service_input;
-struct mail_storage_service_user;
 struct doveadm_mail_cmd_context;
 
 struct doveadm_mail_cmd_vfuncs {
 	bool (*parse_arg)(struct doveadm_mail_cmd_context *ctx,int c);
+	void (*preinit)(struct doveadm_mail_cmd_context *ctx);
 	void (*init)(struct doveadm_mail_cmd_context *ctx,
 		     const char *const args[]);
 	int (*get_next_user)(struct doveadm_mail_cmd_context *ctx,
@@ -42,15 +40,21 @@
 
 	const char *getopt_args;
 	const struct doveadm_settings *set;
+	enum mail_storage_service_flags service_flags;
 	struct mail_storage_service_ctx *storage_service;
 	/* search args aren't set for all mail commands */
 	struct mail_search_args *search_args;
 
+	const char *cur_username;
+	struct mail_storage_service_user *cur_service_user;
 	struct mail_user *cur_mail_user;
 	struct doveadm_mail_cmd_vfuncs v;
 
 	ARRAY_DEFINE(module_contexts, union doveadm_mail_cmd_module_context *);
 
+	/* if non-zero, exit with this code */
+	int exit_code;
+
 	/* We're handling only a single user */
 	unsigned int iterate_single_user:1;
 	/* We're going through all users (not set for wildcard usernames) */
@@ -86,8 +90,7 @@
 doveadm_mail_cmd_init(const struct doveadm_mail_cmd *cmd,
 		      const struct doveadm_settings *set);
 void doveadm_mail_single_user(struct doveadm_mail_cmd_context *ctx,
-			      const struct mail_storage_service_input *input,
-			      enum mail_storage_service_flags service_flags);
+			      const struct mail_storage_service_input *input);
 int doveadm_mail_server_user(struct doveadm_mail_cmd_context *ctx,
 			     const struct mail_storage_service_input *input,
 			     const char **error_r);
--- a/src/doveadm/doveadm-settings.c	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/doveadm-settings.c	Thu Dec 29 14:43:45 2011 +0200
@@ -60,6 +60,7 @@
 	DEF(SET_UINT, doveadm_proxy_port),
 	DEF(SET_STR, doveadm_password),
 	DEF(SET_STR, doveadm_allowed_commands),
+	DEF(SET_STR, dsync_alt_char),
 
 	{ SET_STRLIST, "plugin", offsetof(struct doveadm_settings, plugin_envs), NULL },
 
@@ -75,6 +76,7 @@
 	.doveadm_proxy_port = 0,
 	.doveadm_password = "",
 	.doveadm_allowed_commands = "",
+	.dsync_alt_char = "_",
 
 	.plugin_envs = ARRAY_INIT
 };
--- a/src/doveadm/doveadm-settings.h	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/doveadm-settings.h	Thu Dec 29 14:43:45 2011 +0200
@@ -10,6 +10,7 @@
 	unsigned int doveadm_proxy_port;
 	const char *doveadm_password;
 	const char *doveadm_allowed_commands;
+	const char *dsync_alt_char;
 
 	ARRAY_DEFINE(plugin_envs, const char *);
 };
--- a/src/doveadm/doveadm.c	Thu Dec 29 11:19:52 2011 +0200
+++ b/src/doveadm/doveadm.c	Thu Dec 29 14:43:45 2011 +0200
@@ -12,6 +12,7 @@
 #include "doveadm-dump.h"
 #include "doveadm-mail.h"
 #include "doveadm-settings.h"
+#include "dsync/doveadm-dsync.h"
 #include "doveadm.h"
 
 #include <stdlib.h>
@@ -274,6 +275,8 @@
 	bool quick_init = FALSE;
 	int c;
 
+	doveadm_dsync_main(&argc, &argv);
+
 	/* "+" is GNU extension to stop at the first non-option.
 	   others just accept -+ option. */
 	master_service = master_service_init("doveadm", service_flags,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/Makefile.am	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,80 @@
+noinst_LIBRARIES = libdsync.a
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-test \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/doveadm
+
+libs = \
+	$(LIBDOVECOT_STORAGE)
+
+libdsync_a_SOURCES = \
+	doveadm-dsync.c \
+	dsync-brain.c \
+	dsync-brain-msgs.c \
+	dsync-brain-msgs-new.c \
+	dsync-data.c \
+	dsync-proxy.c \
+	dsync-proxy-client.c \
+	dsync-proxy-server.c \
+	dsync-proxy-server-cmd.c \
+	dsync-worker.c \
+	dsync-worker-local.c
+
+noinst_HEADERS = \
+	dsync-brain.h \
+	dsync-brain-private.h \
+	dsync-data.h \
+	dsync-proxy.h \
+	dsync-proxy-server.h \
+	dsync-worker.h \
+	dsync-worker-private.h \
+	test-dsync-common.h \
+	test-dsync-worker.h
+
+test_programs = \
+	test-dsync-brain \
+	test-dsync-brain-msgs \
+	test-dsync-proxy \
+	test-dsync-proxy-server-cmd
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+	../../lib-test/libtest.la \
+	../../lib-mail/libmail.la \
+	../../lib-imap/libimap.la \
+	../../lib-charset/libcharset.la \
+	../../lib/liblib.la
+
+test_ldadd = \
+	$(test_libs) \
+	$(LIBICONV)
+
+test_dsync_brain_SOURCES = test-dsync-brain.c test-dsync-worker.c test-dsync-common.c
+test_dsync_brain_LDADD = dsync-data.o dsync-brain.o dsync-worker.o $(test_ldadd)
+test_dsync_brain_DEPENDENCIES = dsync-data.o dsync-brain.o dsync-worker.o $(test_libs)
+
+test_dsync_brain_msgs_SOURCES = test-dsync-brain-msgs.c test-dsync-worker.c test-dsync-common.c
+test_dsync_brain_msgs_LDADD = dsync-data.o dsync-brain-msgs.o dsync-worker.o $(test_ldadd)
+test_dsync_brain_msgs_DEPENDENCIES = dsync-data.o dsync-brain-msgs.o dsync-worker.o $(test_libs)
+
+test_dsync_proxy_SOURCES = test-dsync-proxy.c test-dsync-common.c
+test_dsync_proxy_LDADD = dsync-proxy.o dsync-data.o $(test_ldadd)
+test_dsync_proxy_DEPENDENCIES = dsync-proxy.o dsync-data.o $(test_libs)
+
+test_dsync_proxy_server_cmd_SOURCES = test-dsync-proxy-server-cmd.c test-dsync-worker.c test-dsync-common.c
+test_dsync_proxy_server_cmd_LDADD = dsync-worker.o dsync-proxy.o dsync-proxy-server-cmd.o dsync-data.o $(test_ldadd)
+test_dsync_proxy_server_cmd_DEPENDENCIES = dsync-worker.o dsync-proxy.o dsync-proxy-server-cmd.o dsync-data.o $(test_libs)
+
+check: check-am check-test
+check-test: all-am
+	for bin in $(test_programs); do \
+	  if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+	done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/doveadm-dsync.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,494 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "array.h"
+#include "execv-const.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "mail-storage-service.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "doveadm-settings.h"
+#include "doveadm-mail.h"
+#include "dsync-brain.h"
+#include "dsync-worker.h"
+#include "dsync-proxy-server.h"
+#include "doveadm-dsync.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+struct dsync_cmd_context {
+	struct doveadm_mail_cmd_context ctx;
+	enum dsync_brain_flags brain_flags;
+	const char *mailbox;
+
+	const char *const *remote_cmd_args;
+	const char *local_location;
+
+	int fd_in, fd_out;
+
+	unsigned int reverse_workers:1;
+};
+
+static const char *ssh_cmd = "ssh";
+
+static void run_cmd(const char *const *args, int *fd_in_r, int *fd_out_r)
+{
+	int fd_in[2], fd_out[2];
+
+	if (pipe(fd_in) < 0 || pipe(fd_out) < 0)
+		i_fatal("pipe() failed: %m");
+
+	switch (fork()) {
+	case -1:
+		i_fatal("fork() failed: %m");
+		break;
+	case 0:
+		/* child, which will execute the proxy server. stdin/stdout
+		   goes to pipes which we'll pass to proxy client. */
+		if (dup2(fd_in[0], STDIN_FILENO) < 0 ||
+		    dup2(fd_out[1], STDOUT_FILENO) < 0)
+			i_fatal("dup2() failed: %m");
+
+		(void)close(fd_in[0]);
+		(void)close(fd_in[1]);
+		(void)close(fd_out[0]);
+		(void)close(fd_out[1]);
+
+		execvp_const(args[0], args);
+		break;
+	default:
+		/* parent */
+		(void)close(fd_in[0]);
+		(void)close(fd_out[1]);
+		*fd_in_r = fd_out[0];
+		*fd_out_r = fd_in[1];
+		break;
+	}
+}
+
+static void
+mirror_get_remote_cmd_line(const char *const *argv,
+			   const char *const **cmd_args_r)
+{
+	ARRAY_TYPE(const_string) cmd_args;
+	unsigned int i;
+	const char *p;
+
+	t_array_init(&cmd_args, 16);
+	for (i = 0; argv[i] != NULL; i++) {
+		p = argv[i];
+		array_append(&cmd_args, &p, 1);
+	}
+
+	p = strchr(argv[0], '/');
+	if (p == NULL) p = argv[0];
+	if (strstr(p, "dsync") == NULL) {
+		/* we're executing doveadm (not dsync) */
+		p = "dsync"; array_append(&cmd_args, &p, 1);
+	}
+	p = "server"; array_append(&cmd_args, &p, 1);
+	(void)array_append_space(&cmd_args);
+	*cmd_args_r = array_idx(&cmd_args, 0);
+}
+
+static bool mirror_get_remote_cmd(const char *const *argv, const char *user,
+				  const char *const **cmd_args_r)
+{
+	ARRAY_TYPE(const_string) cmd_args;
+	const char *p, *host;
+
+	if (argv[1] != NULL) {
+		/* more than one parameter, so it contains a full command
+		   (e.g. ssh host dsync) */
+		mirror_get_remote_cmd_line(argv, cmd_args_r);
+		return TRUE;
+	}
+
+	/* if it begins with /[a-z0-9]+:/, it's a mail location
+	   (e.g. mdbox:~/mail) */
+	for (p = argv[0]; *p != '\0'; p++) {
+		if (!i_isalnum(*p)) {
+			if (*p == ':')
+				return FALSE;
+			break;
+		}
+	}
+
+	if (strchr(argv[0], ' ') != NULL || strchr(argv[0], '/') != NULL) {
+		/* a) the whole command is in one string. this is mainly for
+		      backwards compatibility.
+		   b) script/path */
+		mirror_get_remote_cmd_line(t_strsplit(argv[0], " "),
+					   cmd_args_r);
+		return TRUE;
+	}
+
+	/* [user@]host */
+	host = strchr(argv[0], '@');
+	if (host != NULL)
+		user = t_strdup_until(argv[0], host++);
+	else
+		host = argv[0];
+
+	/* we'll assume virtual users, so in user@host it really means not to
+	   give ssh a username, but to give dsync -u user parameter. */
+	t_array_init(&cmd_args, 8);
+	array_append(&cmd_args, &ssh_cmd, 1);
+	array_append(&cmd_args, &host, 1);
+	p = "doveadm"; array_append(&cmd_args, &p, 1);
+	p = "dsync"; array_append(&cmd_args, &p, 1);
+	p = "server"; array_append(&cmd_args, &p, 1);
+	if (*user != '\0') {
+		p = "-u"; array_append(&cmd_args, &p, 1);
+		array_append(&cmd_args, &user, 1);
+	}
+	(void)array_append_space(&cmd_args);
+	*cmd_args_r = array_idx(&cmd_args, 0);
+	return TRUE;
+}
+
+static struct dsync_worker *
+cmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user)
+{
+	struct mail_user *user2;
+	struct dsync_worker *worker2;
+	struct setting_parser_context *set_parser;
+	const char *set_line, *path1, *path2;
+
+	i_assert(ctx->local_location != NULL);
+
+	ctx->brain_flags |= DSYNC_BRAIN_FLAG_LOCAL;
+	i_set_failure_prefix(t_strdup_printf("dsync(%s): ", user->username));
+
+	/* update mail_location and create another user for the
+	   second location. */
+	set_parser = mail_storage_service_user_get_settings_parser(ctx->ctx.cur_service_user);
+	set_line = t_strconcat("mail_location=", ctx->local_location, NULL);
+	if (settings_parse_line(set_parser, set_line) < 0)
+		i_unreached();
+	if (mail_storage_service_next(ctx->ctx.storage_service,
+				      ctx->ctx.cur_service_user, &user2) < 0)
+		i_fatal("User init failed");
+	user2->admin = TRUE;
+
+	if (mail_namespaces_get_root_sep(user->namespaces) !=
+	    mail_namespaces_get_root_sep(user2->namespaces)) {
+		i_fatal("Mail locations must use the same "
+			"virtual mailbox hierarchy separator "
+			"(specify separator for the default namespace)");
+	}
+	path1 = mailbox_list_get_path(user->namespaces->list, NULL,
+				      MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	path2 = mailbox_list_get_path(user2->namespaces->list, NULL,
+				      MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	if (path1 != NULL && path2 != NULL &&
+	    strcmp(path1, path2) == 0) {
+		i_fatal("Both source and destination mail_location "
+			"points to same directory: %s", path1);
+	}
+
+	worker2 = dsync_worker_init_local(user2, *ctx->ctx.set->dsync_alt_char);
+	mail_user_unref(&user2);
+	return worker2;
+}
+
+static struct dsync_worker *
+cmd_dsync_run_remote(struct dsync_cmd_context *ctx, struct mail_user *user)
+{
+	i_set_failure_prefix(t_strdup_printf("dsync-local(%s): ",
+					     user->username));
+	return dsync_worker_init_proxy_client(ctx->fd_in, ctx->fd_out);
+}
+
+static void
+cmd_dsync_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user)
+{
+	struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx;
+	struct dsync_worker *worker1, *worker2, *workertmp;
+	struct dsync_brain *brain;
+
+	user->admin = TRUE;
+
+	/* create workers */
+	worker1 = dsync_worker_init_local(user, *_ctx->set->dsync_alt_char);
+	if (ctx->remote_cmd_args == NULL)
+		worker2 = cmd_dsync_run_local(ctx, user);
+	else
+		worker2 = cmd_dsync_run_remote(ctx, user);
+	if (ctx->reverse_workers) {
+		workertmp = worker1;
+		worker1 = worker2;
+		worker2 = workertmp;
+	}
+
+	/* create and run the brain */
+	brain = dsync_brain_init(worker1, worker2, ctx->mailbox,
+				 ctx->brain_flags);
+	if (ctx->remote_cmd_args == NULL)
+		dsync_brain_sync_all(brain);
+	else {
+		dsync_brain_sync(brain);
+		if (!dsync_brain_has_failed(brain))
+			io_loop_run(current_ioloop);
+	}
+	/* deinit */
+	if (dsync_brain_has_unexpected_changes(brain)) {
+		i_warning("Mailbox changes caused a desync. "
+			  "You may want to run dsync again.");
+		_ctx->exit_code = 2;
+	}
+	if (dsync_brain_deinit(&brain) < 0)
+		_ctx->exit_code = 1;
+
+	dsync_worker_deinit(&worker1);
+	dsync_worker_deinit(&worker2);
+}
+
+static void cmd_dsync_init(struct doveadm_mail_cmd_context *_ctx,
+			   const char *const args[])
+{
+	struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx;
+	const char *username = "";
+
+	if (args[0] == NULL)
+		doveadm_mail_help_name("dsync");
+
+	lib_signals_ignore(SIGHUP, TRUE);
+
+	if (doveadm_debug || doveadm_verbose)
+		ctx->brain_flags |= DSYNC_BRAIN_FLAG_VERBOSE;
+
+	/* if we're executing remotely, give -u parameter if we also
+	   did a userdb lookup. this works only when we're handling a
+	   single user */
+	if ((_ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0 &&
+	    _ctx->cur_username != NULL)
+		username = _ctx->cur_username;
+	if (!mirror_get_remote_cmd(args, username, &ctx->remote_cmd_args)) {
+		/* it's a mail_location */
+		if (args[1] != NULL)
+			doveadm_mail_help_name("dsync");
+		ctx->local_location = args[0];
+	}
+
+	if (ctx->remote_cmd_args != NULL) {
+		/* do this before mail_storage_service_next() in case it
+		   drops process privileges */
+		run_cmd(ctx->remote_cmd_args, &ctx->fd_in, &ctx->fd_out);
+	} else {
+		ctx->fd_in = STDIN_FILENO;
+		ctx->fd_out = STDOUT_FILENO;
+	}
+}
+
+static void cmd_dsync_preinit(struct doveadm_mail_cmd_context *ctx)
+{
+	if ((ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0)
+		ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR;
+}
+
+static bool
+cmd_mailbox_dsync_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+	struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx;
+
+	switch (c) {
+	case 'f':
+		ctx->brain_flags |= DSYNC_BRAIN_FLAG_FULL_SYNC;
+		break;
+	case 'm':
+		ctx->mailbox = optarg;
+		break;
+	case 'R':
+		ctx->reverse_workers = TRUE;
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static struct doveadm_mail_cmd_context *cmd_dsync_alloc(void)
+{
+	struct dsync_cmd_context *ctx;
+
+	ctx = doveadm_mail_cmd_alloc(struct dsync_cmd_context);
+	ctx->ctx.getopt_args = "fRm:";
+	ctx->ctx.v.parse_arg = cmd_mailbox_dsync_parse_arg;
+	ctx->ctx.v.preinit = cmd_dsync_preinit;
+	ctx->ctx.v.init = cmd_dsync_init;
+	ctx->ctx.v.run = cmd_dsync_run;
+	return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_dsync_backup_alloc(void)
+{
+	struct doveadm_mail_cmd_context *_ctx;
+	struct dsync_cmd_context *ctx;
+
+	_ctx = cmd_dsync_alloc();
+	ctx = (struct dsync_cmd_context *)_ctx;
+	ctx->brain_flags |= DSYNC_BRAIN_FLAG_BACKUP;
+	return _ctx;
+}
+
+static void
+cmd_dsync_server_run(struct doveadm_mail_cmd_context *ctx,
+		     struct mail_user *user)
+{
+	struct dsync_proxy_server *server;
+	struct dsync_worker *worker;
+
+	user->admin = TRUE;
+
+	i_set_failure_prefix(t_strdup_printf("dsync-remote(%s): ",
+					     user->username));
+	worker = dsync_worker_init_local(user, *ctx->set->dsync_alt_char);
+	server = dsync_proxy_server_init(STDIN_FILENO, STDOUT_FILENO, worker);
+
+	io_loop_run(current_ioloop);
+
+	dsync_proxy_server_deinit(&server);
+	dsync_worker_deinit(&worker);
+}
+
+static struct doveadm_mail_cmd_context *cmd_dsync_server_alloc(void)
+{
+	struct doveadm_mail_cmd_context *ctx;
+
+	ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+	ctx->v.run = cmd_dsync_server_run;
+	return ctx;
+}
+
+struct doveadm_mail_cmd cmd_dsync_mirror = {
+	cmd_dsync_alloc, "dsync mirror", "[-fR] [-m <mailbox>] <dest>"
+};
+struct doveadm_mail_cmd cmd_dsync_backup = {
+	cmd_dsync_backup_alloc, "dsync backup",
+	"[-fR] [-m <mailbox>] <dest>"
+};
+struct doveadm_mail_cmd cmd_dsync_server = {
+	cmd_dsync_server_alloc, "dsync server", NULL
+};
+
+void doveadm_dsync_main(int *_argc, char **_argv[])
+{
+	int argc = *_argc;
+	const char *getopt_str;
+	char **argv = *_argv;
+	char **new_argv, *mailbox = NULL, *alt_char = NULL;
+	char *p, *dup, new_flags[5];
+	int max_argc, src, dest, i, j;
+	bool flag_f = FALSE, flag_R = FALSE, flag_m, flag_C, has_arg;
+
+	p = strrchr(argv[0], '/');
+	if (p == NULL) p = argv[0];
+	if (strstr(p, "dsync") == NULL)
+		return;
+
+	/* @UNSAFE: this is called when the "doveadm" binary is called as
+	   "dsync" (for backwards compatibility) */
+	max_argc = argc + 5;
+	new_argv = calloc(sizeof(char *), max_argc);
+	new_argv[0] = argv[0];
+	dest = 1;
+	getopt_str = master_service_getopt_string();
+
+	/* add global doveadm flags */
+	for (src = 1; src < argc; src++) {
+		if (argv[src][0] != '-')
+			break;
+
+		flag_m = FALSE; flag_C = FALSE; has_arg = FALSE;
+		dup = strdup(argv[src]);
+		for (i = j = 1; argv[src][i] != '\0'; i++) {
+			switch (argv[src][i]) {
+			case 'C':
+				flag_C = TRUE;
+				break;
+			case 'f':
+				flag_f = TRUE;
+				break;
+			case 'R':
+				flag_R = TRUE;
+				break;
+			case 'm':
+				flag_m = TRUE;
+				break;
+			default:
+				p = strchr(getopt_str, argv[src][i]);
+				if (p != NULL && p[1] == ':')
+					has_arg = TRUE;
+				dup[j++] = argv[src][i];
+				break;
+			}
+		}
+		if (j > 1) {
+			dup[j++] = '\0';
+			new_argv[dest++] = dup;
+			if (has_arg && src+1 < argc)
+				new_argv[dest++] = argv[++src];
+		}
+		if (flag_m) {
+			if (src+1 == argc)
+				i_fatal("-m missing parameter");
+			mailbox = argv[++src];
+		}
+		if (flag_C) {
+			if (src+1 == argc)
+				i_fatal("-C missing parameter");
+			alt_char = argv[++src];
+		}
+	}
+	if (alt_char != NULL) {
+		new_argv[dest++] = "-o";
+		new_argv[dest++] =
+			p_strconcat(pool_datastack_create(),
+				    "dsync_alt_char=", alt_char, NULL);
+	}
+
+	new_argv[dest++] = "dsync";
+	if (src < argc) {
+		/* mirror|backup|server */
+		if (strcmp(argv[src], "dsync") == 0) {
+			/* looks like we executed doveconf, which
+			   re-executed ourself with new parameters.
+			   no need to change them anymore. */
+			return;
+		}
+		new_argv[dest++] = argv[src++];
+	}
+
+	/* dsync flags */
+	new_flags[0] = '-'; i = 1;
+	if (flag_f)
+		new_flags[i++] = 'f';
+	if (flag_R)
+		new_flags[i++] = 'R';
+	if (mailbox != NULL)
+		new_flags[i++] = 'm';
+	i_assert((unsigned int)i < sizeof(new_flags));
+	new_flags[i] = '\0';
+
+	if (i > 1) {
+		new_argv[dest++] = strdup(new_flags);
+		if (mailbox != NULL)
+			new_argv[dest++] = mailbox;
+	}
+
+	/* rest of the parameters */
+	for (; src < argc; src++)
+		new_argv[dest++] = argv[src];
+	i_assert(dest < max_argc);
+	new_argv[dest] = NULL;
+
+	*_argc = dest;
+	*_argv = new_argv;
+	optind = 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/doveadm-dsync.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,10 @@
+#ifndef DOVEADM_DSYNC_H
+#define DOVEADM_DSYNC_H
+
+extern struct doveadm_mail_cmd cmd_dsync_mirror;
+extern struct doveadm_mail_cmd cmd_dsync_backup;
+extern struct doveadm_mail_cmd cmd_dsync_server;
+
+void doveadm_dsync_main(int *_argc, char **_argv[]);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-brain-msgs-new.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,392 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+/*
+   This code contains the step 6 explained in dsync-brain-msgs.c:
+   It saves/copies new messages and gives new UIDs for conflicting messages.
+
+   The input is both workers' msg iterators' new_msgs and uid_conflicts
+   variables. They're first sorted by mailbox and secondarily by wanted
+   destination UID. Destination UIDs of conflicts should always be higher
+   than new messages'.
+
+   Mailboxes are handled one at a time:
+
+   1. Go through all saved messages. If we've already seen an instance of this
+      message, try to copy it. Otherwise save a new instance of it.
+   2. Some of the copies may fail because they're already expunged by that
+      time. A list of these failed copies are saved to copy_retry_indexes.
+   3. UID conflicts are resolved by assigning a new UID to the message.
+      To avoid delays with remote dsync, this is done via worker API.
+      Internally the local worker copies the message to its new UID and
+      once the copy succeeds, the old UID is expunged. If the copy fails, it's
+      either due to message already being expunged or something more fatal.
+   4. Once all messages are saved/copied, see if there are any failed copies.
+      If so, goto 1, but going through only the failed messages.
+   5. If there are more mailboxes left, go to next one and goto 1.
+
+   Step 4 may require waiting for remote worker to send all replies.
+*/
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "hash.h"
+#include "dsync-worker.h"
+#include "dsync-brain-private.h"
+
+struct dsync_brain_msg_copy_context {
+	struct dsync_brain_msg_iter *iter;
+	unsigned int msg_idx;
+};
+
+struct dsync_brain_msg_save_context {
+	struct dsync_brain_msg_iter *iter;
+	const struct dsync_message *msg;
+	unsigned int mailbox_idx;
+};
+
+static void
+dsync_brain_msg_sync_add_new_msgs(struct dsync_brain_msg_iter *iter);
+
+static void msg_save_callback(void *context)
+{
+	struct dsync_brain_msg_save_context *ctx = context;
+
+	if (--ctx->iter->save_results_left == 0 && !ctx->iter->adding_msgs)
+		dsync_brain_msg_sync_add_new_msgs(ctx->iter);
+	i_free(ctx);
+}
+
+static void msg_get_callback(enum dsync_msg_get_result result,
+			     const struct dsync_msg_static_data *data,
+			     void *context)
+{
+	struct dsync_brain_msg_save_context *ctx = context;
+	const struct dsync_brain_mailbox *mailbox;
+	struct istream *input;
+
+	i_assert(ctx->iter->save_results_left > 0);
+
+	mailbox = array_idx(&ctx->iter->sync->mailboxes, ctx->mailbox_idx);
+	switch (result) {
+	case DSYNC_MSG_GET_RESULT_SUCCESS:
+		/* the mailbox may have changed, make sure we've the
+		   right one */
+		dsync_worker_select_mailbox(ctx->iter->worker, &mailbox->box);
+
+		input = data->input;
+		dsync_worker_msg_save(ctx->iter->worker, ctx->msg, data,
+				      msg_save_callback, ctx);
+		i_stream_unref(&input);
+		break;
+	case DSYNC_MSG_GET_RESULT_EXPUNGED:
+		/* mail got expunged during sync. just skip this. */
+		msg_save_callback(ctx);
+		break;
+	case DSYNC_MSG_GET_RESULT_FAILED:
+		i_error("msg-get failed: box=%s uid=%u guid=%s",
+			mailbox->box.name, ctx->msg->uid, ctx->msg->guid);
+		dsync_brain_fail(ctx->iter->sync->brain);
+		msg_save_callback(ctx);
+		break;
+	}
+}
+
+static void
+dsync_brain_sync_remove_guid_instance(struct dsync_brain_msg_iter *iter,
+				      const struct dsync_brain_new_msg *msg)
+{
+	struct dsync_brain_guid_instance *inst;
+	void *orig_key, *orig_value;
+
+	if (!hash_table_lookup_full(iter->guid_hash, msg->msg->guid,
+				    &orig_key, &orig_value)) {
+		/* another failed copy already removed it */
+		return;
+	}
+	inst = orig_value;
+
+	if (inst->next == NULL)
+		hash_table_remove(iter->guid_hash, orig_key);
+	else
+		hash_table_update(iter->guid_hash, orig_key, inst->next);
+}
+
+static void dsync_brain_copy_callback(bool success, void *context)
+{
+	struct dsync_brain_msg_copy_context *ctx = context;
+	struct dsync_brain_new_msg *msg;
+
+	if (!success) {
+		/* mark the guid instance invalid and try again later */
+		msg = array_idx_modifiable(&ctx->iter->new_msgs, ctx->msg_idx);
+		i_assert(msg->saved);
+		msg->saved = FALSE;
+
+		if (ctx->iter->next_new_msg > ctx->msg_idx)
+			ctx->iter->next_new_msg = ctx->msg_idx;
+
+		dsync_brain_sync_remove_guid_instance(ctx->iter, msg);
+	}
+
+	if (--ctx->iter->copy_results_left == 0 && !ctx->iter->adding_msgs)
+		dsync_brain_msg_sync_add_new_msgs(ctx->iter);
+	i_free(ctx);
+}
+
+static int
+dsync_brain_msg_sync_add_new_msg(struct dsync_brain_msg_iter *dest_iter,
+				 const mailbox_guid_t *src_mailbox,
+				 unsigned int msg_idx,
+				 struct dsync_brain_new_msg *msg)
+{
+	struct dsync_brain_msg_save_context *save_ctx;
+	struct dsync_brain_msg_copy_context *copy_ctx;
+	struct dsync_brain_msg_iter *src_iter;
+	const struct dsync_brain_guid_instance *inst;
+	const struct dsync_brain_mailbox *inst_box;
+
+	msg->saved = TRUE;
+
+	inst = hash_table_lookup(dest_iter->guid_hash, msg->msg->guid);
+	if (inst != NULL) {
+		/* we can save this by copying an existing message */
+		inst_box = array_idx(&dest_iter->sync->mailboxes,
+				     inst->mailbox_idx);
+
+		copy_ctx = i_new(struct dsync_brain_msg_copy_context, 1);
+		copy_ctx->iter = dest_iter;
+		copy_ctx->msg_idx = msg_idx;
+
+		dest_iter->copy_results_left++;
+		dest_iter->adding_msgs = TRUE;
+		dsync_worker_msg_copy(dest_iter->worker,
+				      &inst_box->box.mailbox_guid,
+				      inst->uid, msg->msg,
+				      dsync_brain_copy_callback, copy_ctx);
+		dest_iter->adding_msgs = FALSE;
+	} else {
+		src_iter = dest_iter == dest_iter->sync->dest_msg_iter ?
+			dest_iter->sync->src_msg_iter :
+			dest_iter->sync->dest_msg_iter;
+
+		save_ctx = i_new(struct dsync_brain_msg_save_context, 1);
+		save_ctx->iter = dest_iter;
+		save_ctx->msg = msg->msg;
+		save_ctx->mailbox_idx = dest_iter->mailbox_idx;
+
+		dest_iter->save_results_left++;
+		dest_iter->adding_msgs = TRUE;
+		dsync_worker_msg_get(src_iter->worker, src_mailbox,
+				     msg->orig_uid, msg_get_callback, save_ctx);
+		dest_iter->adding_msgs = FALSE;
+		if (dsync_worker_output_flush(src_iter->worker) < 0)
+			return -1;
+		if (dsync_worker_is_output_full(dest_iter->worker)) {
+			/* see if the output becomes less full by flushing */
+			if (dsync_worker_output_flush(dest_iter->worker) < 0)
+				return -1;
+		}
+	}
+	return dsync_worker_is_output_full(dest_iter->worker) ? 0 : 1;
+}
+
+static bool
+dsync_brain_mailbox_add_new_msgs(struct dsync_brain_msg_iter *iter,
+				 const mailbox_guid_t *mailbox_guid)
+{
+	struct dsync_brain_new_msg *msgs;
+	unsigned int msg_count;
+	bool ret = TRUE;
+
+	msgs = array_get_modifiable(&iter->new_msgs, &msg_count);
+	while (iter->next_new_msg < msg_count) {
+		struct dsync_brain_new_msg *msg = &msgs[iter->next_new_msg];
+
+		if (msg->mailbox_idx != iter->mailbox_idx) {
+			i_assert(msg->mailbox_idx > iter->mailbox_idx);
+			ret = FALSE;
+			break;
+		}
+		iter->next_new_msg++;
+
+		if (msg->saved)
+			continue;
+		if (dsync_brain_msg_sync_add_new_msg(iter, mailbox_guid,
+						     iter->next_new_msg - 1,
+						     msg) <= 0) {
+			/* failed / continue later */
+			break;
+		}
+	}
+	if (iter->next_new_msg == msg_count)
+		ret = FALSE;
+
+	/* flush copy commands */
+	if (dsync_worker_output_flush(iter->worker) > 0 && ret) {
+		/* we have more space again, continue */
+		return dsync_brain_mailbox_add_new_msgs(iter, mailbox_guid);
+	} else {
+		return ret;
+	}
+}
+
+static void
+dsync_brain_mailbox_save_conflicts(struct dsync_brain_msg_iter *iter)
+{
+	const struct dsync_brain_uid_conflict *conflicts;
+	unsigned int i, count;
+
+	conflicts = array_get(&iter->uid_conflicts, &count);
+	for (i = iter->next_conflict; i < count; i++) {
+		if (conflicts[i].mailbox_idx != iter->mailbox_idx)
+			break;
+
+		dsync_worker_msg_update_uid(iter->worker, conflicts[i].old_uid,
+					    conflicts[i].new_uid);
+	}
+	iter->next_conflict = i;
+}
+
+static void
+dsync_brain_msg_sync_finish(struct dsync_brain_msg_iter *iter)
+{
+	struct dsync_brain_mailbox_sync *sync = iter->sync;
+
+	i_assert(sync->brain->state == DSYNC_STATE_SYNC_MSGS);
+
+	iter->msgs_sent = TRUE;
+
+	/* done with all mailboxes from this iter */
+	dsync_worker_set_input_callback(iter->worker, NULL, NULL);
+
+	if (sync->src_msg_iter->msgs_sent &&
+	    sync->dest_msg_iter->msgs_sent &&
+	    sync->src_msg_iter->save_results_left == 0 &&
+	    sync->dest_msg_iter->save_results_left == 0 &&
+	    dsync_worker_output_flush(sync->dest_worker) > 0 &&
+	    dsync_worker_output_flush(sync->src_worker) > 0) {
+		dsync_worker_set_output_callback(sync->src_msg_iter->worker,
+						 NULL, NULL);
+		dsync_worker_set_output_callback(sync->dest_msg_iter->worker,
+						 NULL, NULL);
+		sync->brain->state++;
+		dsync_brain_sync(sync->brain);
+	}
+}
+
+static bool
+dsync_brain_msg_sync_select_mailbox(struct dsync_brain_msg_iter *iter)
+{
+	const struct dsync_brain_mailbox *mailbox;
+
+	while (iter->mailbox_idx < array_count(&iter->sync->mailboxes)) {
+		if (array_count(&iter->new_msgs) == 0 &&
+		    array_count(&iter->uid_conflicts) == 0) {
+			/* optimization: don't even bother selecting this
+			   mailbox */
+			iter->mailbox_idx++;
+			continue;
+		}
+
+		mailbox = array_idx(&iter->sync->mailboxes, iter->mailbox_idx);
+		dsync_worker_select_mailbox(iter->worker, &mailbox->box);
+		return TRUE;
+	}
+	dsync_brain_msg_sync_finish(iter);
+	return FALSE;
+}
+
+static void
+dsync_brain_msg_sync_add_new_msgs(struct dsync_brain_msg_iter *iter)
+{
+	const struct dsync_brain_mailbox *mailbox;
+	const mailbox_guid_t *mailbox_guid;
+
+	if (iter->msgs_sent) {
+		dsync_brain_msg_sync_finish(iter);
+		return;
+	}
+
+	do {
+		mailbox = array_idx(&iter->sync->mailboxes, iter->mailbox_idx);
+		mailbox_guid = &mailbox->box.mailbox_guid;
+		if (dsync_brain_mailbox_add_new_msgs(iter, mailbox_guid)) {
+			/* continue later */
+			return;
+		}
+
+		/* all messages saved for this mailbox. continue with saving
+		   its conflicts and waiting for copies to finish. */
+		dsync_brain_mailbox_save_conflicts(iter);
+		if (iter->save_results_left > 0 ||
+		    iter->copy_results_left > 0) {
+			/* wait for saves/copies to finish */
+			return;
+		}
+
+		/* done with this mailbox, try the next one */
+		iter->mailbox_idx++;
+	} while (dsync_brain_msg_sync_select_mailbox(iter));
+}
+
+static void dsync_worker_new_msg_output(void *context)
+{
+	struct dsync_brain_msg_iter *iter = context;
+
+	dsync_brain_msg_sync_add_new_msgs(iter);
+}
+
+static int dsync_brain_new_msg_cmp(const struct dsync_brain_new_msg *m1,
+				   const struct dsync_brain_new_msg *m2)
+{
+	if (m1->mailbox_idx < m2->mailbox_idx)
+		return -1;
+	if (m1->mailbox_idx > m2->mailbox_idx)
+		return 1;
+
+	if (m1->msg->uid < m2->msg->uid)
+		return -1;
+	if (m1->msg->uid > m2->msg->uid)
+		return 1;
+	return 0;
+}
+
+static int
+dsync_brain_uid_conflict_cmp(const struct dsync_brain_uid_conflict *c1,
+			     const struct dsync_brain_uid_conflict *c2)
+{
+	if (c1->mailbox_idx < c2->mailbox_idx)
+	       return -1;
+	if (c1->mailbox_idx < c2->mailbox_idx)
+		return 1;
+
+	if (c1->new_uid < c2->new_uid)
+		return -1;
+	if (c1->new_uid > c2->new_uid)
+		return 1;
+	return 0;
+}
+
+static void
+dsync_brain_msg_iter_sync_new_msgs(struct dsync_brain_msg_iter *iter)
+{
+	iter->mailbox_idx = 0;
+
+	/* sort input by 1) mailbox, 2) new message UID */
+	array_sort(&iter->new_msgs, dsync_brain_new_msg_cmp);
+	array_sort(&iter->uid_conflicts, dsync_brain_uid_conflict_cmp);
+
+	dsync_worker_set_input_callback(iter->worker, NULL, iter);
+	dsync_worker_set_output_callback(iter->worker,
+					 dsync_worker_new_msg_output, iter);
+
+	if (dsync_brain_msg_sync_select_mailbox(iter))
+		dsync_brain_msg_sync_add_new_msgs(iter);
+}
+
+void dsync_brain_msg_sync_new_msgs(struct dsync_brain_mailbox_sync *sync)
+{
+	dsync_brain_msg_iter_sync_new_msgs(sync->src_msg_iter);
+	dsync_brain_msg_iter_sync_new_msgs(sync->dest_msg_iter);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-brain-msgs.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,537 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+/* This code synchronizes messages in all mailboxes between two workers.
+   The "src" and "dest" terms don't really have anything to do with reality,
+   they're both treated equal.
+
+   1. Iterate through all messages in all (wanted) mailboxes. The mailboxes
+      are iterated in the same order and messages in ascending order.
+      All of the expunged messages at the end of mailbox (i.e.
+      last_existing_uid+1 .. next_uid-1) are also returned with
+      DSYNC_MAIL_FLAG_EXPUNGED set. We only care about the end of the mailbox,
+      because we can detect UID conflicts for messages in the middle by looking
+      at the next existing message and seeing if it has UID conflict.
+   2. For each seen non-expunged message, save it to GUID instance hash table:
+      message GUID => linked list of { uid, mailbox }
+   3. Each message in a mailbox is matched between the two workers as long as
+      both have messages left (the last ones may be expunged).
+      The possibilities are:
+
+      i) We don't know the GUIDs of both messages:
+
+	a) Message is expunged in both. Do nothing.
+	b) Message is expunged in only one of them. If there have been no UID
+	   conflicts seen so far, expunge the message in the other one.
+	   Otherwise, give the existing a message a new UID (at step 6).
+
+      ii) We know GUIDs of both messages (one/both of them may be expunged):
+
+	a) Messages have conflicting GUIDs. Give new UIDs for the non-expunged
+	   message(s) (at step 6).
+	b) Messages have matching GUIDs and one of them is expunged.
+	   Expunge also the other one. (We don't need to care about previous
+	   UID conflicts here, because we know this message is the same with
+	   both workers, since they have the same GUID.)
+	c) Messages have matching GUIDs and both of them exist. Sync flags from
+	   whichever has the higher modseq. If both modseqs equal but flags
+	   don't, pick the one that has more flags. If even the flag count is
+	   the same, just pick one of them.
+   4. One of the workers may messages left in the mailbox. Copy these
+      (non-expunged) messages to the other worker (at step 6).
+   5. If there are more mailboxes left, go to next one and goto 2.
+
+   6. Copy the new messages and give new UIDs to conflicting messages.
+      This code exists in dsync-brain-msgs-new.c
+*/
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "dsync-worker.h"
+#include "dsync-brain-private.h"
+
+static void dsync_brain_guid_add(struct dsync_brain_msg_iter *iter)
+{
+	struct dsync_brain_guid_instance *inst, *prev_inst;
+
+	if ((iter->msg.flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0)
+		return;
+
+	inst = p_new(iter->sync->pool, struct dsync_brain_guid_instance, 1);
+	inst->mailbox_idx = iter->mailbox_idx;
+	inst->uid = iter->msg.uid;
+
+	prev_inst = hash_table_lookup(iter->guid_hash, iter->msg.guid);
+	if (prev_inst == NULL) {
+		hash_table_insert(iter->guid_hash,
+				  p_strdup(iter->sync->pool, iter->msg.guid),
+				  inst);
+	} else {
+		inst->next = prev_inst->next;
+		prev_inst->next = inst;
+	}
+}
+
+static int dsync_brain_msg_iter_next(struct dsync_brain_msg_iter *iter)
+{
+	int ret = 1;
+
+	if (iter->msg.guid == NULL) {
+		ret = dsync_worker_msg_iter_next(iter->iter,
+						 &iter->mailbox_idx,
+						 &iter->msg);
+		if (ret > 0)
+			dsync_brain_guid_add(iter);
+	}
+
+	if (iter->sync->wanted_mailbox_idx != iter->mailbox_idx) {
+		/* finished with this mailbox */
+		return -1;
+	}
+	return ret;
+}
+
+static int
+dsync_brain_msg_iter_skip_mailbox(struct dsync_brain_mailbox_sync *sync)
+{
+	int ret;
+
+	while ((ret = dsync_brain_msg_iter_next(sync->src_msg_iter)) > 0)
+		sync->src_msg_iter->msg.guid = NULL;
+	if (ret == 0)
+		return 0;
+
+	while ((ret = dsync_brain_msg_iter_next(sync->dest_msg_iter)) > 0)
+		sync->dest_msg_iter->msg.guid = NULL;
+	if (ret == 0)
+		return 0;
+
+	sync->skip_mailbox = FALSE;
+	return -1;
+}
+
+static int dsync_brain_msg_iter_next_pair(struct dsync_brain_mailbox_sync *sync)
+{
+	int ret1, ret2;
+
+	if (sync->skip_mailbox) {
+		if (dsync_brain_msg_iter_skip_mailbox(sync) == 0)
+			return 0;
+	}
+
+	ret1 = dsync_brain_msg_iter_next(sync->src_msg_iter);
+	ret2 = dsync_brain_msg_iter_next(sync->dest_msg_iter);
+	if (ret1 == 0 || ret2 == 0) {
+		/* make sure we iterate through everything in both iterators
+		   (even if it might not seem necessary, because proxy
+		   requires it) */
+		return 0;
+	}
+	if (ret1 < 0 || ret2 < 0)
+		return -1;
+	return 1;
+}
+
+static void
+dsync_brain_msg_sync_save(struct dsync_brain_msg_iter *iter,
+			  unsigned int mailbox_idx,
+			  const struct dsync_message *msg)
+{
+	struct dsync_brain_new_msg *new_msg;
+
+	if ((msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0)
+		return;
+
+	new_msg = array_append_space(&iter->new_msgs);
+	new_msg->mailbox_idx = mailbox_idx;
+	new_msg->orig_uid = msg->uid;
+	new_msg->msg = dsync_message_dup(iter->sync->pool, msg);
+}
+
+static void
+dsync_brain_msg_sync_conflict(struct dsync_brain_msg_iter *conflict_iter,
+			      struct dsync_brain_msg_iter *save_iter,
+			      const struct dsync_message *msg)
+{
+	struct dsync_brain_uid_conflict *conflict;
+	struct dsync_brain_new_msg *new_msg;
+	struct dsync_brain_mailbox *brain_box;
+	uint32_t new_uid;
+
+	brain_box = array_idx_modifiable(&save_iter->sync->mailboxes,
+					 save_iter->mailbox_idx);
+
+	if (save_iter->sync->brain->backup) {
+		i_warning("Destination mailbox %s has been modified, "
+			  "need to recreate it before we can continue syncing",
+			  brain_box->box.name);
+		dsync_worker_delete_mailbox(save_iter->sync->brain->dest_worker,
+					    &brain_box->box);
+		save_iter->sync->brain->unexpected_changes = TRUE;
+		save_iter->sync->skip_mailbox = TRUE;
+		return;
+	}
+
+	new_uid = brain_box->box.uid_next++;
+
+	conflict = array_append_space(&conflict_iter->uid_conflicts);
+	conflict->mailbox_idx = conflict_iter->mailbox_idx;
+	conflict->old_uid = msg->uid;
+	conflict->new_uid = new_uid;
+
+	new_msg = array_append_space(&save_iter->new_msgs);
+	new_msg->mailbox_idx = save_iter->mailbox_idx;
+	new_msg->orig_uid = msg->uid;
+	new_msg->msg = dsync_message_dup(save_iter->sync->pool, msg);
+	new_msg->msg->uid = new_uid;
+}
+
+static int
+dsync_message_flag_importance_cmp(const struct dsync_message *m1,
+				  const struct dsync_message *m2)
+{
+	unsigned int i, count1, count2;
+
+	if (m1->modseq > m2->modseq)
+		return -1;
+	else if (m1->modseq < m2->modseq)
+		return 1;
+
+	if (m1->flags == m2->flags &&
+	    dsync_keyword_list_equals(m1->keywords, m2->keywords))
+		return 0;
+
+	/* modseqs match, but flags aren't the same. pick the one that
+	   has more flags. */
+	count1 = str_array_length(m1->keywords);
+	count2 = str_array_length(m2->keywords);
+	for (i = 1; i != MAIL_RECENT; i <<= 1) {
+		if ((m1->flags & i) != 0)
+			count1++;
+		if ((m2->flags & i) != 0)
+			count2++;
+	}
+	if (count1 > count2)
+		return -1;
+	else if (count1 < count2)
+		return 1;
+
+	/* they even have the same number of flags. don't bother with further
+	   guessing, just pick the first one. */
+	return -1;
+}
+
+static void dsync_brain_msg_sync_existing(struct dsync_brain_mailbox_sync *sync,
+					  struct dsync_message *src_msg,
+					  struct dsync_message *dest_msg)
+{
+	int ret;
+
+	ret = dsync_message_flag_importance_cmp(src_msg, dest_msg);
+	if (ret < 0 || (sync->brain->backup && ret > 0))
+		dsync_worker_msg_update_metadata(sync->dest_worker, src_msg);
+	else if (ret > 0)
+		dsync_worker_msg_update_metadata(sync->src_worker, dest_msg);
+}
+
+static int dsync_brain_msg_sync_pair(struct dsync_brain_mailbox_sync *sync)
+{
+	struct dsync_message *src_msg = &sync->src_msg_iter->msg;
+	struct dsync_message *dest_msg = &sync->dest_msg_iter->msg;
+	const char *src_guid, *dest_guid;
+	unsigned char guid_128_data[GUID_128_SIZE * 2 + 1];
+	bool src_expunged, dest_expunged;
+
+	i_assert(sync->src_msg_iter->mailbox_idx ==
+		 sync->dest_msg_iter->mailbox_idx);
+
+	src_expunged = (src_msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0;
+	dest_expunged = (dest_msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0;
+
+	/* If a message is expunged, it's guaranteed to have a 128bit GUID.
+	   If the other message isn't expunged, we'll need to convert its GUID
+	   to the 128bit GUID form (if it's not already) so that we can compare
+	   them. */
+	if (src_expunged) {
+		src_guid = src_msg->guid;
+		dest_guid = dsync_get_guid_128_str(dest_msg->guid,
+						   guid_128_data,
+						   sizeof(guid_128_data));
+	} else if (dest_expunged) {
+		src_guid = dsync_get_guid_128_str(src_msg->guid, guid_128_data,
+						  sizeof(guid_128_data));
+		dest_guid = dest_msg->guid;
+	} else {
+		src_guid = src_msg->guid;
+		dest_guid = dest_msg->guid;
+	}
+
+	/* FIXME: checking for sync->uid_conflict isn't fully reliable here.
+	   we should be checking if the next matching message pair has a
+	   conflict, not if the previous pair had one. */
+	if (src_msg->uid < dest_msg->uid) {
+		/* message has been expunged from dest. */
+		if (src_expunged) {
+			/* expunged from source already */
+		} else if (sync->uid_conflict || sync->brain->backup) {
+			/* update uid src, copy to dest */
+			dsync_brain_msg_sync_conflict(sync->src_msg_iter,
+						      sync->dest_msg_iter,
+						      src_msg);
+		} else {
+			/* expunge from source */
+			dsync_worker_msg_expunge(sync->src_worker,
+						 src_msg->uid);
+		}
+		src_msg->guid = NULL;
+		return 0;
+	} else if (src_msg->uid > dest_msg->uid) {
+		/* message has been expunged from src. */
+		if (dest_expunged) {
+			/* expunged from dest already */
+		} else if (sync->uid_conflict && !sync->brain->backup) {
+			/* update uid in dest, copy to src */
+			dsync_brain_msg_sync_conflict(sync->dest_msg_iter,
+						      sync->src_msg_iter,
+						      dest_msg);
+		} else {
+			/* expunge from dest */
+			dsync_worker_msg_expunge(sync->dest_worker,
+						 dest_msg->uid);
+		}
+		dest_msg->guid = NULL;
+		return 0;
+	}
+
+	/* UIDs match, but do GUIDs? If either of the GUIDs aren't set, it
+	   means that either the storage doesn't support GUIDs or we're
+	   handling an old-style expunge record. In that case just assume
+	   they match. */
+	if (strcmp(src_guid, dest_guid) != 0 &&
+	    *src_guid != '\0' && *dest_guid != '\0') {
+		/* UID conflict. give new UIDs to messages in both src and
+		   dest (if they're not expunged already) */
+		sync->uid_conflict = TRUE;
+		if (!dest_expunged) {
+			dsync_brain_msg_sync_conflict(sync->dest_msg_iter,
+						      sync->src_msg_iter,
+						      dest_msg);
+		}
+		if (!src_expunged) {
+			dsync_brain_msg_sync_conflict(sync->src_msg_iter,
+						      sync->dest_msg_iter,
+						      src_msg);
+		}
+	} else if (dest_expunged) {
+		/* message expunged from destination */
+		if (src_expunged) {
+			/* expunged from source already */
+		} else if (sync->brain->backup) {
+			dsync_brain_msg_sync_conflict(sync->src_msg_iter,
+						      sync->dest_msg_iter,
+						      src_msg);
+		} else {
+			dsync_worker_msg_expunge(sync->src_worker,
+						 src_msg->uid);
+		}
+	} else if (src_expunged) {
+		/* message expunged from source, expunge from destination too */
+		dsync_worker_msg_expunge(sync->dest_worker, dest_msg->uid);
+	} else {
+		/* message exists in both source and dest, sync metadata */
+		dsync_brain_msg_sync_existing(sync, src_msg, dest_msg);
+	}
+	src_msg->guid = NULL;
+	dest_msg->guid = NULL;
+	return 0;
+}
+
+static bool dsync_brain_msg_sync_mailbox_end(struct dsync_brain_msg_iter *iter1,
+					     struct dsync_brain_msg_iter *iter2)
+{
+	int ret;
+
+	while ((ret = dsync_brain_msg_iter_next(iter1)) > 0) {
+		dsync_brain_msg_sync_save(iter2, iter1->mailbox_idx,
+					  &iter1->msg);
+		iter1->msg.guid = NULL;
+	}
+	return ret < 0;
+}
+
+static bool
+dsync_brain_msg_sync_mailbox_more(struct dsync_brain_mailbox_sync *sync)
+{
+	int ret;
+
+	while ((ret = dsync_brain_msg_iter_next_pair(sync)) > 0) {
+		if (dsync_brain_msg_sync_pair(sync) < 0)
+			break;
+		if (dsync_worker_is_output_full(sync->dest_worker)) {
+			if (dsync_worker_output_flush(sync->dest_worker) <= 0)
+				return FALSE;
+		}
+	}
+ 	if (ret == 0)
+		return FALSE;
+
+	/* finished syncing messages in this mailbox that exist in both source
+	   and destination. if there are messages left, we can't reliably know
+	   if they should be expunged, so just copy them to the other side. */
+	if (!sync->brain->backup) {
+		if (!dsync_brain_msg_sync_mailbox_end(sync->dest_msg_iter,
+						      sync->src_msg_iter))
+			return FALSE;
+	}
+	if (!dsync_brain_msg_sync_mailbox_end(sync->src_msg_iter,
+					      sync->dest_msg_iter))
+		return FALSE;
+
+	/* done with this mailbox. the same iterator is still used for
+	   getting messages from other mailboxes. */
+	return TRUE;
+}
+
+void dsync_brain_msg_sync_more(struct dsync_brain_mailbox_sync *sync)
+{
+	const struct dsync_brain_mailbox *mailboxes;
+	unsigned int count, mailbox_idx = 0;
+
+	mailboxes = array_get(&sync->mailboxes, &count);
+	while (dsync_brain_msg_sync_mailbox_more(sync)) {
+		/* sync the next mailbox */
+		sync->uid_conflict = FALSE;
+		mailbox_idx = ++sync->wanted_mailbox_idx;
+		if (mailbox_idx >= count)
+			break;
+
+		dsync_worker_select_mailbox(sync->src_worker,
+			&mailboxes[mailbox_idx].box);
+		dsync_worker_select_mailbox(sync->dest_worker,
+			&mailboxes[mailbox_idx].box);
+	}
+	if (mailbox_idx < count) {
+		/* output buffer is full */
+		return;
+	}
+
+	/* finished with all mailboxes. */
+	dsync_worker_set_input_callback(sync->src_msg_iter->worker, NULL, NULL);
+	dsync_worker_set_output_callback(sync->src_msg_iter->worker, NULL, NULL);
+	dsync_worker_set_input_callback(sync->dest_msg_iter->worker, NULL, NULL);
+	dsync_worker_set_output_callback(sync->dest_msg_iter->worker, NULL, NULL);
+
+	if (dsync_worker_msg_iter_deinit(&sync->src_msg_iter->iter) < 0 ||
+	    dsync_worker_msg_iter_deinit(&sync->dest_msg_iter->iter) < 0) {
+		dsync_brain_fail(sync->brain);
+		return;
+	}
+
+	dsync_brain_msg_sync_new_msgs(sync);
+}
+
+static void dsync_worker_msg_callback(void *context)
+{
+	struct dsync_brain_mailbox_sync *sync = context;
+
+	dsync_brain_msg_sync_more(sync);
+}
+
+static struct dsync_brain_msg_iter *
+dsync_brain_msg_iter_init(struct dsync_brain_mailbox_sync *sync,
+			  struct dsync_worker *worker,
+			  const mailbox_guid_t mailboxes[],
+			  unsigned int mailbox_count)
+{
+	struct dsync_brain_msg_iter *iter;
+
+	iter = p_new(sync->pool, struct dsync_brain_msg_iter, 1);
+	iter->sync = sync;
+	iter->worker = worker;
+	i_array_init(&iter->uid_conflicts, 128);
+	i_array_init(&iter->new_msgs, 128);
+	iter->guid_hash = hash_table_create(default_pool, sync->pool, 10000,
+					    strcase_hash,
+					    (hash_cmp_callback_t *)strcasecmp);
+
+	iter->iter = dsync_worker_msg_iter_init(worker, mailboxes,
+						mailbox_count);
+	dsync_worker_set_input_callback(worker,
+					dsync_worker_msg_callback, sync);
+	dsync_worker_set_output_callback(worker,
+					 dsync_worker_msg_callback, sync);
+	if (mailbox_count > 0) {
+		const struct dsync_brain_mailbox *first;
+
+		first = array_idx(&sync->mailboxes, 0);
+		dsync_worker_select_mailbox(worker, &first->box);
+	}
+	return iter;
+}
+
+static void dsync_brain_msg_iter_deinit(struct dsync_brain_msg_iter *iter)
+{
+	if (iter->iter != NULL)
+		(void)dsync_worker_msg_iter_deinit(&iter->iter);
+
+	hash_table_destroy(&iter->guid_hash);
+	array_free(&iter->uid_conflicts);
+	array_free(&iter->new_msgs);
+}
+
+static void
+get_mailbox_guids(const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes,
+		  ARRAY_TYPE(mailbox_guid) *guids)
+{
+	const struct dsync_brain_mailbox *brain_box;
+
+	t_array_init(guids, array_count(mailboxes));
+	array_foreach(mailboxes, brain_box)
+		array_append(guids, &brain_box->box.mailbox_guid, 1);
+}
+
+struct dsync_brain_mailbox_sync *
+dsync_brain_msg_sync_init(struct dsync_brain *brain,
+			  const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes)
+{
+	struct dsync_brain_mailbox_sync *sync;
+	pool_t pool;
+
+	pool = pool_alloconly_create("dsync brain mailbox sync", 1024*256);
+	sync = p_new(pool, struct dsync_brain_mailbox_sync, 1);
+	sync->pool = pool;
+	sync->brain = brain;
+	sync->src_worker = brain->src_worker;
+	sync->dest_worker = brain->dest_worker;
+
+	p_array_init(&sync->mailboxes, pool, array_count(mailboxes));
+	array_append_array(&sync->mailboxes, mailboxes);
+	T_BEGIN {
+		ARRAY_TYPE(mailbox_guid) guids_arr;
+		const mailbox_guid_t *guids;
+		unsigned int count;
+
+		get_mailbox_guids(mailboxes, &guids_arr);
+
+		/* initialize message iteration on both workers */
+		guids = array_get(&guids_arr, &count);
+		sync->src_msg_iter =
+			dsync_brain_msg_iter_init(sync, brain->src_worker,
+						  guids, count);
+		sync->dest_msg_iter =
+			dsync_brain_msg_iter_init(sync, brain->dest_worker,
+						  guids, count);
+	} T_END;
+	return sync;
+}
+
+void dsync_brain_msg_sync_deinit(struct dsync_brain_mailbox_sync **_sync)
+{
+	struct dsync_brain_mailbox_sync *sync = *_sync;
+
+	*_sync = NULL;
+
+	dsync_brain_msg_iter_deinit(sync->src_msg_iter);
+	dsync_brain_msg_iter_deinit(sync->dest_msg_iter);
+	pool_unref(&sync->pool);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-brain-private.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,144 @@
+#ifndef DSYNC_BRAIN_PRIVATE_H
+#define DSYNC_BRAIN_PRIVATE_H
+
+#include "dsync-data.h"
+#include "dsync-brain.h"
+
+enum dsync_state {
+	DSYNC_STATE_GET_MAILBOXES = 0,
+	DSYNC_STATE_GET_SUBSCRIPTIONS,
+	DSYNC_STATE_SYNC_MAILBOXES,
+	DSYNC_STATE_SYNC_SUBSCRIPTIONS,
+	DSYNC_STATE_SYNC_MSGS,
+	DSYNC_STATE_SYNC_MSGS_FLUSH,
+	DSYNC_STATE_SYNC_MSGS_FLUSH2,
+	DSYNC_STATE_SYNC_UPDATE_MAILBOXES,
+	DSYNC_STATE_SYNC_FLUSH,
+	DSYNC_STATE_SYNC_FLUSH2,
+	DSYNC_STATE_SYNC_END
+};
+
+struct dsync_brain_mailbox_list {
+	pool_t pool;
+	struct dsync_brain *brain;
+	struct dsync_worker *worker;
+	struct dsync_worker_mailbox_iter *iter;
+	ARRAY_TYPE(dsync_mailbox) mailboxes;
+	ARRAY_TYPE(dsync_mailbox) dirs;
+};
+
+struct dsync_brain_subs_list {
+	pool_t pool;
+	struct dsync_brain *brain;
+	struct dsync_worker *worker;
+	struct dsync_worker_subs_iter *iter;
+	ARRAY_DEFINE(subscriptions, struct dsync_worker_subscription);
+	ARRAY_DEFINE(unsubscriptions, struct dsync_worker_unsubscription);
+};
+
+struct dsync_brain_guid_instance {
+	struct dsync_brain_guid_instance *next;
+	uint32_t uid;
+	/* mailbox index in dsync_brain_mailbox_list.mailboxes */
+	unsigned int mailbox_idx:31;
+	unsigned int failed:1;
+};
+
+struct dsync_brain_msg_iter {
+	struct dsync_brain_mailbox_sync *sync;
+	struct dsync_worker *worker;
+
+	struct dsync_worker_msg_iter *iter;
+	struct dsync_message msg;
+
+	unsigned int mailbox_idx;
+
+	/* char *guid -> struct dsync_brain_guid_instance* */
+	struct hash_table *guid_hash;
+
+	ARRAY_DEFINE(new_msgs, struct dsync_brain_new_msg);
+	ARRAY_DEFINE(uid_conflicts, struct dsync_brain_uid_conflict);
+	unsigned int next_new_msg, next_conflict;
+
+	/* copy operations that failed. indexes point to new_msgs array */
+	unsigned int copy_results_left;
+	unsigned int save_results_left;
+
+	unsigned int msgs_sent:1;
+	unsigned int adding_msgs:1;
+};
+
+struct dsync_brain_uid_conflict {
+	uint32_t mailbox_idx;
+	uint32_t old_uid, new_uid;
+};
+
+struct dsync_brain_new_msg {
+	unsigned int mailbox_idx:30;
+	/* TRUE if it currently looks like message has been saved/copied.
+	   if copying fails, it sets this back to FALSE and updates
+	   iter->next_new_msg. */
+	unsigned int saved:1;
+	uint32_t orig_uid;
+	struct dsync_message *msg;
+};
+
+struct dsync_brain_mailbox {
+	struct dsync_mailbox box;
+	struct dsync_mailbox *src;
+	struct dsync_mailbox *dest;
+};
+ARRAY_DEFINE_TYPE(dsync_brain_mailbox, struct dsync_brain_mailbox);
+
+struct dsync_brain_mailbox_sync {
+	struct dsync_brain *brain;
+	pool_t pool;
+
+	ARRAY_TYPE(dsync_brain_mailbox) mailboxes;
+	unsigned int wanted_mailbox_idx;
+
+	struct dsync_worker *src_worker;
+	struct dsync_worker *dest_worker;
+
+	struct dsync_brain_msg_iter *src_msg_iter;
+	struct dsync_brain_msg_iter *dest_msg_iter;
+
+	unsigned int uid_conflict:1;
+	unsigned int skip_mailbox:1;
+};
+
+struct dsync_brain {
+	struct dsync_worker *src_worker;
+	struct dsync_worker *dest_worker;
+	char *mailbox;
+	enum dsync_brain_flags flags;
+
+	enum dsync_state state;
+
+	struct dsync_brain_mailbox_list *src_mailbox_list;
+	struct dsync_brain_mailbox_list *dest_mailbox_list;
+
+	struct dsync_brain_subs_list *src_subs_list;
+	struct dsync_brain_subs_list *dest_subs_list;
+
+	struct dsync_brain_mailbox_sync *mailbox_sync;
+	struct timeout *to;
+
+	unsigned int failed:1;
+	unsigned int verbose:1;
+	unsigned int backup:1;
+	unsigned int unexpected_changes:1;
+	unsigned int stdout_tty:1;
+};
+
+void dsync_brain_fail(struct dsync_brain *brain);
+
+struct dsync_brain_mailbox_sync *
+dsync_brain_msg_sync_init(struct dsync_brain *brain,
+			  const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes);
+void dsync_brain_msg_sync_more(struct dsync_brain_mailbox_sync *sync);
+void dsync_brain_msg_sync_deinit(struct dsync_brain_mailbox_sync **_sync);
+
+void dsync_brain_msg_sync_new_msgs(struct dsync_brain_mailbox_sync *sync);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-brain.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,917 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "dsync-worker.h"
+#include "dsync-brain-private.h"
+
+#include <unistd.h>
+
+#define DSYNC_WRONG_DIRECTION_ERROR_MSG \
+	"dsync backup: " \
+	"Looks like you're trying to run backup in wrong direction. " \
+	"Source is empty and destination is not."
+
+static void
+dsync_brain_mailbox_list_deinit(struct dsync_brain_mailbox_list **list);
+static void
+dsync_brain_subs_list_deinit(struct dsync_brain_subs_list **list);
+
+struct dsync_brain *
+dsync_brain_init(struct dsync_worker *src_worker,
+		 struct dsync_worker *dest_worker,
+		 const char *mailbox, enum dsync_brain_flags flags)
+{
+	struct dsync_brain *brain;
+
+	brain = i_new(struct dsync_brain, 1);
+	brain->src_worker = src_worker;
+	brain->dest_worker = dest_worker;
+	brain->mailbox = i_strdup(mailbox);
+	brain->flags = flags;
+	brain->verbose = (flags & DSYNC_BRAIN_FLAG_VERBOSE) != 0;
+	brain->backup = (flags & DSYNC_BRAIN_FLAG_BACKUP) != 0;
+	brain->stdout_tty = isatty(STDOUT_FILENO) > 0;
+
+	if ((flags & DSYNC_BRAIN_FLAG_VERBOSE) != 0) {
+		dsync_worker_set_verbose(src_worker);
+		dsync_worker_set_verbose(dest_worker);
+	}
+	return brain;
+}
+
+void dsync_brain_fail(struct dsync_brain *brain)
+{
+	brain->failed = TRUE;
+	io_loop_stop(current_ioloop);
+}
+
+int dsync_brain_deinit(struct dsync_brain **_brain)
+{
+	struct dsync_brain *brain = *_brain;
+	int ret = brain->failed ? -1 : 0;
+
+	if (brain->state != DSYNC_STATE_SYNC_END)
+		ret = -1;
+	if (brain->to != NULL)
+		timeout_remove(&brain->to);
+
+	if (ret < 0) {
+		/* make sure we unreference save input streams before workers
+		   are deinitialized, so they can destroy the streams */
+		dsync_worker_msg_save_cancel(brain->src_worker);
+		dsync_worker_msg_save_cancel(brain->dest_worker);
+	}
+
+	if (brain->mailbox_sync != NULL)
+		dsync_brain_msg_sync_deinit(&brain->mailbox_sync);
+
+	if (brain->src_mailbox_list != NULL)
+		dsync_brain_mailbox_list_deinit(&brain->src_mailbox_list);
+	if (brain->dest_mailbox_list != NULL)
+		dsync_brain_mailbox_list_deinit(&brain->dest_mailbox_list);
+
+	if (brain->src_subs_list != NULL)
+		dsync_brain_subs_list_deinit(&brain->src_subs_list);
+	if (brain->dest_subs_list != NULL)
+		dsync_brain_subs_list_deinit(&brain->dest_subs_list);
+
+	if (dsync_worker_has_failed(brain->src_worker) ||
+	    dsync_worker_has_failed(brain->dest_worker))
+		ret = -1;
+
+	*_brain = NULL;
+	i_free(brain->mailbox);
+	i_free(brain);
+	return ret;
+}
+
+static void dsync_brain_mailbox_list_finished(struct dsync_brain *brain)
+{
+	if (brain->src_mailbox_list->iter != NULL ||
+	    brain->dest_mailbox_list->iter != NULL)
+		return;
+
+	/* both lists are finished */
+	brain->state++;
+	dsync_brain_sync(brain);
+}
+
+static void dsync_worker_mailbox_input(void *context)
+{
+	struct dsync_brain_mailbox_list *list = context;
+	struct dsync_mailbox dsync_box, *dup_box;
+	int ret;
+
+	while ((ret = dsync_worker_mailbox_iter_next(list->iter,
+						     &dsync_box)) > 0) {
+		if (list->brain->mailbox != NULL &&
+		    strcmp(list->brain->mailbox, dsync_box.name) != 0)
+			continue;
+
+		dup_box = dsync_mailbox_dup(list->pool, &dsync_box);
+		if (!dsync_mailbox_is_noselect(dup_box))
+			array_append(&list->mailboxes, &dup_box, 1);
+		else
+			array_append(&list->dirs, &dup_box, 1);
+	}
+	if (ret < 0) {
+		/* finished listing mailboxes */
+		if (dsync_worker_mailbox_iter_deinit(&list->iter) < 0)
+			dsync_brain_fail(list->brain);
+		array_sort(&list->mailboxes, dsync_mailbox_p_guid_cmp);
+		array_sort(&list->dirs, dsync_mailbox_p_name_sha1_cmp);
+		dsync_brain_mailbox_list_finished(list->brain);
+	}
+}
+
+static struct dsync_brain_mailbox_list *
+dsync_brain_mailbox_list_init(struct dsync_brain *brain,
+			      struct dsync_worker *worker)
+{
+	struct dsync_brain_mailbox_list *list;
+	pool_t pool;
+
+	pool = pool_alloconly_create("dsync brain mailbox list", 10240);
+	list = p_new(pool, struct dsync_brain_mailbox_list, 1);
+	list->pool = pool;
+	list->brain = brain;
+	list->worker = worker;
+	list->iter = dsync_worker_mailbox_iter_init(worker);
+	p_array_init(&list->mailboxes, pool, 128);
+	p_array_init(&list->dirs, pool, 32);
+	dsync_worker_set_input_callback(worker, dsync_worker_mailbox_input,
+					list);
+	return list;
+}
+
+static void
+dsync_brain_mailbox_list_deinit(struct dsync_brain_mailbox_list **_list)
+{
+	struct dsync_brain_mailbox_list *list = *_list;
+
+	*_list = NULL;
+
+	if (list->iter != NULL)
+		(void)dsync_worker_mailbox_iter_deinit(&list->iter);
+	pool_unref(&list->pool);
+}
+
+static void dsync_brain_subs_list_finished(struct dsync_brain *brain)
+{
+	if (brain->src_subs_list->iter != NULL ||
+	    brain->dest_subs_list->iter != NULL)
+		return;
+
+	/* both lists are finished */
+	brain->state++;
+	dsync_brain_sync(brain);
+}
+
+static int
+dsync_worker_subscription_cmp(const struct dsync_worker_subscription *s1,
+			      const struct dsync_worker_subscription *s2)
+{
+	return strcmp(s1->vname, s2->vname);
+}
+
+static int
+dsync_worker_unsubscription_cmp(const struct dsync_worker_unsubscription *u1,
+				const struct dsync_worker_unsubscription *u2)
+{
+	int ret;
+
+	ret = strcmp(u1->ns_prefix, u2->ns_prefix);
+	return ret != 0 ? ret :
+		dsync_guid_cmp(&u1->name_sha1, &u2->name_sha1);
+}
+
+static void dsync_worker_subs_input(void *context)
+{
+	struct dsync_brain_subs_list *list = context;
+	struct dsync_worker_subscription subs;
+	struct dsync_worker_unsubscription unsubs;
+	int ret;
+
+	memset(&subs, 0, sizeof(subs));
+	while ((ret = dsync_worker_subs_iter_next(list->iter, &subs)) > 0) {
+		subs.vname = p_strdup(list->pool, subs.vname);
+		subs.storage_name = p_strdup(list->pool, subs.storage_name);
+		subs.ns_prefix = p_strdup(list->pool, subs.ns_prefix);
+		array_append(&list->subscriptions, &subs, 1);
+	}
+	if (ret == 0)
+		return;
+
+	memset(&unsubs, 0, sizeof(unsubs));
+	while ((ret = dsync_worker_subs_iter_next_un(list->iter,
+						     &unsubs)) > 0) {
+		unsubs.ns_prefix = p_strdup(list->pool, unsubs.ns_prefix);
+		array_append(&list->unsubscriptions, &unsubs, 1);
+	}
+
+	if (ret < 0) {
+		/* finished listing subscriptions */
+		if (dsync_worker_subs_iter_deinit(&list->iter) < 0)
+			dsync_brain_fail(list->brain);
+		array_sort(&list->subscriptions,
+			   dsync_worker_subscription_cmp);
+		array_sort(&list->unsubscriptions,
+			   dsync_worker_unsubscription_cmp);
+		dsync_brain_subs_list_finished(list->brain);
+	}
+}
+
+static struct dsync_brain_subs_list *
+dsync_brain_subs_list_init(struct dsync_brain *brain,
+			      struct dsync_worker *worker)
+{
+	struct dsync_brain_subs_list *list;
+	pool_t pool;
+
+	pool = pool_alloconly_create("dsync brain subs list", 1024*4);
+	list = p_new(pool, struct dsync_brain_subs_list, 1);
+	list->pool = pool;
+	list->brain = brain;
+	list->worker = worker;
+	list->iter = dsync_worker_subs_iter_init(worker);
+	p_array_init(&list->subscriptions, pool, 128);
+	p_array_init(&list->unsubscriptions, pool, 64);
+	dsync_worker_set_input_callback(worker, dsync_worker_subs_input, list);
+	return list;
+}
+
+static void
+dsync_brain_subs_list_deinit(struct dsync_brain_subs_list **_list)
+{
+	struct dsync_brain_subs_list *list = *_list;
+
+	*_list = NULL;
+
+	if (list->iter != NULL)
+		(void)dsync_worker_subs_iter_deinit(&list->iter);
+	pool_unref(&list->pool);
+}
+
+enum dsync_brain_mailbox_action {
+	DSYNC_BRAIN_MAILBOX_ACTION_NONE,
+	DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
+	DSYNC_BRAIN_MAILBOX_ACTION_DELETE
+};
+
+static void
+dsync_brain_mailbox_action(struct dsync_brain *brain,
+			   enum dsync_brain_mailbox_action action,
+			   struct dsync_worker *action_worker,
+			   struct dsync_mailbox *action_box)
+{
+	struct dsync_mailbox new_box;
+
+	if (brain->backup && action_worker == brain->src_worker) {
+		/* backup mode: switch actions */
+		action_worker = brain->dest_worker;
+		switch (action) {
+		case DSYNC_BRAIN_MAILBOX_ACTION_NONE:
+			break;
+		case DSYNC_BRAIN_MAILBOX_ACTION_CREATE:
+			action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
+			break;
+		case DSYNC_BRAIN_MAILBOX_ACTION_DELETE:
+			action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
+			break;
+		}
+	}
+
+	switch (action) {
+	case DSYNC_BRAIN_MAILBOX_ACTION_NONE:
+		break;
+	case DSYNC_BRAIN_MAILBOX_ACTION_CREATE:
+		new_box = *action_box;
+		new_box.uid_next = action_box->uid_validity == 0 ? 0 : 1;
+		new_box.first_recent_uid = 0;
+		new_box.highest_modseq = 0;
+		dsync_worker_create_mailbox(action_worker, &new_box);
+		break;
+	case DSYNC_BRAIN_MAILBOX_ACTION_DELETE:
+		if (!dsync_mailbox_is_noselect(action_box))
+			dsync_worker_delete_mailbox(action_worker, action_box);
+		else
+			dsync_worker_delete_dir(action_worker, action_box);
+		break;
+	}
+}
+
+static bool
+dsync_mailbox_list_is_empty(const ARRAY_TYPE(dsync_mailbox) *boxes_arr)
+{
+	struct dsync_mailbox *const *boxes;
+	unsigned int count;
+
+	boxes = array_get(boxes_arr, &count);
+	if (count == 0)
+		return TRUE;
+	if (count == 1 && strcasecmp(boxes[0]->name, "INBOX") == 0 &&
+	    boxes[0]->message_count == 0)
+		return TRUE;
+	return FALSE;
+}
+
+static void dsync_brain_sync_mailboxes(struct dsync_brain *brain)
+{
+	struct dsync_mailbox *const *src_boxes, *const *dest_boxes;
+	struct dsync_mailbox *action_box = NULL;
+	struct dsync_worker *action_worker = NULL;
+	unsigned int src, dest, src_count, dest_count;
+	enum dsync_brain_mailbox_action action;
+	bool src_deleted, dest_deleted;
+	int ret;
+
+	if (brain->backup &&
+	    dsync_mailbox_list_is_empty(&brain->src_mailbox_list->mailboxes) &&
+	    !dsync_mailbox_list_is_empty(&brain->dest_mailbox_list->mailboxes)) {
+		i_fatal(DSYNC_WRONG_DIRECTION_ERROR_MSG);
+	}
+
+	/* create/delete missing mailboxes. the mailboxes are sorted by
+	   GUID, so we can do this quickly. */
+	src_boxes = array_get(&brain->src_mailbox_list->mailboxes, &src_count);
+	dest_boxes = array_get(&brain->dest_mailbox_list->mailboxes, &dest_count);
+	for (src = dest = 0; src < src_count && dest < dest_count; ) {
+		action = DSYNC_BRAIN_MAILBOX_ACTION_NONE;
+		src_deleted = (src_boxes[src]->flags &
+			       DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
+		dest_deleted = (dest_boxes[dest]->flags &
+				DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
+		ret = dsync_mailbox_guid_cmp(src_boxes[src],
+					     dest_boxes[dest]);
+		if (ret < 0) {
+			/* exists only in source */
+			if (!src_deleted) {
+				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
+				action_worker = brain->dest_worker;
+				action_box = src_boxes[src];
+			}
+			src++;
+		} else if (ret > 0) {
+			/* exists only in dest */
+			if (!dest_deleted) {
+				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
+				action_worker = brain->src_worker;
+				action_box = dest_boxes[dest];
+			}
+			dest++;
+		} else if (src_deleted) {
+			/* delete from dest too */
+			if (!dest_deleted) {
+				action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
+				action_worker = brain->dest_worker;
+				action_box = dest_boxes[dest];
+			}
+			src++; dest++;
+		} else if (dest_deleted) {
+			/* delete from src too */
+			action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
+			action_worker = brain->src_worker;
+			action_box = src_boxes[src];
+			src++; dest++;
+		} else {
+			src++; dest++;
+		}
+		dsync_brain_mailbox_action(brain, action,
+					   action_worker, action_box);
+	}
+	for (; src < src_count; src++) {
+		if ((src_boxes[src]->flags &
+		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
+			continue;
+
+		dsync_brain_mailbox_action(brain,
+			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
+			brain->dest_worker, src_boxes[src]);
+	}
+	for (; dest < dest_count; dest++) {
+		if ((dest_boxes[dest]->flags &
+		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
+			continue;
+
+		dsync_brain_mailbox_action(brain,
+			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
+			brain->src_worker, dest_boxes[dest]);
+	}
+}
+
+static void dsync_brain_sync_dirs(struct dsync_brain *brain)
+{
+	struct dsync_mailbox *const *src_boxes, *const *dest_boxes, *action_box;
+	unsigned int src, dest, src_count, dest_count;
+	enum dsync_brain_mailbox_action action;
+	struct dsync_worker *action_worker = NULL;
+	bool src_deleted, dest_deleted;
+	int ret;
+
+	/* create/delete missing directories. */
+	src_boxes = array_get(&brain->src_mailbox_list->dirs, &src_count);
+	dest_boxes = array_get(&brain->dest_mailbox_list->dirs, &dest_count);
+	for (src = dest = 0; src < src_count && dest < dest_count; ) {
+		action = DSYNC_BRAIN_MAILBOX_ACTION_NONE;
+		action_box = NULL;
+
+		src_deleted = (src_boxes[src]->flags &
+			       DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0;
+		dest_deleted = (dest_boxes[dest]->flags &
+				DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0;
+		ret = memcmp(src_boxes[src]->name_sha1.guid,
+			     dest_boxes[dest]->name_sha1.guid,
+			     sizeof(src_boxes[src]->name_sha1.guid));
+		if (ret < 0) {
+			/* exists only in source */
+			if (!src_deleted) {
+				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
+				action_worker = brain->dest_worker;
+				action_box = src_boxes[src];
+			}
+			src++;
+		} else if (ret > 0) {
+			/* exists only in dest */
+			if (!dest_deleted) {
+				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
+				action_worker = brain->src_worker;
+				action_box = dest_boxes[dest];
+			}
+			dest++;
+		} else if (src_deleted) {
+			/* delete from dest too */
+			if (!dest_deleted) {
+				action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
+				action_worker = brain->dest_worker;
+				action_box = dest_boxes[dest];
+			}
+			src++; dest++;
+		} else if (dest_deleted) {
+			/* delete from src too */
+			action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
+			action_worker = brain->src_worker;
+			action_box = src_boxes[src];
+			src++; dest++;
+		} else {
+			src++; dest++;
+		}
+		i_assert(action_box == NULL ||
+			 dsync_mailbox_is_noselect(action_box));
+		dsync_brain_mailbox_action(brain, action,
+					   action_worker, action_box);
+	}
+	for (; src < src_count; src++) {
+		if ((src_boxes[src]->flags &
+		     DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0)
+			continue;
+
+		dsync_brain_mailbox_action(brain,
+			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
+			brain->dest_worker, src_boxes[src]);
+	}
+	for (; dest < dest_count; dest++) {
+		if ((dest_boxes[dest]->flags &
+		     DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0)
+			continue;
+
+		dsync_brain_mailbox_action(brain,
+			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
+			brain->src_worker, dest_boxes[dest]);
+	}
+}
+
+static bool
+dsync_brain_is_unsubscribed(struct dsync_brain_subs_list *list,
+			    const struct dsync_worker_subscription *subs,
+			    time_t *last_change_r)
+{
+	const struct dsync_worker_unsubscription *unsubs;
+	struct dsync_worker_unsubscription lookup;
+
+	lookup.ns_prefix = subs->ns_prefix;
+	dsync_str_sha_to_guid(subs->storage_name, &lookup.name_sha1);
+	unsubs = array_bsearch(&list->unsubscriptions, &lookup,
+			       dsync_worker_unsubscription_cmp);
+	if (unsubs == NULL) {
+		*last_change_r = 0;
+		return FALSE;
+	} else if (unsubs->last_change <= subs->last_change) {
+		*last_change_r = subs->last_change;
+		return FALSE;
+	} else {
+		*last_change_r = unsubs->last_change;
+		return TRUE;
+	}
+}
+
+static void dsync_brain_sync_subscriptions(struct dsync_brain *brain)
+{
+	const struct dsync_worker_subscription *src_subs, *dest_subs;
+	const struct dsync_worker_subscription *action_subs;
+	struct dsync_worker *action_worker;
+	unsigned int src, dest, src_count, dest_count;
+	time_t last_change;
+	bool subscribe;
+	int ret;
+
+	/* subscriptions are sorted by name. */
+	src_subs = array_get(&brain->src_subs_list->subscriptions, &src_count);
+	dest_subs = array_get(&brain->dest_subs_list->subscriptions, &dest_count);
+	for (src = dest = 0;; ) {
+		if (src == src_count) {
+			if (dest == dest_count)
+				break;
+			ret = 1;
+		} else if (dest == dest_count) {
+			ret = -1;
+		} else {
+			ret = strcmp(src_subs[src].vname,
+				     dest_subs[dest].vname);
+			if (ret == 0) {
+				src++; dest++;
+				continue;
+			}
+		}
+
+		if (ret < 0) {
+			/* subscribed only in source */
+			action_subs = &src_subs[src];
+			if (dsync_brain_is_unsubscribed(brain->dest_subs_list,
+							&src_subs[src],
+							&last_change)) {
+				action_worker = brain->src_worker;
+				subscribe = FALSE;
+			} else {
+				action_worker = brain->dest_worker;
+				subscribe = TRUE;
+			}
+			src++;
+		} else {
+			/* subscribed only in dest */
+			action_subs = &dest_subs[dest];
+			if (dsync_brain_is_unsubscribed(brain->src_subs_list,
+							&dest_subs[dest],
+							&last_change)) {
+				action_worker = brain->dest_worker;
+				subscribe = FALSE;
+			} else {
+				action_worker = brain->src_worker;
+				subscribe = TRUE;
+			}
+			dest++;
+		}
+
+		if (brain->backup && action_worker == brain->src_worker) {
+			/* backup mode: switch action */
+			action_worker = brain->dest_worker;
+			subscribe = !subscribe;
+			last_change = ioloop_time;
+		}
+		dsync_worker_set_subscribed(action_worker, action_subs->vname,
+					    last_change, subscribe);
+	}
+}
+
+static bool dsync_mailbox_has_changed_msgs(struct dsync_brain *brain,
+					   const struct dsync_mailbox *box1,
+					   const struct dsync_mailbox *box2)
+{
+	const char *name = *box1->name != '\0' ? box1->name : box2->name;
+
+	if (box1->uid_validity != box2->uid_validity) {
+		if (brain->verbose) {
+			i_info("%s: uidvalidity changed: %u != %u", name,
+			       box1->uid_validity, box2->uid_validity);
+		}
+		return TRUE;
+	}
+	if (box1->uid_next != box2->uid_next) {
+		if (brain->verbose) {
+			i_info("%s: uidnext changed: %u != %u", name,
+			       box1->uid_next, box2->uid_next);
+		}
+		return TRUE;
+	}
+	if (box1->highest_modseq != box2->highest_modseq) {
+		if (brain->verbose) {
+			i_info("%s: highest_modseq changed: %llu != %llu", name,
+			       (unsigned long long)box1->highest_modseq,
+			       (unsigned long long)box2->highest_modseq);
+		}
+		return TRUE;
+	}
+	if (box1->message_count != box2->message_count) {
+		if (brain->verbose) {
+			i_info("%s: message_count changed: %u != %u", name,
+			       box1->message_count, box2->message_count);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static bool dsync_mailbox_has_changes(struct dsync_brain *brain,
+				      const struct dsync_mailbox *box1,
+				      const struct dsync_mailbox *box2)
+{
+	if (strcmp(box1->name, box2->name) != 0)
+		return TRUE;
+	return dsync_mailbox_has_changed_msgs(brain, box1, box2);
+}
+
+static void
+dsync_brain_get_changed_mailboxes(struct dsync_brain *brain,
+				  ARRAY_TYPE(dsync_brain_mailbox) *brain_boxes,
+				  bool full_sync)
+{
+	struct dsync_mailbox *const *src_boxes, *const *dest_boxes;
+	struct dsync_brain_mailbox *brain_box;
+	unsigned int src, dest, src_count, dest_count;
+	bool src_deleted, dest_deleted;
+	int ret;
+
+	src_boxes = array_get(&brain->src_mailbox_list->mailboxes, &src_count);
+	dest_boxes = array_get(&brain->dest_mailbox_list->mailboxes, &dest_count);
+
+	for (src = dest = 0; src < src_count && dest < dest_count; ) {
+		src_deleted = (src_boxes[src]->flags &
+			       DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
+		dest_deleted = (dest_boxes[dest]->flags &
+				DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
+
+		ret = dsync_mailbox_guid_cmp(src_boxes[src], dest_boxes[dest]);
+		if (ret == 0) {
+			if ((full_sync ||
+			     dsync_mailbox_has_changes(brain, src_boxes[src],
+						       dest_boxes[dest])) &&
+			    !src_deleted && !dest_deleted) {
+				brain_box = array_append_space(brain_boxes);
+				brain_box->box = *src_boxes[src];
+
+				brain_box->box.highest_modseq =
+					I_MAX(src_boxes[src]->highest_modseq,
+					      dest_boxes[dest]->highest_modseq);
+				brain_box->box.uid_next =
+					I_MAX(src_boxes[src]->uid_next,
+					      dest_boxes[dest]->uid_next);
+				brain_box->src = src_boxes[src];
+				brain_box->dest = dest_boxes[dest];
+			}
+			src++; dest++;
+		} else if (ret < 0) {
+			/* exists only in source */
+			if (!src_deleted) {
+				brain_box = array_append_space(brain_boxes);
+				brain_box->box = *src_boxes[src];
+				brain_box->src = src_boxes[src];
+				if (brain->verbose) {
+					i_info("%s: only in source (guid=%s)",
+					       brain_box->box.name,
+					       dsync_guid_to_str(&brain_box->box.mailbox_guid));
+				}
+			}
+ 			src++;
+		} else {
+			/* exists only in dest */
+			if (!dest_deleted) {
+				brain_box = array_append_space(brain_boxes);
+				brain_box->box = *dest_boxes[dest];
+				brain_box->dest = dest_boxes[dest];
+				if (brain->verbose) {
+					i_info("%s: only in dest (guid=%s)",
+					       brain_box->box.name,
+					       dsync_guid_to_str(&brain_box->box.mailbox_guid));
+				}
+			}
+			dest++;
+		}
+	}
+	for (; src < src_count; src++) {
+		if ((src_boxes[src]->flags &
+		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
+			continue;
+
+		brain_box = array_append_space(brain_boxes);
+		brain_box->box = *src_boxes[src];
+		brain_box->src = src_boxes[src];
+		if (brain->verbose) {
+			i_info("%s: only in source (guid=%s)",
+			       brain_box->box.name,
+			       dsync_guid_to_str(&brain_box->box.mailbox_guid));
+		}
+	}
+	for (; dest < dest_count; dest++) {
+		if ((dest_boxes[dest]->flags &
+		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
+			continue;
+
+		brain_box = array_append_space(brain_boxes);
+		brain_box->box = *dest_boxes[dest];
+		brain_box->dest = dest_boxes[dest];
+		if (brain->verbose) {
+			i_info("%s: only in dest (guid=%s)",
+			       brain_box->box.name,
+			       dsync_guid_to_str(&brain_box->box.mailbox_guid));
+		}
+	}
+}
+
+static bool dsync_brain_sync_msgs(struct dsync_brain *brain)
+{
+	ARRAY_TYPE(dsync_brain_mailbox) mailboxes;
+	pool_t pool;
+	bool ret;
+
+	pool = pool_alloconly_create("dsync changed mailboxes", 10240);
+	p_array_init(&mailboxes, pool, 128);
+	dsync_brain_get_changed_mailboxes(brain, &mailboxes,
+		(brain->flags & DSYNC_BRAIN_FLAG_FULL_SYNC) != 0);
+	if (array_count(&mailboxes) > 0) {
+		brain->mailbox_sync =
+			dsync_brain_msg_sync_init(brain, &mailboxes);
+		dsync_brain_msg_sync_more(brain->mailbox_sync);
+		ret = TRUE;
+	} else {
+		ret = FALSE;
+	}
+	pool_unref(&pool);
+	return ret;
+}
+
+static void
+dsync_brain_sync_rename_mailbox(struct dsync_brain *brain,
+				const struct dsync_brain_mailbox *mailbox)
+{
+	if (mailbox->src->last_change > mailbox->dest->last_change ||
+	    brain->backup) {
+		dsync_worker_rename_mailbox(brain->dest_worker,
+					    &mailbox->box.mailbox_guid,
+					    mailbox->src);
+	} else {
+		dsync_worker_rename_mailbox(brain->src_worker,
+					    &mailbox->box.mailbox_guid,
+					    mailbox->dest);
+	}
+}
+
+static void
+dsync_brain_sync_update_mailboxes(struct dsync_brain *brain)
+{
+	const struct dsync_brain_mailbox *mailbox;
+	bool failed_changes = dsync_brain_has_unexpected_changes(brain) ||
+		dsync_worker_has_failed(brain->src_worker) ||
+		dsync_worker_has_failed(brain->dest_worker);
+
+	if (brain->mailbox_sync == NULL) {
+		/* no mailboxes changed */
+		return;
+	}
+
+	array_foreach(&brain->mailbox_sync->mailboxes, mailbox) {
+		/* don't update mailboxes if any changes had failed.
+		   for example if some messages couldn't be saved, we don't
+		   want to increase the next_uid to jump over them */
+		if (!brain->backup && !failed_changes) {
+			dsync_worker_update_mailbox(brain->src_worker,
+						    &mailbox->box);
+		}
+		if (!failed_changes) {
+			dsync_worker_update_mailbox(brain->dest_worker,
+						    &mailbox->box);
+		}
+
+		if (mailbox->src != NULL && mailbox->dest != NULL &&
+		    strcmp(mailbox->src->name, mailbox->dest->name) != 0)
+			dsync_brain_sync_rename_mailbox(brain, mailbox);
+	}
+}
+
+static void dsync_brain_worker_finished(bool success, void *context)
+{
+	struct dsync_brain *brain = context;
+
+	switch (brain->state) {
+	case DSYNC_STATE_SYNC_MSGS_FLUSH:
+	case DSYNC_STATE_SYNC_MSGS_FLUSH2:
+	case DSYNC_STATE_SYNC_FLUSH:
+	case DSYNC_STATE_SYNC_FLUSH2:
+		break;
+	default:
+		i_panic("dsync brain state=%d", brain->state);
+	}
+
+	if (!success)
+		dsync_brain_fail(brain);
+
+	brain->state++;
+	if (brain->to == NULL && (brain->flags & DSYNC_BRAIN_FLAG_LOCAL) == 0)
+		brain->to = timeout_add(0, dsync_brain_sync, brain);
+}
+
+void dsync_brain_sync(struct dsync_brain *brain)
+{
+	if (dsync_worker_has_failed(brain->src_worker) ||
+	    dsync_worker_has_failed(brain->dest_worker)) {
+		/* we can't safely continue, especially with backup */
+		return;
+	}
+
+	if (brain->to != NULL)
+		timeout_remove(&brain->to);
+	switch (brain->state) {
+	case DSYNC_STATE_GET_MAILBOXES:
+		i_assert(brain->src_mailbox_list == NULL);
+		brain->src_mailbox_list =
+			dsync_brain_mailbox_list_init(brain, brain->src_worker);
+		brain->dest_mailbox_list =
+			dsync_brain_mailbox_list_init(brain, brain->dest_worker);
+		dsync_worker_mailbox_input(brain->src_mailbox_list);
+		dsync_worker_mailbox_input(brain->dest_mailbox_list);
+		break;
+	case DSYNC_STATE_GET_SUBSCRIPTIONS:
+		i_assert(brain->src_subs_list == NULL);
+		brain->src_subs_list =
+			dsync_brain_subs_list_init(brain, brain->src_worker);
+		brain->dest_subs_list =
+			dsync_brain_subs_list_init(brain, brain->dest_worker);
+		dsync_worker_subs_input(brain->src_subs_list);
+		dsync_worker_subs_input(brain->dest_subs_list);
+		break;
+	case DSYNC_STATE_SYNC_MAILBOXES:
+		dsync_worker_set_input_callback(brain->src_worker, NULL, NULL);
+		dsync_worker_set_input_callback(brain->dest_worker, NULL, NULL);
+
+		dsync_brain_sync_mailboxes(brain);
+		dsync_brain_sync_dirs(brain);
+		brain->state++;
+		/* fall through */
+	case DSYNC_STATE_SYNC_SUBSCRIPTIONS:
+		dsync_brain_sync_subscriptions(brain);
+		brain->state++;
+		/* fall through */
+	case DSYNC_STATE_SYNC_MSGS:
+		if (dsync_brain_sync_msgs(brain))
+			break;
+		brain->state++;
+		/* no mailboxes changed */
+	case DSYNC_STATE_SYNC_MSGS_FLUSH:
+		/* wait until all saves are done, so we don't try to close
+		   the mailbox too early */
+		dsync_worker_finish(brain->src_worker,
+				    dsync_brain_worker_finished, brain);
+		dsync_worker_finish(brain->dest_worker,
+				    dsync_brain_worker_finished, brain);
+		break;
+	case DSYNC_STATE_SYNC_MSGS_FLUSH2:
+		break;
+	case DSYNC_STATE_SYNC_UPDATE_MAILBOXES:
+		dsync_brain_sync_update_mailboxes(brain);
+		brain->state++;
+		/* fall through */
+	case DSYNC_STATE_SYNC_FLUSH:
+		dsync_worker_finish(brain->src_worker,
+				    dsync_brain_worker_finished, brain);
+		dsync_worker_finish(brain->dest_worker,
+				    dsync_brain_worker_finished, brain);
+		break;
+	case DSYNC_STATE_SYNC_FLUSH2:
+		break;
+	case DSYNC_STATE_SYNC_END:
+		io_loop_stop(current_ioloop);
+		break;
+	default:
+		i_unreached();
+	}
+}
+
+void dsync_brain_sync_all(struct dsync_brain *brain)
+{
+	enum dsync_state old_state;
+
+	while (brain->state != DSYNC_STATE_SYNC_END) {
+		old_state = brain->state;
+		dsync_brain_sync(brain);
+
+		if (dsync_worker_has_failed(brain->src_worker) ||
+		    dsync_worker_has_failed(brain->dest_worker))
+			break;
+
+		i_assert(brain->state != old_state);
+	}
+}
+
+bool dsync_brain_has_unexpected_changes(struct dsync_brain *brain)
+{
+	return brain->unexpected_changes ||
+		dsync_worker_has_unexpected_changes(brain->src_worker) ||
+		dsync_worker_has_unexpected_changes(brain->dest_worker);
+}
+
+bool dsync_brain_has_failed(struct dsync_brain *brain)
+{
+	return brain->failed ||
+		dsync_worker_has_failed(brain->src_worker) ||
+		dsync_worker_has_failed(brain->dest_worker);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-brain.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,28 @@
+#ifndef DSYNC_BRAIN_H
+#define DSYNC_BRAIN_H
+
+enum dsync_brain_flags {
+	DSYNC_BRAIN_FLAG_FULL_SYNC	= 0x01,
+	DSYNC_BRAIN_FLAG_VERBOSE	= 0x02,
+	/* Run in backup mode. All changes from src are forced into dest,
+	   discarding any potential changes in dest. */
+	DSYNC_BRAIN_FLAG_BACKUP		= 0x04,
+	/* Run in "local mode". Don't use ioloop. */
+	DSYNC_BRAIN_FLAG_LOCAL		= 0x08
+};
+
+struct dsync_worker;
+
+struct dsync_brain *
+dsync_brain_init(struct dsync_worker *src_worker,
+		 struct dsync_worker *dest_worker,
+		 const char *mailbox, enum dsync_brain_flags flags);
+int dsync_brain_deinit(struct dsync_brain **brain);
+
+void dsync_brain_sync(struct dsync_brain *brain);
+void dsync_brain_sync_all(struct dsync_brain *brain);
+
+bool dsync_brain_has_unexpected_changes(struct dsync_brain *brain);
+bool dsync_brain_has_failed(struct dsync_brain *brain);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-data.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,146 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hex-binary.h"
+#include "sha1.h"
+#include "dsync-data.h"
+
+struct dsync_mailbox *
+dsync_mailbox_dup(pool_t pool, const struct dsync_mailbox *box)
+{
+	struct dsync_mailbox *dest;
+	const struct mailbox_cache_field *cache_fields = NULL;
+	struct mailbox_cache_field *dup;
+	unsigned int i, count = 0;
+
+	dest = p_new(pool, struct dsync_mailbox, 1);
+	*dest = *box;
+	dest->name = p_strdup(pool, box->name);
+
+	if (array_is_created(&box->cache_fields))
+		cache_fields = array_get(&box->cache_fields, &count);
+	if (count == 0)
+		memset(&dest->cache_fields, 0, sizeof(dest->cache_fields));
+	else {
+		p_array_init(&dest->cache_fields, pool, count);
+		for (i = 0; i < count; i++) {
+			dup = array_append_space(&dest->cache_fields);
+			*dup = cache_fields[i];
+			dup->name = p_strdup(pool, dup->name);
+		}
+	}
+	return dest;
+}
+
+struct dsync_message *
+dsync_message_dup(pool_t pool, const struct dsync_message *msg)
+{
+	struct dsync_message *dest;
+	const char **keywords;
+	unsigned int i, count;
+
+	dest = p_new(pool, struct dsync_message, 1);
+	*dest = *msg;
+	dest->guid = p_strdup(pool, msg->guid);
+	if (msg->keywords != NULL) {
+		count = str_array_length(msg->keywords);
+		keywords = p_new(pool, const char *, count+1);
+		for (i = 0; i < count; i++)
+			keywords[i] = p_strdup(pool, msg->keywords[i]);
+		dest->keywords = keywords;
+	}
+	return dest;
+}
+
+int dsync_mailbox_guid_cmp(const struct dsync_mailbox *box1,
+			   const struct dsync_mailbox *box2)
+{
+	return memcmp(box1->mailbox_guid.guid, box2->mailbox_guid.guid,
+		      sizeof(box1->mailbox_guid.guid));
+}
+
+int dsync_mailbox_p_guid_cmp(struct dsync_mailbox *const *box1,
+			     struct dsync_mailbox *const *box2)
+{
+	return dsync_mailbox_guid_cmp(*box1, *box2);
+}
+
+int dsync_mailbox_name_sha1_cmp(const struct dsync_mailbox *box1,
+				const struct dsync_mailbox *box2)
+{
+	int ret;
+
+	ret = memcmp(box1->name_sha1.guid, box2->name_sha1.guid,
+		     sizeof(box1->name_sha1.guid));
+	if (ret != 0)
+		return ret;
+
+	return strcmp(box1->name, box2->name);
+}
+
+int dsync_mailbox_p_name_sha1_cmp(struct dsync_mailbox *const *box1,
+				  struct dsync_mailbox *const *box2)
+{
+	return dsync_mailbox_name_sha1_cmp(*box1, *box2);
+}
+
+bool dsync_keyword_list_equals(const char *const *k1, const char *const *k2)
+{
+	unsigned int i;
+
+	if (k1 == NULL)
+		return k2 == NULL || k2[0] == NULL;
+	if (k2 == NULL)
+		return k1[0] == NULL;
+
+	for (i = 0;; i++) {
+		if (k1[i] == NULL)
+			return k2[i] == NULL;
+		if (k2[i] == NULL)
+			return FALSE;
+
+		if (strcasecmp(k1[i], k2[i]) != 0)
+			return FALSE;
+	}
+}
+
+bool dsync_guid_equals(const mailbox_guid_t *guid1,
+		       const mailbox_guid_t *guid2)
+{
+	return memcmp(guid1->guid, guid2->guid, sizeof(guid1->guid)) == 0;
+}
+
+int dsync_guid_cmp(const mailbox_guid_t *guid1, const mailbox_guid_t *guid2)
+{
+	return memcmp(guid1->guid, guid2->guid, sizeof(guid1->guid));
+}
+
+const char *dsync_guid_to_str(const mailbox_guid_t *guid)
+{
+	return guid_128_to_string(guid->guid);
+}
+
+const char *dsync_get_guid_128_str(const char *guid, unsigned char *dest,
+				   unsigned int dest_len)
+{
+	guid_128_t guid_128;
+	buffer_t guid_128_buf;
+
+	i_assert(dest_len >= GUID_128_SIZE * 2 + 1);
+	buffer_create_data(&guid_128_buf, dest, dest_len);
+	mail_generate_guid_128_hash(guid, guid_128);
+	if (guid_128_is_empty(guid_128))
+		return "";
+	binary_to_hex_append(&guid_128_buf, guid_128, sizeof(guid_128));
+	buffer_append_c(&guid_128_buf, '\0');
+	return guid_128_buf.data;
+}
+
+void dsync_str_sha_to_guid(const char *str, mailbox_guid_t *guid)
+{
+	unsigned char sha[SHA1_RESULTLEN];
+
+	sha1_get_digest(str, strlen(str), sha);
+	memcpy(guid->guid, sha, I_MIN(sizeof(guid->guid), sizeof(sha)));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-data.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,84 @@
+#ifndef DSYNC_DATA_H
+#define DSYNC_DATA_H
+
+#include "mail-storage.h"
+
+typedef struct {
+	guid_128_t guid;
+} mailbox_guid_t;
+ARRAY_DEFINE_TYPE(mailbox_guid, mailbox_guid_t);
+
+enum dsync_mailbox_flags {
+	DSYNC_MAILBOX_FLAG_NOSELECT		= 0x01,
+	DSYNC_MAILBOX_FLAG_DELETED_MAILBOX	= 0x02,
+	DSYNC_MAILBOX_FLAG_DELETED_DIR		= 0x04
+};
+
+struct dsync_mailbox {
+	const char *name;
+	char name_sep;
+	/* 128bit SHA1 sum of mailbox name */
+	mailbox_guid_t name_sha1;
+	/* Mailbox's GUID. Full of zero with \Noselect mailboxes. */
+	mailbox_guid_t mailbox_guid;
+
+	uint32_t uid_validity, uid_next, message_count, first_recent_uid;
+	uint64_t highest_modseq;
+	/* if mailbox is deleted, this is the deletion timestamp.
+	   otherwise it's the last rename timestamp. */
+	time_t last_change;
+	enum dsync_mailbox_flags flags;
+	ARRAY_TYPE(mailbox_cache_field) cache_fields;
+};
+ARRAY_DEFINE_TYPE(dsync_mailbox, struct dsync_mailbox *);
+#define dsync_mailbox_is_noselect(dsync_box) \
+	(((dsync_box)->flags & DSYNC_MAILBOX_FLAG_NOSELECT) != 0)
+
+/* dsync_worker_msg_iter_next() returns also all expunged messages from
+   the end of mailbox with this flag set. The GUIDs are 128 bit GUIDs saved
+   to transaction log (mail_generate_guid_128_hash()). */
+#define DSYNC_MAIL_FLAG_EXPUNGED 0x10000000
+
+struct dsync_message {
+	const char *guid;
+	uint32_t uid;
+	enum mail_flags flags;
+	/* keywords are sorted by name */
+	const char *const *keywords;
+	uint64_t modseq;
+	time_t save_date;
+};
+
+struct dsync_msg_static_data {
+	const char *pop3_uidl;
+	time_t received_date;
+	struct istream *input;
+};
+
+struct dsync_mailbox *
+dsync_mailbox_dup(pool_t pool, const struct dsync_mailbox *box);
+
+struct dsync_message *
+dsync_message_dup(pool_t pool, const struct dsync_message *msg);
+
+int dsync_mailbox_guid_cmp(const struct dsync_mailbox *box1,
+			   const struct dsync_mailbox *box2);
+int dsync_mailbox_p_guid_cmp(struct dsync_mailbox *const *box1,
+			     struct dsync_mailbox *const *box2);
+
+int dsync_mailbox_name_sha1_cmp(const struct dsync_mailbox *box1,
+				const struct dsync_mailbox *box2);
+int dsync_mailbox_p_name_sha1_cmp(struct dsync_mailbox *const *box1,
+				  struct dsync_mailbox *const *box2);
+
+bool dsync_keyword_list_equals(const char *const *k1, const char *const *k2);
+
+bool dsync_guid_equals(const mailbox_guid_t *guid1,
+		       const mailbox_guid_t *guid2);
+int dsync_guid_cmp(const mailbox_guid_t *guid1, const mailbox_guid_t *guid2);
+const char *dsync_guid_to_str(const mailbox_guid_t *guid);
+const char *dsync_get_guid_128_str(const char *guid, unsigned char *dest,
+				   unsigned int dest_len);
+void dsync_str_sha_to_guid(const char *str, mailbox_guid_t *guid);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-proxy-client.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,1191 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "aqueue.h"
+#include "fd-set-nonblock.h"
+#include "istream.h"
+#include "istream-dot.h"
+#include "ostream.h"
+#include "str.h"
+#include "strescape.h"
+#include "imap-util.h"
+#include "dsync-proxy.h"
+#include "dsync-worker-private.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define OUTBUF_THROTTLE_SIZE (1024*64)
+
+enum proxy_client_request_type {
+	PROXY_CLIENT_REQUEST_TYPE_COPY,
+	PROXY_CLIENT_REQUEST_TYPE_GET,
+	PROXY_CLIENT_REQUEST_TYPE_FINISH
+};
+
+struct proxy_client_request {
+	enum proxy_client_request_type type;
+	uint32_t uid;
+	union {
+		dsync_worker_msg_callback_t *get;
+		dsync_worker_copy_callback_t *copy;
+		dsync_worker_finish_callback_t *finish;
+	} callback;
+	void *context;
+};
+
+struct proxy_client_dsync_worker_mailbox_iter {
+	struct dsync_worker_mailbox_iter iter;
+	pool_t pool;
+};
+
+struct proxy_client_dsync_worker_subs_iter {
+	struct dsync_worker_subs_iter iter;
+	pool_t pool;
+};
+
+struct proxy_client_dsync_worker {
+	struct dsync_worker worker;
+	int fd_in, fd_out;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+	struct timeout *to, *to_input;
+
+	mailbox_guid_t selected_box_guid;
+
+	dsync_worker_save_callback_t *save_callback;
+	void *save_context;
+	struct istream *save_input;
+	struct io *save_io;
+	bool save_input_last_lf;
+
+	pool_t msg_get_pool;
+	struct dsync_msg_static_data msg_get_data;
+	ARRAY_DEFINE(request_array, struct proxy_client_request);
+	struct aqueue *request_queue;
+	string_t *pending_commands;
+
+	unsigned int handshake_received:1;
+	unsigned int finishing:1;
+	unsigned int finished:1;
+};
+
+extern struct dsync_worker_vfuncs proxy_client_dsync_worker;
+
+static void proxy_client_worker_input(struct proxy_client_dsync_worker *worker);
+static void proxy_client_send_stream(struct proxy_client_dsync_worker *worker);
+
+static void proxy_client_fail(struct proxy_client_dsync_worker *worker)
+{
+	i_stream_close(worker->input);
+	dsync_worker_set_failure(&worker->worker);
+	io_loop_stop(current_ioloop);
+}
+
+static int
+proxy_client_worker_read_line(struct proxy_client_dsync_worker *worker,
+			      const char **line_r)
+{
+	if (worker->worker.failed)
+		return -1;
+
+	*line_r = i_stream_read_next_line(worker->input);
+	if (*line_r == NULL) {
+		if (worker->input->stream_errno != 0) {
+			errno = worker->input->stream_errno;
+			i_error("read() from worker server failed: %m");
+			dsync_worker_set_failure(&worker->worker);
+			return -1;
+		}
+		if (worker->input->eof) {
+			if (!worker->finished)
+				i_error("read() from worker server failed: EOF");
+			dsync_worker_set_failure(&worker->worker);
+			return -1;
+		}
+	}
+	if (*line_r == NULL)
+		return 0;
+
+	if (!worker->handshake_received) {
+		if (strcmp(*line_r, DSYNC_PROXY_SERVER_GREETING_LINE) != 0) {
+			i_error("Invalid server handshake: %s", *line_r);
+			dsync_worker_set_failure(&worker->worker);
+			return -1;
+		}
+		worker->handshake_received = TRUE;
+		return proxy_client_worker_read_line(worker, line_r);
+	}
+	return 1;
+}
+
+static void
+proxy_client_worker_msg_get_finish(struct proxy_client_dsync_worker *worker)
+{
+	worker->msg_get_data.input = NULL;
+	worker->io = io_add(worker->fd_in, IO_READ,
+			    proxy_client_worker_input, worker);
+
+	/* some input may already be buffered. note that we may be coming here
+	   from the input function itself, in which case this timeout must not
+	   be called (we'll remove it later) */
+	if (worker->to_input == NULL) {
+		worker->to_input =
+			timeout_add(0, proxy_client_worker_input, worker);
+	}
+}
+
+static void
+proxy_client_worker_read_to_eof(struct proxy_client_dsync_worker *worker)
+{
+	struct istream *input = worker->msg_get_data.input;
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0)
+		i_stream_skip(input, size);
+	if (ret == -1) {
+		i_stream_unref(&input);
+		io_remove(&worker->io);
+		proxy_client_worker_msg_get_finish(worker);
+	}
+	timeout_reset(worker->to);
+}
+
+static void
+proxy_client_worker_msg_get_done(struct proxy_client_dsync_worker *worker)
+{
+	struct istream *input = worker->msg_get_data.input;
+
+	i_assert(worker->io == NULL);
+
+	if (input->eof)
+		proxy_client_worker_msg_get_finish(worker);
+	else {
+		/* saving read the message only partially. we'll need to read
+		   the input until EOF or we'll start treating the input as
+		   commands. */
+		worker->io = io_add(worker->fd_in, IO_READ,
+				    proxy_client_worker_read_to_eof, worker);
+		worker->msg_get_data.input =
+			i_stream_create_dot(worker->input, FALSE);
+	}
+}
+
+static bool
+proxy_client_worker_next_copy(struct proxy_client_dsync_worker *worker,
+			      const struct proxy_client_request *request,
+			      const char *line)
+{
+	uint32_t uid;
+	bool success;
+
+	if (line[0] == '1' && line[1] == '\t')
+		success = TRUE;
+	else if (line[0] == '0' && line[1] == '\t')
+		success = FALSE;
+	else {
+		i_error("msg-copy returned invalid input: %s", line);
+		proxy_client_fail(worker);
+		return FALSE;
+	}
+	uid = strtoul(line + 2, NULL, 10);
+	if (uid != request->uid) {
+		i_error("msg-copy returned invalid uid: %u != %u",
+			uid, request->uid);
+		proxy_client_fail(worker);
+		return FALSE;
+	}
+
+	request->callback.copy(success, request->context);
+	return TRUE;
+}
+
+static bool
+proxy_client_worker_next_msg_get(struct proxy_client_dsync_worker *worker,
+				 const struct proxy_client_request *request,
+				 const char *line)
+{
+	enum dsync_msg_get_result result = DSYNC_MSG_GET_RESULT_FAILED;
+	const char *p, *error;
+	uint32_t uid;
+
+	p_clear(worker->msg_get_pool);
+	switch (line[0]) {
+	case '1':
+		/* ok */
+		if (line[1] != '\t')
+			break;
+		line += 2;
+
+		if ((p = strchr(line, '\t')) == NULL)
+			break;
+		uid = strtoul(t_strcut(line, '\t'), NULL, 10);
+		line = p + 1;
+
+		if (uid != request->uid) {
+			i_error("msg-get returned invalid uid: %u != %u",
+				uid, request->uid);
+			proxy_client_fail(worker);
+			return FALSE;
+		}
+
+		if (dsync_proxy_msg_static_import(worker->msg_get_pool,
+						  line, &worker->msg_get_data,
+						  &error) < 0) {
+			i_error("Invalid msg-get static input: %s", error);
+			proxy_client_fail(worker);
+			return FALSE;
+		}
+		worker->msg_get_data.input =
+			i_stream_create_dot(worker->input, FALSE);
+		i_stream_set_destroy_callback(worker->msg_get_data.input,
+					      proxy_client_worker_msg_get_done,
+					      worker);
+		io_remove(&worker->io);
+		result = DSYNC_MSG_GET_RESULT_SUCCESS;
+		break;
+	case '0':
+		/* expunged */
+		result = DSYNC_MSG_GET_RESULT_EXPUNGED;
+		break;
+	default:
+		/* failure */
+		break;
+	}
+
+	request->callback.get(result, &worker->msg_get_data, request->context);
+	return worker->io != NULL && worker->msg_get_data.input == NULL;
+}
+
+static void
+proxy_client_worker_next_finish(struct proxy_client_dsync_worker *worker,
+				const struct proxy_client_request *request,
+				const char *line)
+{
+	bool success = TRUE;
+
+	i_assert(worker->finishing);
+	i_assert(!worker->finished);
+
+	worker->finishing = FALSE;
+	worker->finished = TRUE;
+
+	if (strcmp(line, "changes") == 0)
+		worker->worker.unexpected_changes = TRUE;
+	else if (strcmp(line, "fail") == 0)
+		success = FALSE;
+	else if (strcmp(line, "ok") != 0) {
+		i_error("Unexpected finish reply: %s", line);
+		success = FALSE;
+	}
+		
+	request->callback.finish(success, request->context);
+}
+
+static bool
+proxy_client_worker_next_reply(struct proxy_client_dsync_worker *worker,
+			       const char *line)
+{
+	const struct proxy_client_request *requests;
+	struct proxy_client_request request;
+	bool ret = TRUE;
+
+	i_assert(worker->msg_get_data.input == NULL);
+
+	if (aqueue_count(worker->request_queue) == 0) {
+		i_error("Unexpected reply from server: %s", line);
+		proxy_client_fail(worker);
+		return FALSE;
+	}
+
+	requests = array_idx(&worker->request_array, 0);
+	request = requests[aqueue_idx(worker->request_queue, 0)];
+	aqueue_delete_tail(worker->request_queue);
+
+	switch (request.type) {
+	case PROXY_CLIENT_REQUEST_TYPE_COPY:
+		ret = proxy_client_worker_next_copy(worker, &request, line);
+		break;
+	case PROXY_CLIENT_REQUEST_TYPE_GET:
+		ret = proxy_client_worker_next_msg_get(worker, &request, line);
+		break;
+	case PROXY_CLIENT_REQUEST_TYPE_FINISH:
+		proxy_client_worker_next_finish(worker, &request, line);
+		break;
+	}
+	return ret;
+}
+
+static void proxy_client_worker_input(struct proxy_client_dsync_worker *worker)
+{
+	const char *line;
+	int ret;
+
+	if (worker->to_input != NULL)
+		timeout_remove(&worker->to_input);
+
+	if (worker->worker.input_callback != NULL) {
+		worker->worker.input_callback(worker->worker.input_context);
+		timeout_reset(worker->to);
+		return;
+	}
+
+	while ((ret = proxy_client_worker_read_line(worker, &line)) > 0) {
+		if (!proxy_client_worker_next_reply(worker, line))
+			break;
+	}
+	if (ret < 0) {
+		/* try to continue */
+		proxy_client_worker_next_reply(worker, "");
+	}
+
+	if (worker->to_input != NULL) {
+		/* input stream's destroy callback was already called.
+		   don't get back here. */
+		timeout_remove(&worker->to_input);
+	}
+	timeout_reset(worker->to);
+}
+
+static int
+proxy_client_worker_output_real(struct proxy_client_dsync_worker *worker)
+{
+	int ret;
+
+	if ((ret = o_stream_flush(worker->output)) < 0)
+		return 1;
+
+	if (worker->save_input != NULL) {
+		/* proxy_client_worker_msg_save() hasn't finished yet. */
+		o_stream_cork(worker->output);
+		proxy_client_send_stream(worker);
+		if (worker->save_input != NULL) {
+			/* still unfinished, make sure we get called again */
+			return 0;
+		}
+	}
+
+	if (worker->worker.output_callback != NULL)
+		worker->worker.output_callback(worker->worker.output_context);
+	return ret;
+}
+
+static int proxy_client_worker_output(struct proxy_client_dsync_worker *worker)
+{
+	int ret;
+
+	ret = proxy_client_worker_output_real(worker);
+	timeout_reset(worker->to);
+	return ret;
+}
+
+static void
+proxy_client_worker_timeout(struct proxy_client_dsync_worker *worker)
+{
+	const char *reason;
+
+	if (worker->save_io != NULL)
+		reason = " (waiting for more input from mail being saved)";
+	else if (worker->save_input != NULL) {
+		size_t bytes = o_stream_get_buffer_used_size(worker->output);
+
+		reason = t_strdup_printf(" (waiting for output stream to flush, "
+					 "%"PRIuSIZE_T" bytes left)", bytes);
+	} else if (worker->msg_get_data.input != NULL) {
+		reason = " (waiting for MSG-GET message from remote)";
+	} else {
+		reason = "";
+	}
+	i_error("proxy client timed out%s", reason);
+	proxy_client_fail(worker);
+}
+
+struct dsync_worker *dsync_worker_init_proxy_client(int fd_in, int fd_out)
+{
+	struct proxy_client_dsync_worker *worker;
+
+	worker = i_new(struct proxy_client_dsync_worker, 1);
+	worker->worker.v = proxy_client_dsync_worker;
+	worker->fd_in = fd_in;
+	worker->fd_out = fd_out;
+	worker->to = timeout_add(DSYNC_PROXY_CLIENT_TIMEOUT_MSECS,
+				 proxy_client_worker_timeout, worker);
+	worker->io = io_add(fd_in, IO_READ, proxy_client_worker_input, worker);
+	worker->input = i_stream_create_fd(fd_in, (size_t)-1, FALSE);
+	worker->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
+	o_stream_send_str(worker->output, DSYNC_PROXY_CLIENT_GREETING_LINE"\n");
+	/* we'll keep the output corked until flush is needed */
+	o_stream_cork(worker->output);
+	o_stream_set_flush_callback(worker->output, proxy_client_worker_output,
+				    worker);
+	fd_set_nonblock(fd_in, TRUE);
+	fd_set_nonblock(fd_out, TRUE);
+
+	worker->pending_commands = str_new(default_pool, 1024);
+	worker->msg_get_pool = pool_alloconly_create("dsync proxy msg", 128);
+	i_array_init(&worker->request_array, 64);
+	worker->request_queue = aqueue_init(&worker->request_array.arr);
+
+	return &worker->worker;
+}
+
+static void proxy_client_worker_deinit(struct dsync_worker *_worker)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	timeout_remove(&worker->to);
+	if (worker->to_input != NULL)
+		timeout_remove(&worker->to_input);
+	if (worker->io != NULL)
+		io_remove(&worker->io);
+	i_stream_destroy(&worker->input);
+	o_stream_destroy(&worker->output);
+	if (close(worker->fd_in) < 0)
+		i_error("close(worker input) failed: %m");
+	if (worker->fd_in != worker->fd_out) {
+		if (close(worker->fd_out) < 0)
+			i_error("close(worker output) failed: %m");
+	}
+	aqueue_deinit(&worker->request_queue);
+	array_free(&worker->request_array);
+	pool_unref(&worker->msg_get_pool);
+	str_free(&worker->pending_commands);
+	i_free(worker);
+}
+
+static bool
+worker_is_output_stream_full(struct proxy_client_dsync_worker *worker)
+{
+	return o_stream_get_buffer_used_size(worker->output) >=
+		OUTBUF_THROTTLE_SIZE;
+}
+
+static bool proxy_client_worker_is_output_full(struct dsync_worker *_worker)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	if (worker->save_input != NULL) {
+		/* we haven't finished sending a message save, so we're full. */
+		return TRUE;
+	}
+	return worker_is_output_stream_full(worker);
+}
+
+static int proxy_client_worker_output_flush(struct dsync_worker *_worker)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	int ret = 1;
+
+	if (o_stream_flush(worker->output) < 0)
+		return -1;
+
+	o_stream_uncork(worker->output);
+	if (o_stream_get_buffer_used_size(worker->output) > 0)
+		return 0;
+
+	if (o_stream_send(worker->output, str_data(worker->pending_commands),
+			  str_len(worker->pending_commands)) < 0)
+		ret = -1;
+	str_truncate(worker->pending_commands, 0);
+	o_stream_cork(worker->output);
+	return ret;
+}
+
+static struct dsync_worker_mailbox_iter *
+proxy_client_worker_mailbox_iter_init(struct dsync_worker *_worker)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	struct proxy_client_dsync_worker_mailbox_iter *iter;
+
+	iter = i_new(struct proxy_client_dsync_worker_mailbox_iter, 1);
+	iter->iter.worker = _worker;
+	iter->pool = pool_alloconly_create("proxy mailbox iter", 1024);
+	o_stream_send_str(worker->output, "BOX-LIST\n");
+	(void)proxy_client_worker_output_flush(_worker);
+	return &iter->iter;
+}
+
+static int
+proxy_client_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *_iter,
+				      struct dsync_mailbox *dsync_box_r)
+{
+	struct proxy_client_dsync_worker_mailbox_iter *iter =
+		(struct proxy_client_dsync_worker_mailbox_iter *)_iter;
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_iter->worker;
+	const char *line, *error;
+	int ret;
+
+	if ((ret = proxy_client_worker_read_line(worker, &line)) <= 0) {
+		if (ret < 0)
+			_iter->failed = TRUE;
+		return ret;
+	}
+
+	if ((line[0] == '+' || line[0] == '-') && line[1] == '\0') {
+		/* end of mailboxes */
+		if (line[0] == '-') {
+			i_error("Worker server's mailbox iteration failed");
+			_iter->failed = TRUE;
+		}
+		return -1;
+	}
+
+	p_clear(iter->pool);
+	if (dsync_proxy_mailbox_import(iter->pool, line,
+				       dsync_box_r, &error) < 0) {
+		i_error("Invalid mailbox input from worker server: %s", error);
+		_iter->failed = TRUE;
+		return -1;
+	}
+	return 1;
+}
+
+static int
+proxy_client_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter *_iter)
+{
+	struct proxy_client_dsync_worker_mailbox_iter *iter =
+		(struct proxy_client_dsync_worker_mailbox_iter *)_iter;
+	int ret = _iter->failed ? -1 : 0;
+
+	pool_unref(&iter->pool);
+	i_free(iter);
+	return ret;
+}
+
+static struct dsync_worker_subs_iter *
+proxy_client_worker_subs_iter_init(struct dsync_worker *_worker)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	struct proxy_client_dsync_worker_subs_iter *iter;
+
+	iter = i_new(struct proxy_client_dsync_worker_subs_iter, 1);
+	iter->iter.worker = _worker;
+	iter->pool = pool_alloconly_create("proxy subscription iter", 1024);
+	o_stream_send_str(worker->output, "SUBS-LIST\n");
+	(void)proxy_client_worker_output_flush(_worker);
+	return &iter->iter;
+}
+
+static int
+proxy_client_worker_subs_iter_next_line(struct proxy_client_dsync_worker_subs_iter *iter,
+					unsigned int wanted_arg_count,
+					char ***args_r)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)iter->iter.worker;
+	const char *line;
+	char **args;
+	int ret;
+
+	if ((ret = proxy_client_worker_read_line(worker, &line)) <= 0) {
+		if (ret < 0)
+			iter->iter.failed = TRUE;
+		return ret;
+	}
+
+	if ((line[0] == '+' || line[0] == '-') && line[1] == '\0') {
+		/* end of subscribed subscriptions */
+		if (line[0] == '-') {
+			i_error("Worker server's subscription iteration failed");
+			iter->iter.failed = TRUE;
+		}
+		return -1;
+	}
+
+	p_clear(iter->pool);
+	args = p_strsplit(iter->pool, line, "\t");
+	if (str_array_length((const char *const *)args) < wanted_arg_count) {
+		i_error("Invalid subscription input from worker server");
+		iter->iter.failed = TRUE;
+		return -1;
+	}
+	*args_r = args;
+	return 1;
+}
+
+static int
+proxy_client_worker_subs_iter_next(struct dsync_worker_subs_iter *_iter,
+				   struct dsync_worker_subscription *rec_r)
+{
+	struct proxy_client_dsync_worker_subs_iter *iter =
+		(struct proxy_client_dsync_worker_subs_iter *)_iter;
+	char **args;
+	int ret;
+
+	ret = proxy_client_worker_subs_iter_next_line(iter, 4, &args);
+	if (ret <= 0)
+		return ret;
+
+	rec_r->vname = str_tabunescape(args[0]);
+	rec_r->storage_name = str_tabunescape(args[1]);
+	rec_r->ns_prefix = str_tabunescape(args[2]);
+	rec_r->last_change = strtoul(args[3], NULL, 10);
+	return 1;
+}
+
+static int
+proxy_client_worker_subs_iter_next_un(struct dsync_worker_subs_iter *_iter,
+				      struct dsync_worker_unsubscription *rec_r)
+{
+	struct proxy_client_dsync_worker_subs_iter *iter =
+		(struct proxy_client_dsync_worker_subs_iter *)_iter;
+	char **args;
+	int ret;
+
+	ret = proxy_client_worker_subs_iter_next_line(iter, 3, &args);
+	if (ret <= 0)
+		return ret;
+
+	memset(rec_r, 0, sizeof(*rec_r));
+	if (dsync_proxy_mailbox_guid_import(args[0], &rec_r->name_sha1) < 0) {
+		i_error("Invalid subscription input from worker server: "
+			"Invalid unsubscription mailbox GUID");
+		iter->iter.failed = TRUE;
+		return -1;
+	}
+	rec_r->ns_prefix = str_tabunescape(args[1]);
+	rec_r->last_change = strtoul(args[2], NULL, 10);
+	return 1;
+}
+
+static int
+proxy_client_worker_subs_iter_deinit(struct dsync_worker_subs_iter *_iter)
+{
+	struct proxy_client_dsync_worker_subs_iter *iter =
+		(struct proxy_client_dsync_worker_subs_iter *)_iter;
+	int ret = _iter->failed ? -1 : 0;
+
+	pool_unref(&iter->pool);
+	i_free(iter);
+	return ret;
+}
+
+static void
+proxy_client_worker_set_subscribed(struct dsync_worker *_worker,
+				   const char *name, time_t last_change,
+				   bool set)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "SUBS-SET\t");
+		str_tabescape_write(str, name);
+		str_printfa(str, "\t%s\t%d\n", dec2str(last_change),
+			    set ? 1 : 0);
+		o_stream_send(worker->output, str_data(str), str_len(str));
+	} T_END;
+}
+
+struct proxy_client_dsync_worker_msg_iter {
+	struct dsync_worker_msg_iter iter;
+	pool_t pool;
+	bool done;
+};
+
+static struct dsync_worker_msg_iter *
+proxy_client_worker_msg_iter_init(struct dsync_worker *_worker,
+				  const mailbox_guid_t mailboxes[],
+				  unsigned int mailbox_count)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	struct proxy_client_dsync_worker_msg_iter *iter;
+	string_t *str;
+	unsigned int i;
+
+	iter = i_new(struct proxy_client_dsync_worker_msg_iter, 1);
+	iter->iter.worker = _worker;
+	iter->pool = pool_alloconly_create("proxy message iter", 10240);
+
+	str = str_new(iter->pool, 512);
+	str_append(str, "MSG-LIST");
+	for (i = 0; i < mailbox_count; i++) T_BEGIN {
+		str_append_c(str, '\t');
+		dsync_proxy_mailbox_guid_export(str, &mailboxes[i]);
+	} T_END;
+	str_append_c(str, '\n');
+	o_stream_send(worker->output, str_data(str), str_len(str));
+	p_clear(iter->pool);
+
+	(void)proxy_client_worker_output_flush(_worker);
+	return &iter->iter;
+}
+
+static int
+proxy_client_worker_msg_iter_next(struct dsync_worker_msg_iter *_iter,
+				  unsigned int *mailbox_idx_r,
+				  struct dsync_message *msg_r)
+{
+	struct proxy_client_dsync_worker_msg_iter *iter =
+		(struct proxy_client_dsync_worker_msg_iter *)_iter;
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_iter->worker;
+	const char *line, *error;
+	int ret;
+
+	if (iter->done)
+		return -1;
+
+	if ((ret = proxy_client_worker_read_line(worker, &line)) <= 0) {
+		if (ret < 0)
+			_iter->failed = TRUE;
+		return ret;
+	}
+
+	if ((line[0] == '+' || line[0] == '-') && line[1] == '\0') {
+		/* end of messages */
+		if (line[0] == '-') {
+			i_error("Worker server's message iteration failed");
+			_iter->failed = TRUE;
+		}
+		iter->done = TRUE;
+		return -1;
+	}
+
+	*mailbox_idx_r = 0;
+	while (*line >= '0' && *line <= '9') {
+		*mailbox_idx_r = *mailbox_idx_r * 10 + (*line - '0');
+		line++;
+	}
+	if (*line != '\t') {
+		i_error("Invalid mailbox idx from worker server");
+		_iter->failed = TRUE;
+		return -1;
+	}
+	line++;
+
+	p_clear(iter->pool);
+	if (dsync_proxy_msg_import(iter->pool, line, msg_r, &error) < 0) {
+		i_error("Invalid message input from worker server: %s", error);
+		_iter->failed = TRUE;
+		return -1;
+	}
+	return 1;
+}
+
+static int
+proxy_client_worker_msg_iter_deinit(struct dsync_worker_msg_iter *_iter)
+{
+	struct proxy_client_dsync_worker_msg_iter *iter =
+		(struct proxy_client_dsync_worker_msg_iter *)_iter;
+	int ret = _iter->failed ? -1 : 0;
+
+	pool_unref(&iter->pool);
+	i_free(iter);
+	return ret;
+}
+
+static void
+proxy_client_worker_cmd(struct proxy_client_dsync_worker *worker, string_t *str)
+{
+	if (worker->save_input == NULL)
+		o_stream_send(worker->output, str_data(str), str_len(str));
+	else
+		str_append_str(worker->pending_commands, str);
+}
+
+static void
+proxy_client_worker_create_mailbox(struct dsync_worker *_worker,
+				   const struct dsync_mailbox *dsync_box)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "BOX-CREATE\t");
+		dsync_proxy_mailbox_export(str, dsync_box);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_delete_mailbox(struct dsync_worker *_worker,
+				   const struct dsync_mailbox *dsync_box)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "BOX-DELETE\t");
+		dsync_proxy_mailbox_guid_export(str, &dsync_box->mailbox_guid);
+		str_printfa(str, "\t%s\n", dec2str(dsync_box->last_change));
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_delete_dir(struct dsync_worker *_worker,
+			       const struct dsync_mailbox *dsync_box)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "DIR-DELETE\t");
+		str_tabescape_write(str, dsync_box->name);
+		str_printfa(str, "\t%s\n", dec2str(dsync_box->last_change));
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_rename_mailbox(struct dsync_worker *_worker,
+				   const mailbox_guid_t *mailbox,
+				   const struct dsync_mailbox *dsync_box)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	char sep[2];
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "BOX-RENAME\t");
+		dsync_proxy_mailbox_guid_export(str, mailbox);
+		str_append_c(str, '\t');
+		str_tabescape_write(str, dsync_box->name);
+		str_append_c(str, '\t');
+		sep[0] = dsync_box->name_sep; sep[1] = '\0';
+		str_tabescape_write(str, sep);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_update_mailbox(struct dsync_worker *_worker,
+				   const struct dsync_mailbox *dsync_box)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "BOX-UPDATE\t");
+		dsync_proxy_mailbox_export(str, dsync_box);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_select_mailbox(struct dsync_worker *_worker,
+				   const mailbox_guid_t *mailbox,
+				   const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	if (dsync_guid_equals(&worker->selected_box_guid, mailbox))
+		return;
+	worker->selected_box_guid = *mailbox;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "BOX-SELECT\t");
+		dsync_proxy_mailbox_guid_export(str, mailbox);
+		if (cache_fields != NULL)
+			dsync_proxy_cache_fields_export(str, cache_fields);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_msg_update_metadata(struct dsync_worker *_worker,
+					const struct dsync_message *msg)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_printfa(str, "MSG-UPDATE\t%u\t%llu\t", msg->uid,
+			    (unsigned long long)msg->modseq);
+		imap_write_flags(str, msg->flags, msg->keywords);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_msg_update_uid(struct dsync_worker *_worker,
+				   uint32_t old_uid, uint32_t new_uid)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(64);
+		str_printfa(str, "MSG-UID-CHANGE\t%u\t%u\n", old_uid, new_uid);
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_msg_expunge(struct dsync_worker *_worker, uint32_t uid)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(64);
+		str_printfa(str, "MSG-EXPUNGE\t%u\n", uid);
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+}
+
+static void
+proxy_client_worker_msg_copy(struct dsync_worker *_worker,
+			     const mailbox_guid_t *src_mailbox,
+			     uint32_t src_uid,
+			     const struct dsync_message *dest_msg,
+			     dsync_worker_copy_callback_t *callback,
+			     void *context)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	struct proxy_client_request request;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "MSG-COPY\t");
+		dsync_proxy_mailbox_guid_export(str, src_mailbox);
+		str_printfa(str, "\t%u\t", src_uid);
+		dsync_proxy_msg_export(str, dest_msg);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+
+	memset(&request, 0, sizeof(request));
+	request.type = PROXY_CLIENT_REQUEST_TYPE_COPY;
+	request.callback.copy = callback;
+	request.context = context;
+	request.uid = src_uid;
+	aqueue_append(worker->request_queue, &request);
+}
+
+static void
+proxy_client_send_stream_real(struct proxy_client_dsync_worker *worker)
+{
+	dsync_worker_save_callback_t *callback;
+	void *context;
+	struct istream *input;
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	while ((ret = i_stream_read_data(worker->save_input,
+					 &data, &size, 0)) > 0) {
+		dsync_proxy_send_dot_output(worker->output,
+					    &worker->save_input_last_lf,
+					    data, size);
+		i_stream_skip(worker->save_input, size);
+
+		if (worker_is_output_stream_full(worker)) {
+			o_stream_uncork(worker->output);
+			if (worker_is_output_stream_full(worker))
+				return;
+			o_stream_cork(worker->output);
+		}
+	}
+	if (ret == 0) {
+		/* waiting for more input */
+		o_stream_uncork(worker->output);
+		if (worker->save_io == NULL) {
+			int fd = i_stream_get_fd(worker->save_input);
+
+			worker->save_io =
+				io_add(fd, IO_READ,
+				       proxy_client_send_stream, worker);
+		}
+		return;
+	}
+	if (worker->save_io != NULL)
+		io_remove(&worker->save_io);
+	if (worker->save_input->stream_errno != 0) {
+		errno = worker->save_input->stream_errno;
+		i_error("proxy: reading message input failed: %m");
+		o_stream_close(worker->output);
+	} else {
+		i_assert(!i_stream_have_bytes_left(worker->save_input));
+		o_stream_send(worker->output, "\n.\n", 3);
+	}
+
+	callback = worker->save_callback;
+	context = worker->save_context;
+	worker->save_callback = NULL;
+	worker->save_context = NULL;
+
+	/* a bit ugly way to free the stream. the problem is that local worker
+	   has set a destroy callback, which in turn can call our msg_save()
+	   again before the i_stream_unref() is finished. */
+	input = worker->save_input;
+	worker->save_input = NULL;
+	i_stream_unref(&input);
+
+	(void)proxy_client_worker_output_flush(&worker->worker);
+
+	callback(context);
+}
+
+static void proxy_client_send_stream(struct proxy_client_dsync_worker *worker)
+{
+	proxy_client_send_stream_real(worker);
+	timeout_reset(worker->to);
+}
+
+static void
+proxy_client_worker_msg_save(struct dsync_worker *_worker,
+			     const struct dsync_message *msg,
+			     const struct dsync_msg_static_data *data,
+			     dsync_worker_save_callback_t *callback,
+			     void *context)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "MSG-SAVE\t");
+		dsync_proxy_msg_static_export(str, data);
+		str_append_c(str, '\t');
+		dsync_proxy_msg_export(str, msg);
+		str_append_c(str, '\n');
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+
+	i_assert(worker->save_input == NULL);
+	worker->save_callback = callback;
+	worker->save_context = context;
+	worker->save_input = data->input;
+	worker->save_input_last_lf = TRUE;
+	i_stream_ref(worker->save_input);
+	proxy_client_send_stream(worker);
+}
+
+static void
+proxy_client_worker_msg_save_cancel(struct dsync_worker *_worker)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+
+	if (worker->save_io != NULL)
+		io_remove(&worker->save_io);
+	if (worker->save_input != NULL)
+		i_stream_unref(&worker->save_input);
+}
+
+static void
+proxy_client_worker_msg_get(struct dsync_worker *_worker,
+			    const mailbox_guid_t *mailbox, uint32_t uid,
+			    dsync_worker_msg_callback_t *callback,
+			    void *context)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	struct proxy_client_request request;
+
+	T_BEGIN {
+		string_t *str = t_str_new(128);
+
+		str_append(str, "MSG-GET\t");
+		dsync_proxy_mailbox_guid_export(str, mailbox);
+		str_printfa(str, "\t%u\n", uid);
+		proxy_client_worker_cmd(worker, str);
+	} T_END;
+
+	memset(&request, 0, sizeof(request));
+	request.type = PROXY_CLIENT_REQUEST_TYPE_GET;
+	request.callback.get = callback;
+	request.context = context;
+	request.uid = uid;
+	aqueue_append(worker->request_queue, &request);
+}
+
+static void
+proxy_client_worker_finish(struct dsync_worker *_worker,
+			   dsync_worker_finish_callback_t *callback,
+			   void *context)
+{
+	struct proxy_client_dsync_worker *worker =
+		(struct proxy_client_dsync_worker *)_worker;
+	struct proxy_client_request request;
+
+	i_assert(worker->save_input == NULL);
+	i_assert(!worker->finishing);
+
+	worker->finishing = TRUE;
+	worker->finished = FALSE;
+
+	o_stream_send_str(worker->output, "FINISH\n");
+	o_stream_uncork(worker->output);
+
+	memset(&request, 0, sizeof(request));
+	request.type = PROXY_CLIENT_REQUEST_TYPE_FINISH;
+	request.callback.finish = callback;
+	request.context = context;
+	aqueue_append(worker->request_queue, &request);
+}
+
+struct dsync_worker_vfuncs proxy_client_dsync_worker = {
+	proxy_client_worker_deinit,
+
+	proxy_client_worker_is_output_full,
+	proxy_client_worker_output_flush,
+
+	proxy_client_worker_mailbox_iter_init,
+	proxy_client_worker_mailbox_iter_next,
+	proxy_client_worker_mailbox_iter_deinit,
+
+	proxy_client_worker_subs_iter_init,
+	proxy_client_worker_subs_iter_next,
+	proxy_client_worker_subs_iter_next_un,
+	proxy_client_worker_subs_iter_deinit,
+	proxy_client_worker_set_subscribed,
+
+	proxy_client_worker_msg_iter_init,
+	proxy_client_worker_msg_iter_next,
+	proxy_client_worker_msg_iter_deinit,
+
+	proxy_client_worker_create_mailbox,
+	proxy_client_worker_delete_mailbox,
+	proxy_client_worker_delete_dir,
+	proxy_client_worker_rename_mailbox,
+	proxy_client_worker_update_mailbox,
+
+	proxy_client_worker_select_mailbox,
+	proxy_client_worker_msg_update_metadata,
+	proxy_client_worker_msg_update_uid,
+	proxy_client_worker_msg_expunge,
+	proxy_client_worker_msg_copy,
+	proxy_client_worker_msg_save,
+	proxy_client_worker_msg_save_cancel,
+	proxy_client_worker_msg_get,
+	proxy_client_worker_finish
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-proxy-server-cmd.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,608 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "network.h"
+#include "istream.h"
+#include "istream-dot.h"
+#include "ostream.h"
+#include "imap-util.h"
+#include "dsync-worker.h"
+#include "dsync-proxy.h"
+#include "dsync-proxy-server.h"
+
+#include <stdlib.h>
+
+#define OUTBUF_THROTTLE_SIZE (1024*64)
+
+static bool
+proxy_server_is_output_full(struct dsync_proxy_server *server)
+{
+	return o_stream_get_buffer_used_size(server->output) >=
+		OUTBUF_THROTTLE_SIZE;
+}
+
+static int
+cmd_box_list(struct dsync_proxy_server *server,
+	     const char *const *args ATTR_UNUSED)
+{
+	struct dsync_mailbox dsync_box;
+	string_t *str;
+	int ret;
+
+	if (server->mailbox_iter == NULL) {
+		server->mailbox_iter =
+			dsync_worker_mailbox_iter_init(server->worker);
+	}
+
+	str = t_str_new(256);
+	while ((ret = dsync_worker_mailbox_iter_next(server->mailbox_iter,
+						     &dsync_box)) > 0) {
+		str_truncate(str, 0);
+		dsync_proxy_mailbox_export(str, &dsync_box);
+		str_append_c(str, '\n');
+		o_stream_send(server->output, str_data(str), str_len(str));
+		if (proxy_server_is_output_full(server))
+			break;
+	}
+	if (ret >= 0) {
+		/* continue later */
+		o_stream_set_flush_pending(server->output, TRUE);
+		return 0;
+	}
+	if (dsync_worker_mailbox_iter_deinit(&server->mailbox_iter) < 0) {
+		o_stream_send(server->output, "-\n", 2);
+		return -1;
+	} else {
+		o_stream_send(server->output, "+\n", 2);
+		return 1;
+	}
+}
+
+static bool cmd_subs_list_subscriptions(struct dsync_proxy_server *server)
+{
+	struct dsync_worker_subscription rec;
+	string_t *str;
+	int ret;
+
+	str = t_str_new(256);
+	while ((ret = dsync_worker_subs_iter_next(server->subs_iter,
+						  &rec)) > 0) {
+		str_truncate(str, 0);
+		str_tabescape_write(str, rec.vname);
+		str_append_c(str, '\t');
+		str_tabescape_write(str, rec.storage_name);
+		str_append_c(str, '\t');
+		str_tabescape_write(str, rec.ns_prefix);
+		str_printfa(str, "\t%ld\n", (long)rec.last_change);
+		o_stream_send(server->output, str_data(str), str_len(str));
+		if (proxy_server_is_output_full(server))
+			break;
+	}
+	if (ret >= 0) {
+		/* continue later */
+		o_stream_set_flush_pending(server->output, TRUE);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static bool cmd_subs_list_unsubscriptions(struct dsync_proxy_server *server)
+{
+	struct dsync_worker_unsubscription rec;
+	string_t *str;
+	int ret;
+
+	str = t_str_new(256);
+	while ((ret = dsync_worker_subs_iter_next_un(server->subs_iter,
+						     &rec)) > 0) {
+		str_truncate(str, 0);
+		dsync_proxy_mailbox_guid_export(str, &rec.name_sha1);
+		str_append_c(str, '\t');
+		str_tabescape_write(str, rec.ns_prefix);
+		str_printfa(str, "\t%ld\n", (long)rec.last_change);
+		o_stream_send(server->output, str_data(str), str_len(str));
+		if (proxy_server_is_output_full(server))
+			break;
+	}
+	if (ret >= 0) {
+		/* continue later */
+		o_stream_set_flush_pending(server->output, TRUE);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int
+cmd_subs_list(struct dsync_proxy_server *server,
+	      const char *const *args ATTR_UNUSED)
+{
+	if (server->subs_iter == NULL) {
+		server->subs_iter =
+			dsync_worker_subs_iter_init(server->worker);
+	}
+
+	if (!server->subs_sending_unsubscriptions) {
+		if (!cmd_subs_list_subscriptions(server))
+			return 0;
+		/* a bit hacky way to handle this. this assumes that caller
+		   goes through all subscriptions first, and next starts
+		   going through unsubscriptions */
+		o_stream_send(server->output, "+\n", 2);
+		server->subs_sending_unsubscriptions = TRUE;
+	}
+	if (!cmd_subs_list_unsubscriptions(server))
+		return 0;
+
+	server->subs_sending_unsubscriptions = FALSE;
+	if (dsync_worker_subs_iter_deinit(&server->subs_iter) < 0) {
+		o_stream_send(server->output, "-\n", 2);
+		return -1;
+	} else {
+		o_stream_send(server->output, "+\n", 2);
+		return 1;
+	}
+}
+
+static int
+cmd_subs_set(struct dsync_proxy_server *server, const char *const *args)
+{
+	if (str_array_length(args) < 3) {
+		i_error("subs-set: Missing parameters");
+		return -1;
+	}
+
+	dsync_worker_set_subscribed(server->worker, args[0],
+				    strtoul(args[1], NULL, 10),
+				    strcmp(args[2], "1") == 0);
+	return 1;
+}
+
+static int
+cmd_msg_list_init(struct dsync_proxy_server *server, const char *const *args)
+{
+	mailbox_guid_t *mailboxes;
+	unsigned int i, count;
+	int ret;
+
+	count = str_array_length(args);
+	mailboxes = count == 0 ? NULL : t_new(mailbox_guid_t, count);
+	for (i = 0; i < count; i++) {
+		T_BEGIN {
+			ret = dsync_proxy_mailbox_guid_import(args[i],
+							      &mailboxes[i]);
+		} T_END;
+
+		if (ret < 0) {
+			i_error("msg-list: Invalid mailbox GUID '%s'", args[i]);
+			return -1;
+		}
+	}
+	server->msg_iter = dsync_worker_msg_iter_init(server->worker,
+						      mailboxes, count);
+	return 0;
+}
+
+static int
+cmd_msg_list(struct dsync_proxy_server *server, const char *const *args)
+{
+	unsigned int mailbox_idx;
+	struct dsync_message msg;
+	string_t *str;
+	int ret;
+
+	if (server->msg_iter == NULL) {
+		if (cmd_msg_list_init(server, args) < 0)
+			return -1;
+	}
+
+	str = t_str_new(256);
+	while ((ret = dsync_worker_msg_iter_next(server->msg_iter,
+						 &mailbox_idx, &msg)) > 0) {
+		str_truncate(str, 0);
+		str_printfa(str, "%u\t", mailbox_idx);
+		dsync_proxy_msg_export(str, &msg);
+		str_append_c(str, '\n');
+		o_stream_send(server->output, str_data(str), str_len(str));
+		if (proxy_server_is_output_full(server))
+			break;
+	}
+	if (ret >= 0) {
+		/* continue later */
+		o_stream_set_flush_pending(server->output, TRUE);
+		return 0;
+	}
+	if (dsync_worker_msg_iter_deinit(&server->msg_iter) < 0) {
+		o_stream_send(server->output, "-\n", 2);
+		return -1;
+	} else {
+		o_stream_send(server->output, "+\n", 2);
+		return 1;
+	}
+}
+
+static int
+cmd_box_create(struct dsync_proxy_server *server, const char *const *args)
+{
+	struct dsync_mailbox dsync_box;
+	const char *error;
+
+	if (dsync_proxy_mailbox_import_unescaped(pool_datastack_create(),
+						 args, &dsync_box,
+						 &error) < 0) {
+		i_error("Invalid mailbox input: %s", error);
+		return -1;
+	}
+	dsync_worker_create_mailbox(server->worker, &dsync_box);
+	return 1;
+}
+
+static int
+cmd_box_delete(struct dsync_proxy_server *server, const char *const *args)
+{
+	mailbox_guid_t guid;
+	struct dsync_mailbox dsync_box;
+
+	if (str_array_length(args) < 2)
+		return -1;
+	if (dsync_proxy_mailbox_guid_import(args[0], &guid) < 0) {
+		i_error("box-delete: Invalid mailbox GUID '%s'", args[0]);
+		return -1;
+	}
+
+	memset(&dsync_box, 0, sizeof(dsync_box));
+	dsync_box.mailbox_guid = guid;
+	dsync_box.last_change = strtoul(args[1], NULL, 10);
+	dsync_worker_delete_mailbox(server->worker, &dsync_box);
+	return 1;
+}
+
+static int
+cmd_dir_delete(struct dsync_proxy_server *server, const char *const *args)
+{
+	struct dsync_mailbox dsync_box;
+
+	if (str_array_length(args) < 2)
+		return -1;
+
+	memset(&dsync_box, 0, sizeof(dsync_box));
+	dsync_box.name = str_tabunescape(t_strdup_noconst(args[0]));
+	dsync_box.last_change = strtoul(args[1], NULL, 10);
+	dsync_worker_delete_dir(server->worker, &dsync_box);
+	return 1;
+}
+
+static int
+cmd_box_rename(struct dsync_proxy_server *server, const char *const *args)
+{
+	mailbox_guid_t guid;
+	struct dsync_mailbox dsync_box;
+
+	if (str_array_length(args) < 3)
+		return -1;
+	if (dsync_proxy_mailbox_guid_import(args[0], &guid) < 0) {
+		i_error("box-delete: Invalid mailbox GUID '%s'", args[0]);
+		return -1;
+	}
+
+	memset(&dsync_box, 0, sizeof(dsync_box));
+	dsync_box.name = args[1];
+	dsync_box.name_sep = args[2][0];
+	dsync_worker_rename_mailbox(server->worker, &guid, &dsync_box);
+	return 1;
+}
+
+static int
+cmd_box_update(struct dsync_proxy_server *server, const char *const *args)
+{
+	struct dsync_mailbox dsync_box;
+	const char *error;
+
+	if (dsync_proxy_mailbox_import_unescaped(pool_datastack_create(),
+						 args, &dsync_box,
+						 &error) < 0) {
+		i_error("Invalid mailbox input: %s", error);
+		return -1;
+	}
+	dsync_worker_update_mailbox(server->worker, &dsync_box);
+	return 1;
+}
+
+static int
+cmd_box_select(struct dsync_proxy_server *server, const char *const *args)
+{
+	struct dsync_mailbox box;
+	const char *error;
+
+	memset(&box, 0, sizeof(box));
+	if (args[0] == NULL ||
+	    dsync_proxy_mailbox_guid_import(args[0], &box.mailbox_guid) < 0) {
+		i_error("box-select: Invalid mailbox GUID '%s'", args[0]);
+		return -1;
+	}
+	args++;
+
+	if (dsync_proxy_cache_fields_import(args, pool_datastack_create(),
+					    &box.cache_fields, &error) < 0) {
+		i_error("box-select: %s", error);
+		return -1;
+	}
+	dsync_worker_select_mailbox(server->worker, &box);
+	return 1;
+}
+
+static int
+cmd_msg_update(struct dsync_proxy_server *server, const char *const *args)
+{
+	struct dsync_message msg;
+
+	/* uid modseq flags */
+	if (str_array_length(args) < 3)
+		return -1;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.uid = strtoul(args[0], NULL, 10);
+	msg.modseq = strtoull(args[1], NULL, 10);
+	if (dsync_proxy_msg_parse_flags(pool_datastack_create(),
+					args[2], &msg) < 0)
+		return -1;
+
+	dsync_worker_msg_update_metadata(server->worker, &msg);
+	return 1;
+}
+
+static int
+cmd_msg_uid_change(struct dsync_proxy_server *server, const char *const *args)
+{
+	if (args[0] == NULL || args[1] == NULL)
+		return -1;
+
+	dsync_worker_msg_update_uid(server->worker,
+				    strtoul(args[0], NULL, 10),
+				    strtoul(args[1], NULL, 10));
+	return 1;
+}
+
+static int
+cmd_msg_expunge(struct dsync_proxy_server *server, const char *const *args)
+{
+	if (args[0] == NULL)
+		return -1;
+
+	dsync_worker_msg_expunge(server->worker, strtoul(args[0], NULL, 10));
+	return 1;
+}
+
+static void copy_callback(bool success, void *context)
+{
+	struct dsync_proxy_server *server = context;
+	const char *reply;
+
+	i_assert(server->copy_uid != 0);
+
+	reply = t_strdup_printf("%d\t%u\n", success ? 1 : 0, server->copy_uid);
+	o_stream_send_str(server->output, reply);
+}
+
+static int
+cmd_msg_copy(struct dsync_proxy_server *server, const char *const *args)
+{
+	mailbox_guid_t src_mailbox_guid;
+	uint32_t src_uid;
+	struct dsync_message msg;
+	const char *error;
+
+	/* src_mailbox_guid src_uid <message> */
+	if (str_array_length(args) < 3)
+		return -1;
+
+	if (dsync_proxy_mailbox_guid_import(args[0], &src_mailbox_guid) < 0) {
+		i_error("msg-copy: Invalid mailbox GUID '%s'", args[0]);
+		return -1;
+	}
+	src_uid = strtoul(args[1], NULL, 10);
+
+	if (dsync_proxy_msg_import_unescaped(pool_datastack_create(),
+					     args + 2, &msg, &error) < 0)
+		i_error("Invalid message input: %s", error);
+
+	server->copy_uid = src_uid;
+	dsync_worker_msg_copy(server->worker, &src_mailbox_guid, src_uid, &msg,
+			      copy_callback, server);
+	server->copy_uid = 0;
+	return 1;
+}
+
+static void cmd_msg_save_callback(void *context)
+{
+	struct dsync_proxy_server *server = context;
+
+	server->save_finished = TRUE;
+}
+
+static int
+cmd_msg_save(struct dsync_proxy_server *server, const char *const *args)
+{
+	struct dsync_message msg;
+	struct dsync_msg_static_data data;
+	const char *error;
+	int ret;
+
+	if (dsync_proxy_msg_static_import_unescaped(pool_datastack_create(),
+						    args, &data, &error) < 0) {
+		i_error("Invalid message input: %s", error);
+		return -1;
+	}
+	data.input = i_stream_create_dot(server->input, FALSE);
+
+	if (dsync_proxy_msg_import_unescaped(pool_datastack_create(),
+					     args + 2, &msg, &error) < 0) {
+		i_error("Invalid message input: %s", error);
+		return -1;
+	}
+
+	/* we rely on save reading the entire input */
+	server->save_finished = FALSE;
+	net_set_nonblock(server->fd_in, FALSE);
+	dsync_worker_msg_save(server->worker, &msg, &data,
+			      cmd_msg_save_callback, server);
+	net_set_nonblock(server->fd_in, TRUE);
+	ret = dsync_worker_has_failed(server->worker) ? -1 : 1;
+	i_assert(server->save_finished);
+	i_assert(data.input->eof || ret < 0);
+	i_stream_destroy(&data.input);
+	return ret;
+}
+
+static void cmd_msg_get_send_more(struct dsync_proxy_server *server)
+{
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	while (!proxy_server_is_output_full(server)) {
+		ret = i_stream_read_data(server->get_input, &data, &size, 0);
+		if (ret == -1) {
+			/* done */
+			o_stream_send(server->output, "\n.\n", 3);
+			i_stream_unref(&server->get_input);
+			return;
+		} else {
+			/* for now we assume input is blocking */
+			i_assert(ret != 0);
+		}
+
+		dsync_proxy_send_dot_output(server->output,
+					    &server->get_input_last_lf,
+					    data, size);
+		i_stream_skip(server->get_input, size);
+	}
+	o_stream_set_flush_pending(server->output, TRUE);
+}
+
+static void
+cmd_msg_get_callback(enum dsync_msg_get_result result,
+		     const struct dsync_msg_static_data *data, void *context)
+{
+	struct dsync_proxy_server *server = context;
+	string_t *str;
+
+	i_assert(server->get_uid != 0);
+
+	switch (result) {
+	case DSYNC_MSG_GET_RESULT_SUCCESS:
+		break;
+	case DSYNC_MSG_GET_RESULT_EXPUNGED:
+		o_stream_send(server->output, "0\n", 3);
+		return;
+	case DSYNC_MSG_GET_RESULT_FAILED:
+		o_stream_send(server->output, "-\n", 3);
+		return;
+	}
+
+	str = t_str_new(128);
+	str_printfa(str, "1\t%u\t", server->get_uid);
+	dsync_proxy_msg_static_export(str, data);
+	str_append_c(str, '\n');
+	o_stream_send(server->output, str_data(str), str_len(str));
+
+	/* then we'll still have to send the message body. */
+	server->get_input = data->input;
+	cmd_msg_get_send_more(server);
+	if (server->get_input == NULL) {
+		/* if we came here from ioloop, make sure the command gets
+		   freed in the output flush callback */
+		o_stream_set_flush_pending(server->output, TRUE);
+	}
+}
+
+static int
+cmd_msg_get(struct dsync_proxy_server *server, const char *const *args)
+{
+	mailbox_guid_t mailbox_guid;
+	uint32_t uid;
+
+	if (str_array_length(args) < 2)
+		return -1;
+
+	if (dsync_proxy_mailbox_guid_import(args[0], &mailbox_guid) < 0) {
+		i_error("msg-get: Invalid mailbox GUID '%s'", args[0]);
+		return -1;
+	}
+
+	uid = strtoul(args[1], NULL, 10);
+	if (uid == 0)
+		return -1;
+
+	if (server->get_input != NULL) {
+		i_assert(server->get_uid == uid);
+		cmd_msg_get_send_more(server);
+	} else {
+		server->get_uid = uid;
+		dsync_worker_msg_get(server->worker, &mailbox_guid, uid,
+				     cmd_msg_get_callback, server);
+	}
+	if (server->get_input != NULL)
+		return 0;
+	server->get_uid = 0;
+	return 1;
+}
+
+static void cmd_finish_callback(bool success, void *context)
+{
+	struct dsync_proxy_server *server = context;
+	const char *reply;
+
+	if (!success)
+		reply = "fail\n";
+	else if (dsync_worker_has_unexpected_changes(server->worker))
+		reply = "changes\n";
+	else
+		reply = "ok\n";
+
+	server->finished = TRUE;
+	o_stream_send_str(server->output, reply);
+}
+
+static int
+cmd_finish(struct dsync_proxy_server *server,
+	   const char *const *args ATTR_UNUSED)
+{
+	dsync_worker_finish(server->worker, cmd_finish_callback, server);
+	return 1;
+}
+
+static struct dsync_proxy_server_command commands[] = {
+	{ "BOX-LIST", cmd_box_list },
+	{ "SUBS-LIST", cmd_subs_list },
+	{ "SUBS-SET", cmd_subs_set },
+	{ "MSG-LIST", cmd_msg_list },
+	{ "BOX-CREATE", cmd_box_create },
+	{ "BOX-DELETE", cmd_box_delete },
+	{ "DIR-DELETE", cmd_dir_delete },
+	{ "BOX-RENAME", cmd_box_rename },
+	{ "BOX-UPDATE", cmd_box_update },
+	{ "BOX-SELECT", cmd_box_select },
+	{ "MSG-UPDATE", cmd_msg_update },
+	{ "MSG-UID-CHANGE", cmd_msg_uid_change },
+	{ "MSG-EXPUNGE", cmd_msg_expunge },
+	{ "MSG-COPY", cmd_msg_copy },
+	{ "MSG-SAVE", cmd_msg_save },
+	{ "MSG-GET", cmd_msg_get },
+	{ "FINISH", cmd_finish },
+	{ NULL, NULL }
+};
+
+struct dsync_proxy_server_command *
+dsync_proxy_server_command_find(const char *name)
+{
+	unsigned int i;
+
+	for (i = 0; commands[i].name != NULL; i++) {
+		if (strcasecmp(commands[i].name, name) == 0)
+			return &commands[i];
+	}
+	return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-proxy-server.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,205 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "strescape.h"
+#include "fd-set-nonblock.h"
+#include "istream.h"
+#include "ostream.h"
+#include "dsync-worker.h"
+#include "dsync-proxy.h"
+#include "dsync-proxy-server.h"
+
+#include <stdlib.h>
+
+static int
+proxy_server_read_line(struct dsync_proxy_server *server,
+		       const char **line_r)
+{
+	*line_r = i_stream_read_next_line(server->input);
+	if (*line_r == NULL) {
+		if (server->input->stream_errno != 0) {
+			errno = server->input->stream_errno;
+			i_error("read() from proxy client failed: %m");
+			io_loop_stop(current_ioloop);
+			return -1;
+		}
+		if (server->input->eof) {
+			if (!server->finished)
+				i_error("read() from proxy client failed: EOF");
+			io_loop_stop(current_ioloop);
+			return -1;
+		}
+	}
+	if (*line_r == NULL)
+		return 0;
+
+	if (!server->handshake_received) {
+		if (strcmp(*line_r, DSYNC_PROXY_CLIENT_GREETING_LINE) != 0) {
+			i_error("Invalid client handshake: %s", *line_r);
+			io_loop_stop(current_ioloop);
+			return -1;
+		}
+		server->handshake_received = TRUE;
+		return proxy_server_read_line(server, line_r);
+	}
+	return 1;
+}
+
+static int proxy_server_run_cmd(struct dsync_proxy_server *server)
+{
+	int ret;
+
+	if ((ret = server->cur_cmd->func(server, server->cur_args)) == 0)
+		return 0;
+	if (ret < 0) {
+		i_error("command %s failed", server->cur_cmd->name);
+		return -1;
+	}
+
+	server->cur_cmd = NULL;
+	server->cur_args = NULL;
+	return 1;
+}
+
+static int
+proxy_server_input_line(struct dsync_proxy_server *server, const char *line)
+{
+	const char *const *args;
+	const char **cmd_args;
+	unsigned int i, count;
+
+	i_assert(server->cur_cmd == NULL);
+
+	p_clear(server->cmd_pool);
+	args = (const char *const *)p_strsplit(server->cmd_pool, line, "\t");
+	if (args[0] == NULL) {
+		i_error("proxy client sent invalid input: %s", line);
+		return -1;
+	}
+
+	server->cur_cmd = dsync_proxy_server_command_find(args[0]);
+	if (server->cur_cmd == NULL) {
+		i_error("proxy client sent unknown command: %s", args[0]);
+		return -1;
+	} else {
+		args++;
+		count = str_array_length(args);
+
+		cmd_args = p_new(server->cmd_pool, const char *, count + 1);
+		for (i = 0; i < count; i++) {
+			cmd_args[i] = str_tabunescape(p_strdup(server->cmd_pool,
+							       args[i]));
+		}
+
+		server->cur_args = cmd_args;
+		return proxy_server_run_cmd(server);
+	}
+}
+
+static void proxy_server_input(struct dsync_proxy_server *server)
+{
+	const char *line;
+	int ret = 0;
+
+	if (server->cur_cmd != NULL) {
+		/* wait until command handling is finished */
+		io_remove(&server->io);
+		return;
+	}
+
+	o_stream_cork(server->output);
+	while (proxy_server_read_line(server, &line) > 0) {
+		T_BEGIN {
+			ret = proxy_server_input_line(server, line);
+		} T_END;
+		if (ret <= 0)
+			break;
+	}
+	o_stream_uncork(server->output);
+	if (server->output->closed)
+		ret = -1;
+
+	if (ret < 0)
+		io_loop_stop(current_ioloop);
+	timeout_reset(server->to);
+}
+
+static int proxy_server_output(struct dsync_proxy_server *server)
+{
+	struct ostream *output = server->output;
+	int ret;
+
+	if ((ret = o_stream_flush(output)) < 0)
+		ret = 1;
+	else if (server->cur_cmd != NULL) {
+		o_stream_cork(output);
+		(void)proxy_server_run_cmd(server);
+		o_stream_uncork(output);
+
+		if (server->cur_cmd == NULL) {
+			if (server->io == NULL) {
+				server->io = io_add(server->fd_in, IO_READ,
+						    proxy_server_input, server);
+			}
+			/* handle pending input */
+			proxy_server_input(server);
+		}
+	}
+	if (output->closed)
+		io_loop_stop(current_ioloop);
+	timeout_reset(server->to);
+	return ret;
+}
+
+static void dsync_proxy_server_timeout(void *context ATTR_UNUSED)
+{
+	i_error("proxy server timed out");
+	io_loop_stop(current_ioloop);
+}
+
+struct dsync_proxy_server *
+dsync_proxy_server_init(int fd_in, int fd_out, struct dsync_worker *worker)
+{
+	struct dsync_proxy_server *server;
+
+	server = i_new(struct dsync_proxy_server, 1);
+	server->worker = worker;
+	server->fd_in = fd_in;
+	server->fd_out = fd_out;
+
+	server->cmd_pool = pool_alloconly_create("worker server cmd", 1024);
+	server->io = io_add(fd_in, IO_READ, proxy_server_input, server);
+	server->input = i_stream_create_fd(fd_in, (size_t)-1, FALSE);
+	server->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
+	server->to = timeout_add(DSYNC_PROXY_SERVER_TIMEOUT_MSECS,
+				 dsync_proxy_server_timeout, NULL);
+	o_stream_set_flush_callback(server->output, proxy_server_output,
+				    server);
+	o_stream_send_str(server->output, DSYNC_PROXY_SERVER_GREETING_LINE"\n");
+	fd_set_nonblock(fd_in, TRUE);
+	fd_set_nonblock(fd_out, TRUE);
+	return server;
+}
+
+void dsync_proxy_server_deinit(struct dsync_proxy_server **_server)
+{
+	struct dsync_proxy_server *server = *_server;
+
+	*_server = NULL;
+
+	if (server->get_input != NULL)
+		i_stream_unref(&server->get_input);
+	pool_unref(&server->cmd_pool);
+	timeout_remove(&server->to);
+	if (server->io != NULL)
+		io_remove(&server->io);
+	i_stream_destroy(&server->input);
+	o_stream_destroy(&server->output);
+	if (close(server->fd_in) < 0)
+		i_error("close(proxy input) failed: %m");
+	if (server->fd_in != server->fd_out) {
+		if (close(server->fd_out) < 0)
+			i_error("close(proxy output) failed: %m");
+	}
+	i_free(server);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-proxy-server.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,46 @@
+#ifndef DSYNC_PROXY_SERVER_H
+#define DSYNC_PROXY_SERVER_H
+
+struct dsync_proxy_server;
+
+struct dsync_proxy_server_command {
+	const char *name;
+	int (*func)(struct dsync_proxy_server *server,
+		    const char *const *args);
+};
+
+struct dsync_proxy_server {
+	int fd_in, fd_out;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+	struct timeout *to;
+
+	struct dsync_worker *worker;
+
+	pool_t cmd_pool;
+	struct dsync_proxy_server_command *cur_cmd;
+	const char *const *cur_args;
+
+	struct dsync_worker_mailbox_iter *mailbox_iter;
+	struct dsync_worker_subs_iter *subs_iter;
+	struct dsync_worker_msg_iter *msg_iter;
+
+	struct istream *get_input;
+	bool get_input_last_lf;
+	uint32_t get_uid, copy_uid;
+
+	unsigned int handshake_received:1;
+	unsigned int subs_sending_unsubscriptions:1;
+	unsigned int save_finished:1;
+	unsigned int finished:1;
+};
+
+struct dsync_proxy_server *
+dsync_proxy_server_init(int fd_in, int fd_out, struct dsync_worker *worker);
+void dsync_proxy_server_deinit(struct dsync_proxy_server **server);
+
+struct dsync_proxy_server_command *
+dsync_proxy_server_command_find(const char *name);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-proxy.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,412 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "ostream.h"
+#include "hex-binary.h"
+#include "mail-types.h"
+#include "imap-util.h"
+#include "mail-cache.h"
+#include "dsync-data.h"
+#include "dsync-proxy.h"
+
+#include <stdlib.h>
+
+#define DSYNC_CACHE_DECISION_NO 'n'
+#define DSYNC_CACHE_DECISION_YES 'y'
+#define DSYNC_CACHE_DECISION_TEMP 't'
+#define DSYNC_CACHE_DECISION_FORCED 'f'
+
+void dsync_proxy_cache_fields_export(string_t *str,
+				     const ARRAY_TYPE(mailbox_cache_field) *_fields)
+{
+	const struct mailbox_cache_field *fields;
+	unsigned int i, count;
+
+	if (!array_is_created(_fields))
+		return;
+
+	fields = array_get(_fields, &count);
+	for (i = 0; i < count; i++) {
+		str_append_c(str, '\t');
+		str_tabescape_write(str, fields[i].name);
+		str_append_c(str, '\t');
+		switch (fields[i].decision & ~MAIL_CACHE_DECISION_FORCED) {
+		case MAIL_CACHE_DECISION_NO:
+			str_append_c(str, DSYNC_CACHE_DECISION_NO);
+			break;
+		case MAIL_CACHE_DECISION_YES:
+			str_append_c(str, DSYNC_CACHE_DECISION_YES);
+			break;
+		case MAIL_CACHE_DECISION_TEMP:
+			str_append_c(str, DSYNC_CACHE_DECISION_TEMP);
+			break;
+		}
+		if ((fields[i].decision & MAIL_CACHE_DECISION_FORCED) != 0)
+			str_append_c(str, DSYNC_CACHE_DECISION_FORCED);
+		str_printfa(str, "\t%ld", fields[i].last_used);
+	}
+}
+
+static int dsync_proxy_cache_dec_parse(const char *str,
+				       enum mail_cache_decision_type *dec_r)
+{
+	switch (*str++) {
+	case DSYNC_CACHE_DECISION_NO:
+		*dec_r = MAIL_CACHE_DECISION_NO;
+		break;
+	case DSYNC_CACHE_DECISION_YES:
+		*dec_r = MAIL_CACHE_DECISION_YES;
+		break;
+	case DSYNC_CACHE_DECISION_TEMP:
+		*dec_r = MAIL_CACHE_DECISION_TEMP;
+		break;
+	default:
+		return -1;
+	}
+	if (*str == DSYNC_CACHE_DECISION_FORCED) {
+		*dec_r |= MAIL_CACHE_DECISION_FORCED;
+		str++;
+	}
+	if (*str != '\0')
+		return -1;
+	return 0;
+}
+
+int dsync_proxy_cache_fields_import(const char *const *args, pool_t pool,
+				    ARRAY_TYPE(mailbox_cache_field) *fields,
+				    const char **error_r)
+{
+	struct mailbox_cache_field *cache_field;
+	enum mail_cache_decision_type dec;
+	unsigned int i, count = str_array_length(args);
+
+	if (count % 3 != 0) {
+		*error_r = "Invalid mailbox cache fields";
+		return -1;
+	}
+	if (!array_is_created(fields))
+		p_array_init(fields, pool, (count/3) + 1);
+	for (i = 0; i < count; i += 3) {
+		cache_field = array_append_space(fields);
+		cache_field->name = p_strdup(pool, args[i]);
+
+		if (dsync_proxy_cache_dec_parse(args[i+1], &dec) < 0) {
+			*error_r = "Invalid cache decision";
+			return -1;
+		}
+		cache_field->decision = dec;
+		if (str_to_time(args[i+2], &cache_field->last_used) < 0) {
+			*error_r = "Invalid cache last_used";
+			return -1;
+		}
+	}
+	return 0;
+}
+
+void dsync_proxy_msg_export(string_t *str,
+			    const struct dsync_message *msg)
+{
+	str_tabescape_write(str, msg->guid);
+	str_printfa(str, "\t%u\t%llu\t", msg->uid,
+		    (unsigned long long)msg->modseq);
+	if ((msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0)
+		str_append(str, "\\dsync-expunged ");
+	imap_write_flags(str, msg->flags & MAIL_FLAGS_MASK, msg->keywords);
+	str_printfa(str, "\t%ld", (long)msg->save_date);
+}
+
+int dsync_proxy_msg_parse_flags(pool_t pool, const char *str,
+				struct dsync_message *msg_r)
+{
+	ARRAY_TYPE(const_string) keywords;
+	const char *const *args, *kw;
+	enum mail_flags flag;
+
+	msg_r->flags = 0;
+	p_array_init(&keywords, pool, 16);
+	for (args = t_strsplit_spaces(str, " "); *args != NULL; args++) {
+		if (**args != '\\') {
+			kw = p_strdup(pool, *args);
+			array_append(&keywords, &kw, 1);
+		} else if (strcasecmp(*args, "\\dsync-expunged") == 0) {
+			msg_r->flags |= DSYNC_MAIL_FLAG_EXPUNGED;
+		} else {
+			flag = imap_parse_system_flag(*args);
+			if (flag == 0)
+				return -1;
+			msg_r->flags |= flag;
+		}
+	}
+	(void)array_append_space(&keywords);
+
+	msg_r->keywords = array_idx(&keywords, 0);
+	return 0;
+}
+
+int dsync_proxy_msg_import_unescaped(pool_t pool, const char *const *args,
+				     struct dsync_message *msg_r,
+				     const char **error_r)
+{
+	/* guid uid modseq flags save_date */
+	if (str_array_length(args) < 5) {
+		*error_r = "Missing parameters";
+		return -1;
+	}
+
+	memset(msg_r, 0, sizeof(*msg_r));
+	msg_r->guid = p_strdup(pool, args[0]);
+	msg_r->uid = strtoul(args[1], NULL, 10);
+	msg_r->modseq = strtoull(args[2], NULL, 10);
+	if (dsync_proxy_msg_parse_flags(pool, args[3], msg_r) < 0) {
+		*error_r = "Invalid system flags";
+		return -1;
+	}
+	msg_r->save_date = strtoul(args[4], NULL, 10);
+	return 0;
+}
+
+int dsync_proxy_msg_import(pool_t pool, const char *str,
+			   struct dsync_message *msg_r, const char **error_r)
+{
+	char **args;
+	unsigned int i;
+	int ret;
+
+	T_BEGIN {
+		args = p_strsplit(pool_datastack_create(), str, "\t");
+		for (i = 0; args[i] != NULL; i++)
+			args[i] = str_tabunescape(args[i]);
+		ret = dsync_proxy_msg_import_unescaped(pool,
+						(const char *const *)args,
+						msg_r, error_r);
+	} T_END;
+	return ret;
+}
+
+void dsync_proxy_msg_static_export(string_t *str,
+				   const struct dsync_msg_static_data *msg)
+{
+	str_printfa(str, "%ld\t", (long)msg->received_date);
+	str_tabescape_write(str, msg->pop3_uidl);
+}
+
+int dsync_proxy_msg_static_import_unescaped(pool_t pool,
+					    const char *const *args,
+					    struct dsync_msg_static_data *msg_r,
+					    const char **error_r)
+{
+	/* received_date pop3_uidl */
+	if (str_array_length(args) < 2) {
+		*error_r = "Missing parameters";
+		return -1;
+	}
+
+	memset(msg_r, 0, sizeof(*msg_r));
+	msg_r->received_date = strtoul(args[0], NULL, 10);
+	msg_r->pop3_uidl = p_strdup(pool, args[1]);
+	return 0;
+}
+
+int dsync_proxy_msg_static_import(pool_t pool, const char *str,
+				  struct dsync_msg_static_data *msg_r,
+				  const char **error_r)
+{
+	char **args;
+	unsigned int i;
+	int ret;
+
+	T_BEGIN {
+		args = p_strsplit(pool_datastack_create(), str, "\t");
+		for (i = 0; args[i] != NULL; i++)
+			args[i] = str_tabunescape(args[i]);
+		ret = dsync_proxy_msg_static_import_unescaped(pool, 
+						(const char *const *)args,
+						msg_r, error_r);
+	} T_END;
+	return ret;
+}
+
+void dsync_proxy_mailbox_export(string_t *str,
+				const struct dsync_mailbox *box)
+{
+	char s[2];
+
+	str_tabescape_write(str, box->name);
+	str_append_c(str, '\t');
+	s[0] = box->name_sep; s[1] = '\0';
+	str_tabescape_write(str, s);
+	str_printfa(str, "\t%lu\t%u", (unsigned long)box->last_change,
+		    box->flags);
+
+	if (dsync_mailbox_is_noselect(box)) {
+		i_assert(box->uid_validity == 0);
+		return;
+	}
+	i_assert(box->uid_validity != 0 ||
+		 (box->flags & DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0);
+	i_assert(box->uid_validity == 0 || box->uid_next != 0);
+
+	str_append_c(str, '\t');
+	dsync_proxy_mailbox_guid_export(str, &box->mailbox_guid);
+	str_printfa(str, "\t%u\t%u\t%u\t%llu\t%u",
+		    box->uid_validity, box->uid_next, box->message_count,
+		    (unsigned long long)box->highest_modseq,
+		    box->first_recent_uid);
+	dsync_proxy_cache_fields_export(str, &box->cache_fields);
+}
+
+int dsync_proxy_mailbox_import_unescaped(pool_t pool, const char *const *args,
+					 struct dsync_mailbox *box_r,
+					 const char **error_r)
+{
+	unsigned int i = 0, count;
+	bool box_deleted;
+	char *p;
+
+	memset(box_r, 0, sizeof(*box_r));
+
+	count = str_array_length(args);
+	if (count != 4 && count < 8) {
+		*error_r = "Mailbox missing parameters";
+		return -1;
+	}
+
+	/* name dir_guid mailbox_guid uid_validity uid_next
+	   message_count highest_modseq */
+	box_r->name = p_strdup(pool, args[i++]);
+	dsync_str_sha_to_guid(box_r->name, &box_r->name_sha1);
+
+	if (strlen(args[i]) > 1) {
+		*error_r = "Invalid mailbox name hierarchy separator";
+		return -1;
+	}
+	box_r->name_sep = args[i++][0];
+
+	box_r->last_change = strtoul(args[i++], &p, 10);
+	if (*p != '\0') {
+		*error_r = "Invalid mailbox last_change";
+		return -1;
+	}
+	box_r->flags = strtoul(args[i++], &p, 10);
+	if (*p != '\0' ||
+	    (dsync_mailbox_is_noselect(box_r) != (args[i] == NULL))) {
+		*error_r = "Invalid mailbox flags";
+		return -1;
+	}
+	box_deleted = (box_r->flags & (DSYNC_MAILBOX_FLAG_DELETED_MAILBOX |
+				       DSYNC_MAILBOX_FLAG_DELETED_DIR)) != 0;
+	if (box_r->name_sep == '\0' && !box_deleted) {
+		*error_r = "Missing mailbox name hierarchy separator";
+		return -1;
+	}
+
+	if (args[i] == NULL) {
+		/* \noselect mailbox */
+		return 0;
+	}
+
+	if (dsync_proxy_mailbox_guid_import(args[i++],
+					    &box_r->mailbox_guid) < 0) {
+		*error_r = "Invalid mailbox GUID";
+		return -1;
+	}
+
+	box_r->uid_validity = strtoul(args[i++], &p, 10);
+	if (*p != '\0' || (box_r->uid_validity == 0 && !box_deleted)) {
+		abort();
+		*error_r = "Invalid mailbox uid_validity";
+		return -1;
+	}
+
+	box_r->uid_next = strtoul(args[i++], &p, 10);
+	if (*p != '\0' || (box_r->uid_next == 0 && !box_deleted)) {
+		*error_r = "Invalid mailbox uid_next";
+		return -1;
+	}
+
+	box_r->message_count = strtoul(args[i++], &p, 10);
+	if (*p != '\0') {
+		*error_r = "Invalid mailbox message_count";
+		return -1;
+	}
+
+	box_r->highest_modseq = strtoull(args[i++], &p, 10);
+	if (*p != '\0') {
+		*error_r = "Invalid mailbox highest_modseq";
+		return -1;
+	}
+
+	box_r->first_recent_uid = strtoul(args[i++], &p, 10);
+	if (*p != '\0') {
+		*error_r = "Invalid mailbox first_recent_uid";
+		return -1;
+	}
+
+	args += i;
+	count -= i;
+	if (dsync_proxy_cache_fields_import(args, pool, &box_r->cache_fields,
+					    error_r) < 0)
+		return -1;
+	return 0;
+}
+
+int dsync_proxy_mailbox_import(pool_t pool, const char *str,
+			       struct dsync_mailbox *box_r,
+			       const char **error_r)
+{
+	char **args;
+	int ret;
+
+	T_BEGIN {
+		args = p_strsplit(pool_datastack_create(), str, "\t");
+		if (args[0] != NULL)
+			args[0] = str_tabunescape(args[0]);
+		ret = dsync_proxy_mailbox_import_unescaped(pool,
+						(const char *const *)args,
+						box_r, error_r);
+	} T_END;
+	return ret;
+}
+
+void dsync_proxy_mailbox_guid_export(string_t *str,
+				     const mailbox_guid_t *mailbox)
+{
+	str_append(str, dsync_guid_to_str(mailbox));
+}
+
+int dsync_proxy_mailbox_guid_import(const char *str, mailbox_guid_t *guid_r)
+{
+	buffer_t *buf;
+
+	buf = buffer_create_dynamic(pool_datastack_create(),
+				    sizeof(guid_r->guid));
+	if (hex_to_binary(str, buf) < 0 || buf->used != sizeof(guid_r->guid))
+		return -1;
+	memcpy(guid_r->guid, buf->data, sizeof(guid_r->guid));
+	return 0;
+}
+
+void dsync_proxy_send_dot_output(struct ostream *output, bool *last_lf,
+				 const unsigned char *data, size_t size)
+{
+	size_t i, start;
+
+	i_assert(size > 0);
+
+	if (*last_lf && data[0] == '.')
+		o_stream_send(output, ".", 1);
+
+	for (i = 1, start = 0; i < size; i++) {
+		if (data[i-1] == '\n' && data[i] == '.') {
+			o_stream_send(output, data + start, i - start);
+			o_stream_send(output, ".", 1);
+			start = i;
+		}
+	}
+	o_stream_send(output, data + start, i - start);
+	*last_lf = data[i-1] == '\n';
+	i_assert(i == size);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-proxy.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,55 @@
+#ifndef DSYNC_PROXY_H
+#define DSYNC_PROXY_H
+
+#include "dsync-data.h"
+
+#define DSYNC_PROXY_CLIENT_TIMEOUT_MSECS (14*60*1000)
+#define DSYNC_PROXY_SERVER_TIMEOUT_MSECS (15*60*1000)
+
+#define DSYNC_PROXY_CLIENT_GREETING_LINE "dsync-client\t2"
+#define DSYNC_PROXY_SERVER_GREETING_LINE "dsync-server\t2"
+
+struct dsync_message;
+struct dsync_mailbox;
+
+void dsync_proxy_cache_fields_export(string_t *str,
+				     const ARRAY_TYPE(mailbox_cache_field) *fields);
+int dsync_proxy_cache_fields_import(const char *const *args, pool_t pool,
+				    ARRAY_TYPE(mailbox_cache_field) *fields,
+				    const char **error_r);
+
+void dsync_proxy_msg_export(string_t *str, const struct dsync_message *msg);
+int dsync_proxy_msg_parse_flags(pool_t pool, const char *str,
+				struct dsync_message *msg_r);
+int dsync_proxy_msg_import_unescaped(pool_t pool, const char *const *args,
+				     struct dsync_message *msg_r,
+				     const char **error_r);
+int dsync_proxy_msg_import(pool_t pool, const char *str,
+			   struct dsync_message *msg_r, const char **error_r);
+
+void dsync_proxy_msg_static_export(string_t *str,
+				   const struct dsync_msg_static_data *msg);
+int dsync_proxy_msg_static_import(pool_t pool, const char *str,
+				  struct dsync_msg_static_data *msg_r,
+				  const char **error_r);
+int dsync_proxy_msg_static_import_unescaped(pool_t pool,
+					    const char *const *args,
+					    struct dsync_msg_static_data *msg_r,
+					    const char **error_r);
+
+void dsync_proxy_mailbox_export(string_t *str, const struct dsync_mailbox *box);
+int dsync_proxy_mailbox_import(pool_t pool, const char *str,
+			       struct dsync_mailbox *box_r,
+			       const char **error_r);
+int dsync_proxy_mailbox_import_unescaped(pool_t pool, const char *const *args,
+					 struct dsync_mailbox *box_r,
+					 const char **error_r);
+
+void dsync_proxy_mailbox_guid_export(string_t *str,
+				     const mailbox_guid_t *mailbox);
+int dsync_proxy_mailbox_guid_import(const char *str, mailbox_guid_t *guid_r);
+
+void dsync_proxy_send_dot_output(struct ostream *output, bool *last_lf,
+				 const unsigned char *data, size_t size);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-worker-local.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,1899 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "network.h"
+#include "istream.h"
+#include "settings-parser.h"
+#include "mailbox-log.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-search-build.h"
+#include "mailbox-list-private.h"
+#include "dsync-worker-private.h"
+
+#include <ctype.h>
+
+struct local_dsync_worker_mailbox_iter {
+	struct dsync_worker_mailbox_iter iter;
+	pool_t ret_pool;
+	struct mailbox_list_iterate_context *list_iter;
+	struct hash_iterate_context *deleted_iter;
+	struct hash_iterate_context *deleted_dir_iter;
+};
+
+struct local_dsync_worker_subs_iter {
+	struct dsync_worker_subs_iter iter;
+	struct mailbox_list_iterate_context *list_iter;
+	struct hash_iterate_context *deleted_iter;
+};
+
+struct local_dsync_worker_msg_iter {
+	struct dsync_worker_msg_iter iter;
+	mailbox_guid_t *mailboxes;
+	unsigned int mailbox_idx, mailbox_count;
+
+	struct mail_search_context *search_ctx;
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+	uint32_t prev_uid;
+
+	string_t *tmp_guid_str;
+	ARRAY_TYPE(mailbox_expunge_rec) expunges;
+	unsigned int expunge_idx;
+	unsigned int expunges_set:1;
+};
+
+struct local_dsync_mailbox {
+	struct mail_namespace *ns;
+	mailbox_guid_t guid;
+	const char *name;
+	bool deleted;
+};
+
+struct local_dsync_mailbox_change {
+	mailbox_guid_t guid;
+	time_t last_delete;
+
+	unsigned int deleted_mailbox:1;
+};
+
+struct local_dsync_dir_change {
+	mailbox_guid_t name_sha1;
+	struct mailbox_list *list;
+
+	time_t last_rename;
+	time_t last_delete;
+	time_t last_subs_change;
+
+	unsigned int unsubscribed:1;
+	unsigned int deleted_dir:1;
+};
+
+struct local_dsync_worker_msg_get {
+	mailbox_guid_t mailbox;
+	uint32_t uid;
+	dsync_worker_msg_callback_t *callback;
+	void *context;
+};
+
+struct local_dsync_worker {
+	struct dsync_worker worker;
+	struct mail_user *user;
+
+	pool_t pool;
+	/* mailbox_guid_t -> struct local_dsync_mailbox* */
+	struct hash_table *mailbox_hash;
+	/* mailbox_guid_t -> struct local_dsync_mailbox_change* */
+	struct hash_table *mailbox_changes_hash;
+	/* <-> struct local_dsync_dir_change */
+	struct hash_table *dir_changes_hash;
+
+	char alt_char;
+	ARRAY_DEFINE(subs_namespaces, struct mail_namespace *);
+
+	mailbox_guid_t selected_box_guid;
+	struct mailbox *selected_box;
+	struct mail *mail, *ext_mail;
+
+	ARRAY_TYPE(uint32_t) saved_uids;
+
+	mailbox_guid_t get_mailbox;
+	struct mail *get_mail;
+	ARRAY_DEFINE(msg_get_queue, struct local_dsync_worker_msg_get);
+
+	struct io *save_io;
+	struct mail_save_context *save_ctx;
+	struct istream *save_input;
+	dsync_worker_save_callback_t *save_callback;
+	void *save_context;
+
+	dsync_worker_finish_callback_t *finish_callback;
+	void *finish_context;
+
+	unsigned int reading_mail:1;
+	unsigned int finishing:1;
+	unsigned int finished:1;
+};
+
+extern struct dsync_worker_vfuncs local_dsync_worker;
+
+static void local_worker_mailbox_close(struct local_dsync_worker *worker);
+static void local_worker_msg_box_close(struct local_dsync_worker *worker);
+static void
+local_worker_msg_get_next(struct local_dsync_worker *worker,
+			  const struct local_dsync_worker_msg_get *get);
+
+static int mailbox_guid_cmp(const void *p1, const void *p2)
+{
+	const mailbox_guid_t *g1 = p1, *g2 = p2;
+
+	return memcmp(g1->guid, g2->guid, sizeof(g1->guid));
+}
+
+static unsigned int mailbox_guid_hash(const void *p)
+{
+	const mailbox_guid_t *guid = p;
+        const uint8_t *s = guid->guid;
+	unsigned int i, g, h = 0;
+
+	for (i = 0; i < sizeof(guid->guid); i++) {
+		h = (h << 4) + s[i];
+		if ((g = h & 0xf0000000UL)) {
+			h = h ^ (g >> 24);
+			h = h ^ g;
+		}
+	}
+	return h;
+}
+
+static struct mail_namespace *
+namespace_find_set(struct mail_user *user,
+		   const struct mail_namespace_settings *set)
+{
+	struct mail_namespace *ns;
+
+	for (ns = user->namespaces; ns != NULL; ns = ns->next) {
+		/* compare settings pointers so that it'll work
+		   for shared namespaces */
+		if (ns->set == set)
+			return ns;
+	}
+	return NULL;
+}
+
+static void dsync_drop_extra_namespaces(struct local_dsync_worker *worker)
+{
+	struct mail_user *user = worker->user;
+	struct mail_namespace_settings *const *ns_unset, *const *ns_set;
+	struct mail_namespace *ns;
+	unsigned int i, count, count2;
+
+	if (!array_is_created(&user->unexpanded_set->namespaces))
+		return;
+
+	/* drop all namespaces that have a location defined internally */
+	ns_unset = array_get(&user->unexpanded_set->namespaces, &count);
+	ns_set = array_get(&user->set->namespaces, &count2);
+	i_assert(count == count2);
+	for (i = 0; i < count; i++) {
+		if (strcmp(ns_unset[i]->location,
+			   SETTING_STRVAR_UNEXPANDED) == 0)
+			continue;
+
+		ns = namespace_find_set(user, ns_set[i]);
+		i_assert(ns != NULL);
+		if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0) {
+			/* remember the subscriptions=no namespaces so we can
+			   handle their subscriptions in parent namespaces
+			   properly */
+			mail_namespace_ref(ns);
+			array_append(&worker->subs_namespaces, &ns, 1);
+		}
+		mail_namespace_destroy(ns);
+	}
+	if (user->namespaces == NULL) {
+		i_fatal("All your namespaces have a location setting. "
+			"It should be empty (default mail_location) in the "
+			"namespace to be converted.");
+	}
+}
+
+struct dsync_worker *
+dsync_worker_init_local(struct mail_user *user, char alt_char)
+{
+	struct local_dsync_worker *worker;
+	pool_t pool;
+
+	pool = pool_alloconly_create("local dsync worker", 10240);
+	worker = p_new(pool, struct local_dsync_worker, 1);
+	worker->worker.v = local_dsync_worker;
+	worker->user = user;
+	worker->pool = pool;
+	worker->alt_char = alt_char;
+	worker->mailbox_hash =
+		hash_table_create(default_pool, pool, 0,
+				  mailbox_guid_hash, mailbox_guid_cmp);
+	i_array_init(&worker->saved_uids, 128);
+	i_array_init(&worker->msg_get_queue, 32);
+	p_array_init(&worker->subs_namespaces, pool, 8);
+	dsync_drop_extra_namespaces(worker);
+
+	mail_user_ref(worker->user);
+	return &worker->worker;
+}
+
+static void local_worker_deinit(struct dsync_worker *_worker)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mail_namespace **nsp;
+
+	i_assert(worker->save_input == NULL);
+
+	array_foreach_modifiable(&worker->subs_namespaces, nsp)
+		mail_namespace_unref(nsp);
+
+	local_worker_msg_box_close(worker);
+	local_worker_mailbox_close(worker);
+	mail_user_unref(&worker->user);
+
+	hash_table_destroy(&worker->mailbox_hash);
+	if (worker->mailbox_changes_hash != NULL)
+		hash_table_destroy(&worker->mailbox_changes_hash);
+	if (worker->dir_changes_hash != NULL)
+		hash_table_destroy(&worker->dir_changes_hash);
+	array_free(&worker->msg_get_queue);
+	array_free(&worker->saved_uids);
+	pool_unref(&worker->pool);
+}
+
+static bool local_worker_is_output_full(struct dsync_worker *worker ATTR_UNUSED)
+{
+	return FALSE;
+}
+
+static int local_worker_output_flush(struct dsync_worker *worker ATTR_UNUSED)
+{
+	return 1;
+}
+
+static void
+dsync_worker_save_mailbox_change(struct local_dsync_worker *worker,
+				 const struct mailbox_log_record *rec)
+{
+	struct local_dsync_mailbox_change *change;
+	time_t stamp;
+
+	change = hash_table_lookup(worker->mailbox_changes_hash,
+				   rec->mailbox_guid);
+	if (change == NULL) {
+		change = i_new(struct local_dsync_mailbox_change, 1);
+		memcpy(change->guid.guid, rec->mailbox_guid,
+		       sizeof(change->guid.guid));
+		hash_table_insert(worker->mailbox_changes_hash,
+				  change->guid.guid, change);
+	}
+
+	stamp = mailbox_log_record_get_timestamp(rec);
+	switch (rec->type) {
+	case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
+		change->deleted_mailbox = TRUE;
+		if (change->last_delete < stamp)
+			change->last_delete = stamp;
+		break;
+	case MAILBOX_LOG_RECORD_DELETE_DIR:
+	case MAILBOX_LOG_RECORD_RENAME:
+	case MAILBOX_LOG_RECORD_SUBSCRIBE:
+	case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
+		i_unreached();
+	}
+}
+
+static void
+dsync_worker_save_dir_change(struct local_dsync_worker *worker,
+			     struct mailbox_list *list,
+			     const struct mailbox_log_record *rec)
+{
+	struct local_dsync_dir_change *change, new_change;
+	time_t stamp;
+
+	memset(&new_change, 0, sizeof(new_change));
+	new_change.list = list;
+	memcpy(new_change.name_sha1.guid, rec->mailbox_guid,
+	       sizeof(new_change.name_sha1.guid));
+
+	stamp = mailbox_log_record_get_timestamp(rec);
+	change = hash_table_lookup(worker->dir_changes_hash, &new_change);
+	if (change == NULL) {
+		change = i_new(struct local_dsync_dir_change, 1);
+		*change = new_change;
+		hash_table_insert(worker->dir_changes_hash, change, change);
+	}
+
+	switch (rec->type) {
+	case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
+		i_unreached();
+	case MAILBOX_LOG_RECORD_DELETE_DIR:
+		change->deleted_dir = TRUE;
+		if (change->last_delete < stamp)
+			change->last_delete = stamp;
+		break;
+	case MAILBOX_LOG_RECORD_RENAME:
+		if (change->last_rename < stamp)
+			change->last_rename = stamp;
+		break;
+	case MAILBOX_LOG_RECORD_SUBSCRIBE:
+	case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
+		if (change->last_subs_change > stamp) {
+			/* we've already seen a newer subscriptions state. this
+			   is probably a stale record created by dsync */
+		} else {
+			change->last_subs_change = stamp;
+			change->unsubscribed =
+				rec->type == MAILBOX_LOG_RECORD_UNSUBSCRIBE;
+		}
+		break;
+	}
+}
+
+static int
+dsync_worker_get_list_mailbox_log(struct local_dsync_worker *worker,
+				  struct mailbox_list *list)
+{
+	struct mailbox_log *log;
+	struct mailbox_log_iter *iter;
+	const struct mailbox_log_record *rec;
+
+	log = mailbox_list_get_changelog(list);
+	iter = mailbox_log_iter_init(log);
+	while ((rec = mailbox_log_iter_next(iter)) != NULL) {
+		switch (rec->type) {
+		case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
+			dsync_worker_save_mailbox_change(worker, rec);
+			break;
+		case MAILBOX_LOG_RECORD_DELETE_DIR:
+		case MAILBOX_LOG_RECORD_RENAME:
+		case MAILBOX_LOG_RECORD_SUBSCRIBE:
+		case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
+			dsync_worker_save_dir_change(worker, list, rec);
+			break;
+		}
+	}
+	return mailbox_log_iter_deinit(&iter);
+}
+
+static unsigned int mailbox_log_record_hash(const void *p)
+{
+	const uint8_t *guid = p;
+
+	return ((unsigned int)guid[0] << 24) |
+		((unsigned int)guid[1] << 16) |
+		((unsigned int)guid[2] << 8) |
+		(unsigned int)guid[3];
+}
+
+static int mailbox_log_record_cmp(const void *p1, const void *p2)
+{
+	return memcmp(p1, p2, GUID_128_SIZE);
+}
+
+static unsigned int dir_change_hash(const void *p)
+{
+	const struct local_dsync_dir_change *change = p;
+
+	return mailbox_log_record_hash(change->name_sha1.guid) ^
+		POINTER_CAST_TO(change->list, unsigned int);
+}
+
+static int dir_change_cmp(const void *p1, const void *p2)
+{
+	const struct local_dsync_dir_change *c1 = p1, *c2 = p2;
+
+	if (c1->list != c2->list)
+		return 1;
+
+	return memcmp(c1->name_sha1.guid, c2->name_sha1.guid,
+		      GUID_128_SIZE);
+}
+
+static int dsync_worker_get_mailbox_log(struct local_dsync_worker *worker)
+{
+	struct mail_namespace *ns;
+	int ret = 0;
+
+	if (worker->mailbox_changes_hash != NULL)
+		return 0;
+
+	worker->mailbox_changes_hash =
+		hash_table_create(default_pool, worker->pool, 0,
+				  mailbox_log_record_hash,
+				  mailbox_log_record_cmp);
+	worker->dir_changes_hash =
+		hash_table_create(default_pool, worker->pool, 0,
+				  dir_change_hash, dir_change_cmp);
+	for (ns = worker->user->namespaces; ns != NULL; ns = ns->next) {
+		if (ns->alias_for != NULL)
+			continue;
+
+		if (dsync_worker_get_list_mailbox_log(worker, ns->list) < 0)
+			ret = -1;
+	}
+	return ret;
+}
+
+static struct dsync_worker_mailbox_iter *
+local_worker_mailbox_iter_init(struct dsync_worker *_worker)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct local_dsync_worker_mailbox_iter *iter;
+	enum mailbox_list_iter_flags list_flags =
+		MAILBOX_LIST_ITER_SKIP_ALIASES |
+		MAILBOX_LIST_ITER_NO_AUTO_BOXES;
+	static const char *patterns[] = { "*", NULL };
+
+	iter = i_new(struct local_dsync_worker_mailbox_iter, 1);
+	iter->iter.worker = _worker;
+	iter->ret_pool = pool_alloconly_create("local mailbox iter", 1024);
+	iter->list_iter =
+		mailbox_list_iter_init_namespaces(worker->user->namespaces,
+						  patterns, NAMESPACE_PRIVATE,
+						  list_flags);
+	(void)dsync_worker_get_mailbox_log(worker);
+	return &iter->iter;
+}
+
+static void
+local_dsync_worker_add_mailbox(struct local_dsync_worker *worker,
+			       struct mail_namespace *ns, const char *name,
+			       const mailbox_guid_t *guid)
+{
+	struct local_dsync_mailbox *lbox;
+
+	lbox = p_new(worker->pool, struct local_dsync_mailbox, 1);
+	lbox->ns = ns;
+	memcpy(lbox->guid.guid, guid->guid, sizeof(lbox->guid.guid));
+	lbox->name = p_strdup(worker->pool, name);
+
+	hash_table_insert(worker->mailbox_hash, &lbox->guid, lbox);
+}
+
+static int
+iter_next_deleted(struct local_dsync_worker_mailbox_iter *iter,
+		  struct local_dsync_worker *worker,
+		  struct dsync_mailbox *dsync_box_r)
+{
+	void *key, *value;
+
+	if (iter->deleted_iter == NULL) {
+		iter->deleted_iter =
+			hash_table_iterate_init(worker->mailbox_changes_hash);
+	}
+	while (hash_table_iterate(iter->deleted_iter, &key, &value)) {
+		const struct local_dsync_mailbox_change *change = value;
+
+		if (change->deleted_mailbox) {
+			/* the name doesn't matter */
+			dsync_box_r->name = "";
+			dsync_box_r->mailbox_guid = change->guid;
+			dsync_box_r->last_change = change->last_delete;
+			dsync_box_r->flags |=
+				DSYNC_MAILBOX_FLAG_DELETED_MAILBOX;
+			return 1;
+		}
+	}
+
+	if (iter->deleted_dir_iter == NULL) {
+		iter->deleted_dir_iter =
+			hash_table_iterate_init(worker->dir_changes_hash);
+	}
+	while (hash_table_iterate(iter->deleted_dir_iter, &key, &value)) {
+		const struct local_dsync_dir_change *change = value;
+
+		if (change->deleted_dir) {
+			/* the name doesn't matter */
+			dsync_box_r->name = "";
+			dsync_box_r->name_sha1 = change->name_sha1;
+			dsync_box_r->last_change = change->last_delete;
+			dsync_box_r->flags |= DSYNC_MAILBOX_FLAG_NOSELECT |
+				DSYNC_MAILBOX_FLAG_DELETED_DIR;
+			return 1;
+		}
+	}
+	hash_table_iterate_deinit(&iter->deleted_iter);
+	return -1;
+}
+
+static int
+local_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *_iter,
+			       struct dsync_mailbox *dsync_box_r)
+{
+	struct local_dsync_worker_mailbox_iter *iter =
+		(struct local_dsync_worker_mailbox_iter *)_iter;
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_iter->worker;
+	const enum mailbox_flags flags = MAILBOX_FLAG_READONLY;
+	const enum mailbox_status_items status_items =
+		STATUS_UIDNEXT | STATUS_UIDVALIDITY |
+		STATUS_HIGHESTMODSEQ | STATUS_FIRST_RECENT_UID;
+	const enum mailbox_metadata_items metadata_items =
+		MAILBOX_METADATA_CACHE_FIELDS | MAILBOX_METADATA_GUID;
+	const struct mailbox_info *info;
+	const char *storage_name;
+	struct mailbox *box;
+	struct mailbox_status status;
+	struct mailbox_metadata metadata;
+	struct local_dsync_mailbox_change *change;
+	struct local_dsync_dir_change *dir_change, change_lookup;
+	struct local_dsync_mailbox *old_lbox;
+	enum mail_error error;
+	struct mailbox_cache_field *cache_fields;
+	unsigned int i, cache_field_count;
+
+	memset(dsync_box_r, 0, sizeof(*dsync_box_r));
+
+	info = mailbox_list_iter_next(iter->list_iter);
+	if (info == NULL)
+		return iter_next_deleted(iter, worker, dsync_box_r);
+
+	dsync_box_r->name = info->name;
+	dsync_box_r->name_sep = mail_namespace_get_sep(info->ns);
+
+	storage_name = mailbox_list_get_storage_name(info->ns->list, info->name);
+	dsync_str_sha_to_guid(storage_name, &dsync_box_r->name_sha1);
+
+	/* get last change timestamp */
+	change_lookup.list = info->ns->list;
+	change_lookup.name_sha1 = dsync_box_r->name_sha1;
+	dir_change = hash_table_lookup(worker->dir_changes_hash,
+				       &change_lookup);
+	if (dir_change != NULL) {
+		/* it shouldn't be marked as deleted, but drop it to be sure */
+		dir_change->deleted_dir = FALSE;
+		dsync_box_r->last_change = dir_change->last_rename;
+	}
+
+	if ((info->flags & MAILBOX_NOSELECT) != 0) {
+		dsync_box_r->flags |= DSYNC_MAILBOX_FLAG_NOSELECT;
+		local_dsync_worker_add_mailbox(worker, info->ns, info->name,
+					       &dsync_box_r->name_sha1);
+		return 1;
+	}
+
+	box = mailbox_alloc(info->ns->list, info->name, flags);
+	if (mailbox_get_status(box, status_items, &status) < 0 ||
+	    mailbox_get_metadata(box, metadata_items, &metadata) < 0) {
+		i_error("Failed to sync mailbox %s: %s", info->name,
+			mailbox_get_last_error(box, &error));
+		mailbox_free(&box);
+		if (error == MAIL_ERROR_NOTFOUND ||
+		    error == MAIL_ERROR_NOTPOSSIBLE) {
+			/* Mailbox isn't selectable, try the next one. We
+			   should have already caught \Noselect mailboxes, but
+			   check them anyway here. The NOTPOSSIBLE check is
+			   mainly for invalid mbox files. */
+			return local_worker_mailbox_iter_next(_iter,
+							      dsync_box_r);
+		}
+		_iter->failed = TRUE;
+		return -1;
+	}
+
+	change = hash_table_lookup(worker->mailbox_changes_hash, metadata.guid);
+	if (change != NULL) {
+		/* it shouldn't be marked as deleted, but drop it to be sure */
+		change->deleted_mailbox = FALSE;
+	}
+
+	memcpy(dsync_box_r->mailbox_guid.guid, metadata.guid,
+	       sizeof(dsync_box_r->mailbox_guid.guid));
+	dsync_box_r->uid_validity = status.uidvalidity;
+	dsync_box_r->uid_next = status.uidnext;
+	dsync_box_r->message_count = status.messages;
+	dsync_box_r->first_recent_uid = status.first_recent_uid;
+	dsync_box_r->highest_modseq = status.highest_modseq;
+
+	p_clear(iter->ret_pool);
+	p_array_init(&dsync_box_r->cache_fields, iter->ret_pool,
+		     array_count(metadata.cache_fields));
+	array_append_array(&dsync_box_r->cache_fields, metadata.cache_fields);
+	cache_fields = array_get_modifiable(&dsync_box_r->cache_fields,
+					    &cache_field_count);
+	for (i = 0; i < cache_field_count; i++) {
+		cache_fields[i].name =
+			p_strdup(iter->ret_pool, cache_fields[i].name);
+	}
+
+	old_lbox = hash_table_lookup(worker->mailbox_hash,
+				     &dsync_box_r->mailbox_guid);
+	if (old_lbox != NULL) {
+		i_error("Mailboxes don't have unique GUIDs: "
+			"%s is shared by %s and %s",
+			dsync_guid_to_str(&dsync_box_r->mailbox_guid),
+			old_lbox->name, info->name);
+		mailbox_free(&box);
+		_iter->failed = TRUE;
+		return -1;
+	}
+	local_dsync_worker_add_mailbox(worker, info->ns, info->name,
+				       &dsync_box_r->mailbox_guid);
+	mailbox_free(&box);
+	return 1;
+}
+
+static int
+local_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter *_iter)
+{
+	struct local_dsync_worker_mailbox_iter *iter =
+		(struct local_dsync_worker_mailbox_iter *)_iter;
+	int ret = _iter->failed ? -1 : 0;
+
+	if (mailbox_list_iter_deinit(&iter->list_iter) < 0)
+		ret = -1;
+	pool_unref(&iter->ret_pool);
+	i_free(iter);
+	return ret;
+}
+
+static struct dsync_worker_subs_iter *
+local_worker_subs_iter_init(struct dsync_worker *_worker)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct local_dsync_worker_subs_iter *iter;
+	enum mailbox_list_iter_flags list_flags =
+		MAILBOX_LIST_ITER_SKIP_ALIASES |
+		MAILBOX_LIST_ITER_SELECT_SUBSCRIBED;
+	static const char *patterns[] = { "*", NULL };
+
+	iter = i_new(struct local_dsync_worker_subs_iter, 1);
+	iter->iter.worker = _worker;
+	iter->list_iter =
+		mailbox_list_iter_init_namespaces(worker->user->namespaces,
+						  patterns, NAMESPACE_PRIVATE,
+						  list_flags);
+	(void)dsync_worker_get_mailbox_log(worker);
+	return &iter->iter;
+}
+
+static int
+local_worker_subs_iter_next(struct dsync_worker_subs_iter *_iter,
+			    struct dsync_worker_subscription *rec_r)
+{
+	struct local_dsync_worker_subs_iter *iter =
+		(struct local_dsync_worker_subs_iter *)_iter;
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_iter->worker;
+	struct local_dsync_dir_change *change, change_lookup;
+	const struct mailbox_info *info;
+	const char *storage_name;
+
+	memset(rec_r, 0, sizeof(*rec_r));
+
+	info = mailbox_list_iter_next(iter->list_iter);
+	if (info == NULL)
+		return -1;
+
+	storage_name = mailbox_list_get_storage_name(info->ns->list, info->name);
+	dsync_str_sha_to_guid(storage_name, &change_lookup.name_sha1);
+	change_lookup.list = info->ns->list;
+
+	change = hash_table_lookup(worker->dir_changes_hash,
+				   &change_lookup);
+	if (change != NULL) {
+		/* it shouldn't be marked as unsubscribed, but drop it to
+		   be sure */
+		change->unsubscribed = FALSE;
+		rec_r->last_change = change->last_subs_change;
+	}
+	rec_r->ns_prefix = info->ns->prefix;
+	rec_r->vname = info->name;
+	rec_r->storage_name = storage_name;
+	return 1;
+}
+
+static int
+local_worker_subs_iter_next_un(struct dsync_worker_subs_iter *_iter,
+			       struct dsync_worker_unsubscription *rec_r)
+{
+	struct local_dsync_worker_subs_iter *iter =
+		(struct local_dsync_worker_subs_iter *)_iter;
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_iter->worker;
+	void *key, *value;
+
+	if (iter->deleted_iter == NULL) {
+		iter->deleted_iter =
+			hash_table_iterate_init(worker->dir_changes_hash);
+	}
+	while (hash_table_iterate(iter->deleted_iter, &key, &value)) {
+		const struct local_dsync_dir_change *change = value;
+
+		if (change->unsubscribed) {
+			/* the name doesn't matter */
+			struct mail_namespace *ns =
+				mailbox_list_get_namespace(change->list);
+			memset(rec_r, 0, sizeof(*rec_r));
+			rec_r->name_sha1 = change->name_sha1;
+			rec_r->ns_prefix = ns->prefix;
+			rec_r->last_change = change->last_subs_change;
+			return 1;
+		}
+	}
+	hash_table_iterate_deinit(&iter->deleted_iter);
+	return -1;
+}
+
+static int
+local_worker_subs_iter_deinit(struct dsync_worker_subs_iter *_iter)
+{
+	struct local_dsync_worker_subs_iter *iter =
+		(struct local_dsync_worker_subs_iter *)_iter;
+	int ret = _iter->failed ? -1 : 0;
+
+	if (mailbox_list_iter_deinit(&iter->list_iter) < 0)
+		ret = -1;
+	i_free(iter);
+	return ret;
+}
+
+static void
+local_worker_set_subscribed(struct dsync_worker *_worker,
+			    const char *name, time_t last_change, bool set)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mail_namespace *ns;
+	struct mailbox *box;
+
+	ns = mail_namespace_find(worker->user->namespaces, name);
+	if (ns == NULL) {
+		i_error("Can't find namespace for mailbox %s", name);
+		return;
+	}
+
+	box = mailbox_alloc(ns->list, name, 0);
+	ns = mailbox_get_namespace(box);
+
+	mailbox_list_set_changelog_timestamp(ns->list, last_change);
+	if (mailbox_set_subscribed(box, set) < 0) {
+		dsync_worker_set_failure(_worker);
+		i_error("Can't update subscription %s: %s", name,
+			mail_storage_get_last_error(mailbox_get_storage(box),
+						    NULL));
+	}
+	mailbox_list_set_changelog_timestamp(ns->list, (time_t)-1);
+	mailbox_free(&box);
+}
+
+static int local_mailbox_open(struct local_dsync_worker *worker,
+			      const mailbox_guid_t *guid,
+			      struct mailbox **box_r)
+{
+	struct local_dsync_mailbox *lbox;
+	struct mailbox *box;
+	struct mailbox_metadata metadata;
+
+	lbox = hash_table_lookup(worker->mailbox_hash, guid);
+	if (lbox == NULL) {
+		i_error("Trying to open a non-listed mailbox with guid=%s",
+			dsync_guid_to_str(guid));
+		return -1;
+	}
+	if (lbox->deleted) {
+		*box_r = NULL;
+		return 0;
+	}
+
+	box = mailbox_alloc(lbox->ns->list, lbox->name, 0);
+	if (mailbox_sync(box, 0) < 0 ||
+	    mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
+		i_error("Failed to sync mailbox %s: %s", lbox->name,
+			mailbox_get_last_error(box, NULL));
+		mailbox_free(&box);
+		return -1;
+	}
+
+	if (memcmp(metadata.guid, guid->guid, sizeof(guid->guid)) != 0) {
+		i_error("Mailbox %s changed its GUID (%s -> %s)",
+			lbox->name, dsync_guid_to_str(guid),
+			guid_128_to_string(metadata.guid));
+		mailbox_free(&box);
+		return -1;
+	}
+	*box_r = box;
+	return 1;
+}
+
+static int iter_local_mailbox_open(struct local_dsync_worker_msg_iter *iter)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)iter->iter.worker;
+	mailbox_guid_t *guid;
+	struct mailbox *box;
+	struct mail_search_args *search_args;
+	int ret;
+
+	for (;;) {
+		if (iter->mailbox_idx == iter->mailbox_count) {
+			/* no more mailboxes */
+			return -1;
+		}
+
+		guid = &iter->mailboxes[iter->mailbox_idx];
+		ret = local_mailbox_open(worker, guid, &box);
+		if (ret != 0)
+			break;
+		/* mailbox was deleted. try next one. */
+		iter->mailbox_idx++;
+	}
+	if (ret < 0) {
+		i_error("msg iteration failed: Couldn't open mailbox %s",
+			dsync_guid_to_str(guid));
+		iter->iter.failed = TRUE;
+		return -1;
+	}
+
+	search_args = mail_search_build_init();
+	mail_search_build_add_all(search_args);
+
+	iter->box = box;
+	iter->trans = mailbox_transaction_begin(box, 0);
+	iter->search_ctx =
+		mailbox_search_init(iter->trans, search_args, NULL,
+				    MAIL_FETCH_FLAGS | MAIL_FETCH_GUID, NULL);
+	return 0;
+}
+
+static void
+iter_local_mailbox_close(struct local_dsync_worker_msg_iter *iter)
+{
+	iter->expunges_set = FALSE;
+	if (mailbox_search_deinit(&iter->search_ctx) < 0) {
+		i_error("msg search failed: %s",
+			mailbox_get_last_error(iter->box, NULL));
+		iter->iter.failed = TRUE;
+	}
+	(void)mailbox_transaction_commit(&iter->trans);
+	mailbox_free(&iter->box);
+}
+
+static struct dsync_worker_msg_iter *
+local_worker_msg_iter_init(struct dsync_worker *worker,
+			   const mailbox_guid_t mailboxes[],
+			   unsigned int mailbox_count)
+{
+	struct local_dsync_worker_msg_iter *iter;
+	unsigned int i;
+
+	iter = i_new(struct local_dsync_worker_msg_iter, 1);
+	iter->iter.worker = worker;
+	iter->mailboxes = mailbox_count == 0 ? NULL :
+		i_new(mailbox_guid_t, mailbox_count);
+	iter->mailbox_count = mailbox_count;
+	for (i = 0; i < mailbox_count; i++) {
+		memcpy(iter->mailboxes[i].guid, &mailboxes[i],
+		       sizeof(iter->mailboxes[i].guid));
+	}
+	i_array_init(&iter->expunges, 32);
+	iter->tmp_guid_str = str_new(default_pool, GUID_128_SIZE * 2 + 1);
+	(void)iter_local_mailbox_open(iter);
+	return &iter->iter;
+}
+
+static int mailbox_expunge_rec_cmp(const struct mailbox_expunge_rec *e1,
+				   const struct mailbox_expunge_rec *e2)
+{
+	if (e1->uid < e2->uid)
+		return -1;
+	else if (e1->uid > e2->uid)
+		return 1;
+	else
+		return 0;
+}
+
+static bool
+iter_local_mailbox_next_expunge(struct local_dsync_worker_msg_iter *iter,
+				uint32_t prev_uid, struct dsync_message *msg_r)
+{
+	struct mailbox *box = iter->box;
+	struct mailbox_status status;
+	const uint8_t *guid_128;
+	const struct mailbox_expunge_rec *expunges;
+	unsigned int count;
+
+	if (iter->expunges_set) {
+		expunges = array_get(&iter->expunges, &count);
+		if (iter->expunge_idx == count)
+			return FALSE;
+
+		memset(msg_r, 0, sizeof(*msg_r));
+		str_truncate(iter->tmp_guid_str, 0);
+		guid_128 = expunges[iter->expunge_idx].guid_128;
+		if (!guid_128_is_empty(guid_128)) {
+			binary_to_hex_append(iter->tmp_guid_str, guid_128,
+					     GUID_128_SIZE);
+		}
+		msg_r->guid = str_c(iter->tmp_guid_str);
+		msg_r->uid = expunges[iter->expunge_idx].uid;
+		msg_r->flags = DSYNC_MAIL_FLAG_EXPUNGED;
+		iter->expunge_idx++;
+		return TRUE;
+	}
+
+	/* initialize list of expunged messages at the end of mailbox */
+	iter->expunge_idx = 0;
+	array_clear(&iter->expunges);
+	iter->expunges_set = TRUE;
+
+	mailbox_get_open_status(box, STATUS_UIDNEXT, &status);
+	if (prev_uid + 1 >= status.uidnext) {
+		/* no expunged messages at the end of mailbox */
+		return FALSE;
+	}
+
+	T_BEGIN {
+		ARRAY_TYPE(seq_range) uids_filter;
+
+		t_array_init(&uids_filter, 1);
+		seq_range_array_add_range(&uids_filter, prev_uid + 1,
+					  status.uidnext - 1);
+		(void)mailbox_get_expunges(box, 0, &uids_filter,
+					   &iter->expunges);
+		array_sort(&iter->expunges, mailbox_expunge_rec_cmp);
+	} T_END;
+	return iter_local_mailbox_next_expunge(iter, prev_uid, msg_r);
+}
+
+static int
+local_worker_msg_iter_next(struct dsync_worker_msg_iter *_iter,
+			   unsigned int *mailbox_idx_r,
+			   struct dsync_message *msg_r)
+{
+	struct local_dsync_worker_msg_iter *iter =
+		(struct local_dsync_worker_msg_iter *)_iter;
+	struct mail *mail;
+	const char *guid;
+
+	if (_iter->failed || iter->search_ctx == NULL)
+		return -1;
+
+	if (!mailbox_search_next(iter->search_ctx, &mail)) {
+		if (iter_local_mailbox_next_expunge(iter, iter->prev_uid,
+						    msg_r)) {
+			*mailbox_idx_r = iter->mailbox_idx;
+			return 1;
+		}
+		iter_local_mailbox_close(iter);
+		iter->mailbox_idx++;
+		if (iter_local_mailbox_open(iter) < 0)
+			return -1;
+		return local_worker_msg_iter_next(_iter, mailbox_idx_r, msg_r);
+	}
+	*mailbox_idx_r = iter->mailbox_idx;
+	iter->prev_uid = mail->uid;
+
+	if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0) {
+		if (!mail->expunged) {
+			i_error("msg guid lookup failed: %s",
+				mailbox_get_last_error(mail->box, NULL));
+			_iter->failed = TRUE;
+			return -1;
+		}
+		return local_worker_msg_iter_next(_iter, mailbox_idx_r, msg_r);
+	}
+
+	memset(msg_r, 0, sizeof(*msg_r));
+	msg_r->guid = guid;
+	msg_r->uid = mail->uid;
+	msg_r->flags = mail_get_flags(mail);
+	msg_r->keywords = mail_get_keywords(mail);
+	msg_r->modseq = mail_get_modseq(mail);
+	if (mail_get_save_date(mail, &msg_r->save_date) < 0)
+		msg_r->save_date = (time_t)-1;
+	return 1;
+}
+
+static int
+local_worker_msg_iter_deinit(struct dsync_worker_msg_iter *_iter)
+{
+	struct local_dsync_worker_msg_iter *iter =
+		(struct local_dsync_worker_msg_iter *)_iter;
+	int ret = _iter->failed ? -1 : 0;
+
+	if (iter->box != NULL)
+		iter_local_mailbox_close(iter);
+	array_free(&iter->expunges);
+	str_free(&iter->tmp_guid_str);
+	i_free(iter->mailboxes);
+	i_free(iter);
+	return ret;
+}
+
+static void
+local_worker_copy_mailbox_update(const struct dsync_mailbox *dsync_box,
+				 struct mailbox_update *update_r)
+{
+	memset(update_r, 0, sizeof(*update_r));
+	memcpy(update_r->mailbox_guid, dsync_box->mailbox_guid.guid,
+	       sizeof(update_r->mailbox_guid));
+	update_r->uid_validity = dsync_box->uid_validity;
+	update_r->min_next_uid = dsync_box->uid_next;
+	update_r->min_first_recent_uid = dsync_box->first_recent_uid;
+	update_r->min_highest_modseq = dsync_box->highest_modseq;
+}
+
+static const char *
+mailbox_name_convert(struct local_dsync_worker *worker,
+		     const char *name, char src_sep, char dest_sep)
+{
+	char *dest_name, *p;
+
+	dest_name = t_strdup_noconst(name);
+	for (p = dest_name; *p != '\0'; p++) {
+		if (*p == dest_sep && worker->alt_char != '\0')
+			*p = worker->alt_char;
+		else if (*p == src_sep)
+			*p = dest_sep;
+	}
+	return dest_name;
+}
+
+static const char *
+mailbox_name_cleanup(const char *input, char real_sep, char alt_char)
+{
+	char *output, *p;
+
+	output = t_strdup_noconst(input);
+	for (p = output; *p != '\0'; p++) {
+		if (*p == real_sep || (uint8_t)*input < 32 ||
+		    (uint8_t)*input >= 0x80)
+			*p = alt_char;
+	}
+	return output;
+}
+
+static const char *mailbox_name_force_cleanup(const char *input, char alt_char)
+{
+	char *output, *p;
+
+	output = t_strdup_noconst(input);
+	for (p = output; *p != '\0'; p++) {
+		if (!i_isalnum(*p))
+			*p = alt_char;
+	}
+	return output;
+}
+
+static const char *
+local_worker_convert_mailbox_name(struct local_dsync_worker *worker,
+				  const char *name, struct mail_namespace *ns,
+				  const struct dsync_mailbox *dsync_box,
+				  bool creating)
+{
+	char list_sep, ns_sep = mail_namespace_get_sep(ns);
+
+	if (dsync_box->name_sep != ns_sep) {
+		/* mailbox names use different separators. convert them. */
+		name = mailbox_name_convert(worker, name,
+					    dsync_box->name_sep, ns_sep);
+	}
+	if (creating) {
+		list_sep = mailbox_list_get_hierarchy_sep(ns->list);
+		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
+			/* change any real separators to alt separators,
+			   drop any potentially invalid characters */
+			name = mailbox_name_cleanup(name, list_sep,
+						    worker->alt_char);
+		}
+		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
+			/* still not working, apparently it's not valid mUTF-7.
+			   just drop all non-alphanumeric characters. */
+			name = mailbox_name_force_cleanup(name,
+							  worker->alt_char);
+		}
+		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
+			/* probably some reserved name (e.g. dbox-Mails) */
+			name = t_strconcat("_", name, NULL);
+		}
+		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
+			/* name is too long? just give up and generate a
+			   unique name */
+			guid_128_t guid;
+
+			guid_128_generate(guid);
+			name = guid_128_to_string(guid);
+		}
+		i_assert(mailbox_list_is_valid_create_name(ns->list, name));
+	}
+	return name;
+}
+
+static struct mailbox *
+local_worker_mailbox_alloc(struct local_dsync_worker *worker,
+			   const struct dsync_mailbox *dsync_box, bool creating)
+{
+	struct mail_namespace *ns;
+	struct local_dsync_mailbox *lbox;
+	const char *name;
+
+	lbox = dsync_mailbox_is_noselect(dsync_box) ? NULL :
+		hash_table_lookup(worker->mailbox_hash,
+				  &dsync_box->mailbox_guid);
+	if (lbox != NULL) {
+		/* use the existing known mailbox name */
+		return mailbox_alloc(lbox->ns->list, lbox->name, 0);
+	}
+
+	ns = mail_namespace_find(worker->user->namespaces, dsync_box->name);
+	if (ns == NULL) {
+		i_error("Can't find namespace for mailbox %s", dsync_box->name);
+		return NULL;
+	}
+
+	name = local_worker_convert_mailbox_name(worker, dsync_box->name, ns,
+						 dsync_box, creating);
+	if (!dsync_mailbox_is_noselect(dsync_box)) {
+		local_dsync_worker_add_mailbox(worker, ns, name,
+					       &dsync_box->mailbox_guid);
+	}
+	return mailbox_alloc(ns->list, name, 0);
+}
+
+static int local_worker_create_dir(struct mailbox *box,
+				   const struct dsync_mailbox *dsync_box)
+{
+	struct mailbox_list *list = mailbox_get_namespace(box)->list;
+	const char *errstr;
+	enum mail_error error;
+
+	if (mailbox_list_create_dir(list, mailbox_get_name(box)) == 0)
+		return 0;
+
+	errstr = mailbox_list_get_last_error(list, &error);
+	switch (error) {
+	case MAIL_ERROR_EXISTS:
+		/* directory already exists - that's ok */
+		return 0;
+	case MAIL_ERROR_NOTPOSSIBLE:
+		/* \noselect mailboxes not supported - just ignore them
+		   (we don't want to create a selectable mailbox if the other
+		   side of the sync doesn't support dual-use mailboxes,
+		   e.g. mbox) */
+		return 0;
+	default:
+		i_error("Can't create mailbox %s: %s", dsync_box->name, errstr);
+		return -1;
+	}
+}
+
+static int
+local_worker_create_allocated_mailbox(struct local_dsync_worker *worker,
+				      struct mailbox *box,
+				      const struct dsync_mailbox *dsync_box)
+{
+	struct mailbox_update update;
+	const char *errstr;
+	enum mail_error error;
+
+	local_worker_copy_mailbox_update(dsync_box, &update);
+
+	if (dsync_mailbox_is_noselect(dsync_box)) {
+		if (local_worker_create_dir(box, dsync_box) < 0) {
+			dsync_worker_set_failure(&worker->worker);
+			return -1;
+		}
+		return 1;
+	}
+
+	if (mailbox_create(box, &update, FALSE) < 0) {
+		errstr = mailbox_get_last_error(box, &error);
+		if (error == MAIL_ERROR_EXISTS) {
+			/* mailbox already exists */
+			return 0;
+		}
+
+		dsync_worker_set_failure(&worker->worker);
+		i_error("Can't create mailbox %s: %s", dsync_box->name, errstr);
+		return -1;
+	}
+
+	local_dsync_worker_add_mailbox(worker,
+				       mailbox_get_namespace(box),
+				       mailbox_get_name(box),
+				       &dsync_box->mailbox_guid);
+	return 1;
+}
+
+static void
+local_worker_create_mailbox(struct dsync_worker *_worker,
+			    const struct dsync_mailbox *dsync_box)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mailbox *box;
+	struct mail_namespace *ns;
+	const char *new_name;
+	int ret;
+
+	box = local_worker_mailbox_alloc(worker, dsync_box, TRUE);
+	if (box == NULL) {
+		dsync_worker_set_failure(_worker);
+		return;
+	}
+
+	ret = local_worker_create_allocated_mailbox(worker, box, dsync_box);
+	if (ret != 0) {
+		mailbox_free(&box);
+		return;
+	}
+
+	/* mailbox name already exists. add mailbox guid to the name,
+	   that shouldn't exist. */
+	new_name = t_strconcat(mailbox_get_name(box), "_",
+			       dsync_guid_to_str(&dsync_box->mailbox_guid),
+			       NULL);
+	ns = mailbox_get_namespace(box);
+	mailbox_free(&box);
+
+	local_dsync_worker_add_mailbox(worker, ns, new_name,
+				       &dsync_box->mailbox_guid);
+	box = mailbox_alloc(ns->list, new_name, 0);
+	(void)local_worker_create_allocated_mailbox(worker, box, dsync_box);
+	mailbox_free(&box);
+}
+
+static void
+local_worker_delete_mailbox(struct dsync_worker *_worker,
+			    const struct dsync_mailbox *dsync_box)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct local_dsync_mailbox *lbox;
+	const mailbox_guid_t *mailbox = &dsync_box->mailbox_guid;
+	struct mailbox *box;
+
+	lbox = hash_table_lookup(worker->mailbox_hash, mailbox);
+	if (lbox == NULL) {
+		i_error("Trying to delete a non-listed mailbox with guid=%s",
+			dsync_guid_to_str(mailbox));
+		dsync_worker_set_failure(_worker);
+		return;
+	}
+
+	mailbox_list_set_changelog_timestamp(lbox->ns->list,
+					     dsync_box->last_change);
+	box = mailbox_alloc(lbox->ns->list, lbox->name, 0);
+	if (mailbox_delete(box) < 0) {
+		i_error("Can't delete mailbox %s: %s", lbox->name,
+			mailbox_get_last_error(box, NULL));
+		dsync_worker_set_failure(_worker);
+	} else {
+		lbox->deleted = TRUE;
+	}
+	mailbox_free(&box);
+	mailbox_list_set_changelog_timestamp(lbox->ns->list, (time_t)-1);
+}
+
+static void
+local_worker_delete_dir(struct dsync_worker *_worker,
+			const struct dsync_mailbox *dsync_box)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mail_namespace *ns;
+	const char *storage_name;
+
+	ns = mail_namespace_find(worker->user->namespaces, dsync_box->name);
+	storage_name = mailbox_list_get_storage_name(ns->list, dsync_box->name);
+
+	mailbox_list_set_changelog_timestamp(ns->list, dsync_box->last_change);
+	if (mailbox_list_delete_dir(ns->list, storage_name) < 0) {
+		i_error("Can't delete mailbox directory %s: %s",
+			dsync_box->name,
+			mailbox_list_get_last_error(ns->list, NULL));
+	}
+	mailbox_list_set_changelog_timestamp(ns->list, (time_t)-1);
+}
+
+static void
+local_worker_rename_mailbox(struct dsync_worker *_worker,
+			    const mailbox_guid_t *mailbox,
+			    const struct dsync_mailbox *dsync_box)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct local_dsync_mailbox *lbox;
+	struct mailbox_list *list;
+	struct mailbox *old_box, *new_box;
+	const char *newname;
+
+	lbox = hash_table_lookup(worker->mailbox_hash, mailbox);
+	if (lbox == NULL) {
+		i_error("Trying to rename a non-listed mailbox with guid=%s",
+			dsync_guid_to_str(mailbox));
+		dsync_worker_set_failure(_worker);
+		return;
+	}
+
+	list = lbox->ns->list;
+	newname = local_worker_convert_mailbox_name(worker, dsync_box->name,
+						    lbox->ns, dsync_box, TRUE);
+	if (strcmp(lbox->name, newname) == 0) {
+		/* nothing changed after all. probably because some characters
+		   in mailbox name weren't valid. */
+		return;
+	}
+
+	mailbox_list_set_changelog_timestamp(list, dsync_box->last_change);
+	old_box = mailbox_alloc(list, lbox->name, 0);
+	new_box = mailbox_alloc(list, newname, 0);
+	if (mailbox_rename(old_box, new_box, FALSE) < 0) {
+		i_error("Can't rename mailbox %s to %s: %s", lbox->name,
+			newname, mailbox_get_last_error(old_box, NULL));
+		dsync_worker_set_failure(_worker);
+	} else {
+		lbox->name = p_strdup(worker->pool, newname);
+	}
+	mailbox_free(&old_box);
+	mailbox_free(&new_box);
+	mailbox_list_set_changelog_timestamp(list, (time_t)-1);
+}
+
+static bool
+has_expected_save_uids(struct local_dsync_worker *worker,
+		       const struct mail_transaction_commit_changes *changes)
+{
+	struct seq_range_iter iter;
+	const uint32_t *expected_uids;
+	uint32_t uid;
+	unsigned int i, n, expected_count;
+
+	expected_uids = array_get(&worker->saved_uids, &expected_count);
+	seq_range_array_iter_init(&iter, &changes->saved_uids); i = n = 0;
+	while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+		if (i == expected_count || uid != expected_uids[i++])
+			return FALSE;
+	}
+	return i == expected_count;
+}
+
+static void local_worker_mailbox_close(struct local_dsync_worker *worker)
+{
+	struct mailbox_transaction_context *trans, *ext_trans;
+	struct mail_transaction_commit_changes changes;
+
+	i_assert(worker->save_input == NULL);
+
+	memset(&worker->selected_box_guid, 0,
+	       sizeof(worker->selected_box_guid));
+
+	if (worker->selected_box == NULL)
+		return;
+
+	trans = worker->mail->transaction;
+	ext_trans = worker->ext_mail->transaction;
+	mail_free(&worker->mail);
+	mail_free(&worker->ext_mail);
+
+	/* all saves and copies go to ext_trans */
+	if (mailbox_transaction_commit_get_changes(&ext_trans, &changes) < 0)
+		dsync_worker_set_failure(&worker->worker);
+	else {
+		if (changes.ignored_modseq_changes != 0) {
+			if (worker->worker.verbose) {
+				i_info("%s: Ignored %u modseq changes",
+				       mailbox_get_vname(worker->selected_box),
+				       changes.ignored_modseq_changes);
+			}
+			worker->worker.unexpected_changes = TRUE;
+		}
+		if (!has_expected_save_uids(worker, &changes)) {
+			if (worker->worker.verbose) {
+				i_info("%s: Couldn't keep all uids",
+				       mailbox_get_vname(worker->selected_box));
+			}
+			worker->worker.unexpected_changes = TRUE;
+		}
+		pool_unref(&changes.pool);
+	}
+	array_clear(&worker->saved_uids);
+
+	if (mailbox_transaction_commit(&trans) < 0 ||
+	    mailbox_sync(worker->selected_box, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0)
+		dsync_worker_set_failure(&worker->worker);
+
+	mailbox_free(&worker->selected_box);
+}
+
+static void
+local_worker_update_mailbox(struct dsync_worker *_worker,
+			    const struct dsync_mailbox *dsync_box)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mailbox *box;
+	struct mailbox_update update;
+	bool selected = FALSE;
+
+	/* if we're updating a selected mailbox, close it first so that all
+	   pending changes get committed. */
+	selected = worker->selected_box != NULL &&
+		dsync_guid_equals(&dsync_box->mailbox_guid,
+				  &worker->selected_box_guid);
+	if (selected)
+		local_worker_mailbox_close(worker);
+
+	box = local_worker_mailbox_alloc(worker, dsync_box, FALSE);
+	if (box == NULL) {
+		dsync_worker_set_failure(_worker);
+		return;
+	}
+
+	local_worker_copy_mailbox_update(dsync_box, &update);
+	if (mailbox_update(box, &update) < 0) {
+		dsync_worker_set_failure(_worker);
+		i_error("Can't update mailbox %s: %s", dsync_box->name,
+			mailbox_get_last_error(box, NULL));
+	}
+	mailbox_free(&box);
+
+	if (selected)
+		dsync_worker_select_mailbox(_worker, dsync_box);
+}
+
+static void
+local_worker_set_cache_fields(struct local_dsync_worker *worker,
+			      const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
+{
+	struct mailbox_update update;
+	const struct mailbox_cache_field *fields;
+	struct mailbox_cache_field *new_fields;
+	unsigned int count;
+
+	fields = array_get(cache_fields, &count);
+	new_fields = t_new(struct mailbox_cache_field, count + 1);
+	memcpy(new_fields, fields, sizeof(struct mailbox_cache_field) * count);
+
+	memset(&update, 0, sizeof(update));
+	update.cache_updates = new_fields;
+	mailbox_update(worker->selected_box, &update);
+}
+
+static void
+local_worker_select_mailbox(struct dsync_worker *_worker,
+			    const mailbox_guid_t *mailbox,
+			    const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mailbox_transaction_context *trans, *ext_trans;
+
+	if (dsync_guid_equals(&worker->selected_box_guid, mailbox)) {
+		/* already selected or previous select failed */
+		i_assert(worker->selected_box != NULL || _worker->failed);
+		return;
+	}
+	if (worker->selected_box != NULL)
+		local_worker_mailbox_close(worker);
+	worker->selected_box_guid = *mailbox;
+
+	if (local_mailbox_open(worker, mailbox, &worker->selected_box) <= 0) {
+		dsync_worker_set_failure(_worker);
+		return;
+	}
+	if (cache_fields != NULL && array_is_created(cache_fields))
+		local_worker_set_cache_fields(worker, cache_fields);
+
+	ext_trans = mailbox_transaction_begin(worker->selected_box,
+					MAILBOX_TRANSACTION_FLAG_EXTERNAL |
+					MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
+	trans = mailbox_transaction_begin(worker->selected_box, 0);
+	worker->mail = mail_alloc(trans, 0, NULL);
+	worker->ext_mail = mail_alloc(ext_trans, 0, NULL);
+}
+
+static void
+local_worker_msg_update_metadata(struct dsync_worker *_worker,
+				 const struct dsync_message *msg)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mail_keywords *keywords;
+
+	if (msg->modseq > 1) {
+		(void)mailbox_enable(worker->mail->box,
+				     MAILBOX_FEATURE_CONDSTORE);
+	}
+
+	if (!mail_set_uid(worker->mail, msg->uid))
+		dsync_worker_set_failure(_worker);
+	else {
+		mail_update_flags(worker->mail, MODIFY_REPLACE, msg->flags);
+
+		keywords = mailbox_keywords_create_valid(worker->mail->box,
+							 msg->keywords);
+		mail_update_keywords(worker->mail, MODIFY_REPLACE, keywords);
+		mailbox_keywords_unref(&keywords);
+		mail_update_modseq(worker->mail, msg->modseq);
+	}
+}
+
+static void
+local_worker_msg_update_uid(struct dsync_worker *_worker,
+			    uint32_t old_uid, uint32_t new_uid)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mail_save_context *save_ctx;
+
+	if (!mail_set_uid(worker->ext_mail, old_uid)) {
+		dsync_worker_set_failure(_worker);
+		return;
+	}
+
+	save_ctx = mailbox_save_alloc(worker->ext_mail->transaction);
+	mailbox_save_copy_flags(save_ctx, worker->ext_mail);
+	mailbox_save_set_uid(save_ctx, new_uid);
+	if (mailbox_copy(&save_ctx, worker->ext_mail) == 0)
+		mail_expunge(worker->ext_mail);
+}
+
+static void local_worker_msg_expunge(struct dsync_worker *_worker, uint32_t uid)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+
+	if (mail_set_uid(worker->mail, uid))
+		mail_expunge(worker->mail);
+}
+
+static void
+local_worker_msg_save_set_metadata(struct local_dsync_worker *worker,
+				   struct mailbox *box,
+				   struct mail_save_context *save_ctx,
+				   const struct dsync_message *msg)
+{
+	struct mail_keywords *keywords;
+
+	i_assert(msg->uid != 0);
+
+	if (msg->modseq > 1)
+		(void)mailbox_enable(box, MAILBOX_FEATURE_CONDSTORE);
+
+	keywords = str_array_length(msg->keywords) == 0 ? NULL :
+		mailbox_keywords_create_valid(box, msg->keywords);
+	mailbox_save_set_flags(save_ctx, msg->flags, keywords);
+	if (keywords != NULL)
+		mailbox_keywords_unref(&keywords);
+	mailbox_save_set_uid(save_ctx, msg->uid);
+	mailbox_save_set_save_date(save_ctx, msg->save_date);
+	mailbox_save_set_min_modseq(save_ctx, msg->modseq);
+
+	array_append(&worker->saved_uids, &msg->uid, 1);
+}
+
+static void
+local_worker_msg_copy(struct dsync_worker *_worker,
+		      const mailbox_guid_t *src_mailbox, uint32_t src_uid,
+		      const struct dsync_message *dest_msg,
+		      dsync_worker_copy_callback_t *callback, void *context)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mailbox *src_box;
+	struct mailbox_transaction_context *src_trans;
+	struct mail *src_mail;
+	struct mail_save_context *save_ctx;
+	int ret;
+
+	if (local_mailbox_open(worker, src_mailbox, &src_box) <= 0) {
+		callback(FALSE, context);
+		return;
+	}
+
+	src_trans = mailbox_transaction_begin(src_box, 0);
+	src_mail = mail_alloc(src_trans, 0, NULL);
+	if (!mail_set_uid(src_mail, src_uid))
+		ret = -1;
+	else {
+		save_ctx = mailbox_save_alloc(worker->ext_mail->transaction);
+		local_worker_msg_save_set_metadata(worker, worker->mail->box,
+						   save_ctx, dest_msg);
+		ret = mailbox_copy(&save_ctx, src_mail);
+	}
+
+	mail_free(&src_mail);
+	(void)mailbox_transaction_commit(&src_trans);
+	mailbox_free(&src_box);
+
+	callback(ret == 0, context);
+}
+
+static void dsync_worker_try_finish(struct local_dsync_worker *worker)
+{
+	if (worker->finish_callback == NULL)
+		return;
+	if (worker->save_io != NULL || worker->reading_mail)
+		return;
+
+	i_assert(worker->finishing);
+	i_assert(!worker->finished);
+	worker->finishing = FALSE;
+	worker->finished = TRUE;
+	worker->finish_callback(!worker->worker.failed, worker->finish_context);
+}
+
+static void
+local_worker_save_msg_continue(struct local_dsync_worker *worker)
+{
+	struct mailbox *dest_box = worker->ext_mail->box;
+	dsync_worker_save_callback_t *callback;
+	ssize_t ret;
+
+	while ((ret = i_stream_read(worker->save_input)) > 0) {
+		if (mailbox_save_continue(worker->save_ctx) < 0)
+			break;
+	}
+	if (ret == 0) {
+		if (worker->save_io != NULL)
+			return;
+		worker->save_io =
+			io_add(i_stream_get_fd(worker->save_input), IO_READ,
+			       local_worker_save_msg_continue, worker);
+		return;
+	}
+	i_assert(ret == -1);
+
+	/* drop save_io before destroying save_input, so that save_input's
+	   destroy callback can add io back to its fd. */
+	if (worker->save_io != NULL)
+		io_remove(&worker->save_io);
+	if (worker->save_input->stream_errno != 0) {
+		errno = worker->save_input->stream_errno;
+		i_error("read(msg input) failed: %m");
+		mailbox_save_cancel(&worker->save_ctx);
+		dsync_worker_set_failure(&worker->worker);
+	} else {
+		i_assert(worker->save_input->eof);
+		if (mailbox_save_finish(&worker->save_ctx) < 0) {
+			i_error("Can't save message to mailbox %s: %s",
+				mailbox_get_vname(dest_box),
+				mailbox_get_last_error(dest_box, NULL));
+			dsync_worker_set_failure(&worker->worker);
+		}
+	}
+	callback = worker->save_callback;
+	worker->save_callback = NULL;
+	i_stream_unref(&worker->save_input);
+
+	dsync_worker_try_finish(worker);
+	/* call the callback last, since it could call worker code again and
+	   cause problems (e.g. if _try_finish() is called again, it could
+	   cause a duplicate finish_callback()) */
+	callback(worker->save_context);
+}
+
+static void
+local_worker_msg_save(struct dsync_worker *_worker,
+		      const struct dsync_message *msg,
+		      const struct dsync_msg_static_data *data,
+		      dsync_worker_save_callback_t *callback,
+		      void *context)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct mailbox *dest_box = worker->ext_mail->box;
+	struct mail_save_context *save_ctx;
+
+	i_assert(worker->save_input == NULL);
+
+	save_ctx = mailbox_save_alloc(worker->ext_mail->transaction);
+	if (*msg->guid != '\0')
+		mailbox_save_set_guid(save_ctx, msg->guid);
+	local_worker_msg_save_set_metadata(worker, worker->mail->box,
+					   save_ctx, msg);
+	if (*data->pop3_uidl != '\0')
+		mailbox_save_set_pop3_uidl(save_ctx, data->pop3_uidl);
+
+	mailbox_save_set_received_date(save_ctx, data->received_date, 0);
+
+	if (mailbox_save_begin(&save_ctx, data->input) < 0) {
+		i_error("Can't save message to mailbox %s: %s",
+			mailbox_get_vname(dest_box),
+			mailbox_get_last_error(dest_box, NULL));
+		dsync_worker_set_failure(_worker);
+		callback(context);
+		return;
+	}
+
+	worker->save_callback = callback;
+	worker->save_context = context;
+	worker->save_input = data->input;
+	worker->save_ctx = save_ctx;
+	i_stream_ref(worker->save_input);
+	local_worker_save_msg_continue(worker);
+}
+
+static void local_worker_msg_save_cancel(struct dsync_worker *_worker)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+
+	if (worker->save_input == NULL)
+		return;
+
+	if (worker->save_io != NULL)
+		io_remove(&worker->save_io);
+	mailbox_save_cancel(&worker->save_ctx);
+	i_stream_unref(&worker->save_input);
+}
+
+static void local_worker_msg_get_done(struct local_dsync_worker *worker)
+{
+	const struct local_dsync_worker_msg_get *gets;
+	struct local_dsync_worker_msg_get get;
+	unsigned int count;
+
+	worker->reading_mail = FALSE;
+
+	gets = array_get(&worker->msg_get_queue, &count);
+	if (count == 0)
+		dsync_worker_try_finish(worker);
+	else {
+		get = gets[0];
+		array_delete(&worker->msg_get_queue, 0, 1);
+		local_worker_msg_get_next(worker, &get);
+	}
+}
+
+static void local_worker_msg_box_close(struct local_dsync_worker *worker)
+{
+	struct mailbox_transaction_context *trans;
+	struct mailbox *box;
+
+	if (worker->get_mail == NULL)
+		return;
+
+	box = worker->get_mail->box;
+	trans = worker->get_mail->transaction;
+
+	mail_free(&worker->get_mail);
+	(void)mailbox_transaction_commit(&trans);
+	mailbox_free(&box);
+	memset(&worker->get_mailbox, 0, sizeof(worker->get_mailbox));
+}
+
+static void
+local_worker_msg_get_next(struct local_dsync_worker *worker,
+			  const struct local_dsync_worker_msg_get *get)
+{
+	struct dsync_msg_static_data data;
+	struct mailbox_transaction_context *trans;
+	struct mailbox *box;
+
+	i_assert(!worker->reading_mail);
+
+	if (!dsync_guid_equals(&worker->get_mailbox, &get->mailbox)) {
+		local_worker_msg_box_close(worker);
+		if (local_mailbox_open(worker, &get->mailbox, &box) <= 0) {
+			get->callback(DSYNC_MSG_GET_RESULT_FAILED,
+				      NULL, get->context);
+			return;
+		}
+		worker->get_mailbox = get->mailbox;
+
+		trans = mailbox_transaction_begin(box, 0);
+		worker->get_mail = mail_alloc(trans, MAIL_FETCH_UIDL_BACKEND |
+					      MAIL_FETCH_RECEIVED_DATE |
+					      MAIL_FETCH_STREAM_HEADER |
+					      MAIL_FETCH_STREAM_BODY, NULL);
+	}
+
+	if (!mail_set_uid(worker->get_mail, get->uid)) {
+		get->callback(DSYNC_MSG_GET_RESULT_EXPUNGED,
+			      NULL, get->context);
+		return;
+	}
+
+	memset(&data, 0, sizeof(data));
+	if (mail_get_special(worker->get_mail, MAIL_FETCH_UIDL_BACKEND,
+			     &data.pop3_uidl) < 0)
+		data.pop3_uidl = "";
+	else
+		data.pop3_uidl = t_strdup(data.pop3_uidl);
+	if (mail_get_received_date(worker->get_mail, &data.received_date) < 0 ||
+	    mail_get_stream(worker->get_mail, NULL, NULL, &data.input) < 0) {
+		get->callback(worker->get_mail->expunged ?
+			      DSYNC_MSG_GET_RESULT_EXPUNGED :
+			      DSYNC_MSG_GET_RESULT_FAILED, NULL, get->context);
+	} else {
+		worker->reading_mail = TRUE;
+		data.input = i_stream_create_limit(data.input, (uoff_t)-1);
+		i_stream_set_destroy_callback(data.input,
+					      local_worker_msg_get_done,
+					      worker);
+		get->callback(DSYNC_MSG_GET_RESULT_SUCCESS,
+			      &data, get->context);
+	}
+}
+
+static void
+local_worker_msg_get(struct dsync_worker *_worker,
+		     const mailbox_guid_t *mailbox, uint32_t uid,
+		     dsync_worker_msg_callback_t *callback, void *context)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+	struct local_dsync_worker_msg_get get;
+
+	memset(&get, 0, sizeof(get));
+	get.mailbox = *mailbox;
+	get.uid = uid;
+	get.callback = callback;
+	get.context = context;
+
+	if (!worker->reading_mail)
+		local_worker_msg_get_next(worker, &get);
+	else
+		array_append(&worker->msg_get_queue, &get, 1);
+}
+
+static void
+local_worker_finish(struct dsync_worker *_worker,
+		    dsync_worker_finish_callback_t *callback, void *context)
+{
+	struct local_dsync_worker *worker =
+		(struct local_dsync_worker *)_worker;
+
+	i_assert(!worker->finishing);
+
+	worker->finishing = TRUE;
+	worker->finished = FALSE;
+	worker->finish_callback = callback;
+	worker->finish_context = context;
+
+	dsync_worker_try_finish(worker);
+}
+
+struct dsync_worker_vfuncs local_dsync_worker = {
+	local_worker_deinit,
+
+	local_worker_is_output_full,
+	local_worker_output_flush,
+
+	local_worker_mailbox_iter_init,
+	local_worker_mailbox_iter_next,
+	local_worker_mailbox_iter_deinit,
+
+	local_worker_subs_iter_init,
+	local_worker_subs_iter_next,
+	local_worker_subs_iter_next_un,
+	local_worker_subs_iter_deinit,
+	local_worker_set_subscribed,
+
+	local_worker_msg_iter_init,
+	local_worker_msg_iter_next,
+	local_worker_msg_iter_deinit,
+
+	local_worker_create_mailbox,
+	local_worker_delete_mailbox,
+	local_worker_delete_dir,
+	local_worker_rename_mailbox,
+	local_worker_update_mailbox,
+
+	local_worker_select_mailbox,
+	local_worker_msg_update_metadata,
+	local_worker_msg_update_uid,
+	local_worker_msg_expunge,
+	local_worker_msg_copy,
+	local_worker_msg_save,
+	local_worker_msg_save_cancel,
+	local_worker_msg_get,
+	local_worker_finish
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-worker-private.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,105 @@
+#ifndef DSYNC_WORKER_PRIVATE_H
+#define DSYNC_WORKER_PRIVATE_H
+
+#include "dsync-worker.h"
+
+struct mail_user;
+
+struct dsync_worker_vfuncs {
+	void (*deinit)(struct dsync_worker *);
+
+	bool (*is_output_full)(struct dsync_worker *worker);
+	int (*output_flush)(struct dsync_worker *worker);
+
+	struct dsync_worker_mailbox_iter *
+		(*mailbox_iter_init)(struct dsync_worker *worker);
+	int (*mailbox_iter_next)(struct dsync_worker_mailbox_iter *iter,
+				 struct dsync_mailbox *dsync_box_r);
+	int (*mailbox_iter_deinit)(struct dsync_worker_mailbox_iter *iter);
+
+	struct dsync_worker_subs_iter *
+		(*subs_iter_init)(struct dsync_worker *worker);
+	int (*subs_iter_next)(struct dsync_worker_subs_iter *iter,
+			      struct dsync_worker_subscription *rec_r);
+	int (*subs_iter_next_un)(struct dsync_worker_subs_iter *iter,
+				 struct dsync_worker_unsubscription *rec_r);
+	int (*subs_iter_deinit)(struct dsync_worker_subs_iter *iter);
+	void (*set_subscribed)(struct dsync_worker *worker,
+			       const char *name, time_t last_change, bool set);
+
+	struct dsync_worker_msg_iter *
+		(*msg_iter_init)(struct dsync_worker *worker,
+				 const mailbox_guid_t mailboxes[],
+				 unsigned int mailbox_count);
+	int (*msg_iter_next)(struct dsync_worker_msg_iter *iter,
+			     unsigned int *mailbox_idx_r,
+			     struct dsync_message *msg_r);
+	int (*msg_iter_deinit)(struct dsync_worker_msg_iter *iter);
+
+	void (*create_mailbox)(struct dsync_worker *worker,
+			       const struct dsync_mailbox *dsync_box);
+	void (*delete_mailbox)(struct dsync_worker *worker,
+			       const struct dsync_mailbox *dsync_box);
+	void (*delete_dir)(struct dsync_worker *worker,
+			   const struct dsync_mailbox *dsync_box);
+	void (*rename_mailbox)(struct dsync_worker *worker,
+			       const mailbox_guid_t *mailbox,
+			       const struct dsync_mailbox *dsync_box);
+	void (*update_mailbox)(struct dsync_worker *worker,
+			       const struct dsync_mailbox *dsync_box);
+
+	void (*select_mailbox)(struct dsync_worker *worker,
+			       const mailbox_guid_t *mailbox,
+			       const ARRAY_TYPE(mailbox_cache_field) *cache_fields);
+	void (*msg_update_metadata)(struct dsync_worker *worker,
+				    const struct dsync_message *msg);
+	void (*msg_update_uid)(struct dsync_worker *worker,
+			       uint32_t old_uid, uint32_t new_uid);
+	void (*msg_expunge)(struct dsync_worker *worker, uint32_t uid);
+	void (*msg_copy)(struct dsync_worker *worker,
+			 const mailbox_guid_t *src_mailbox, uint32_t src_uid,
+			 const struct dsync_message *dest_msg,
+			 dsync_worker_copy_callback_t *callback, void *context);
+	void (*msg_save)(struct dsync_worker *worker,
+			 const struct dsync_message *msg,
+			 const struct dsync_msg_static_data *data,
+			 dsync_worker_save_callback_t *callback,
+			 void *context);
+	void (*msg_save_cancel)(struct dsync_worker *worker);
+	void (*msg_get)(struct dsync_worker *worker,
+			const mailbox_guid_t *mailbox, uint32_t uid,
+			dsync_worker_msg_callback_t *callback, void *context);
+	void (*finish)(struct dsync_worker *worker,
+		       dsync_worker_finish_callback_t *callback, void *context);
+};
+
+struct dsync_worker {
+	struct dsync_worker_vfuncs v;
+
+	io_callback_t *input_callback, *output_callback;
+	void *input_context, *output_context;
+
+	unsigned int readonly:1;
+	unsigned int failed:1;
+	unsigned int verbose:1;
+	unsigned int unexpected_changes:1;
+};
+
+struct dsync_worker_mailbox_iter {
+	struct dsync_worker *worker;
+	bool failed;
+};
+
+struct dsync_worker_subs_iter {
+	struct dsync_worker *worker;
+	bool failed;
+};
+
+struct dsync_worker_msg_iter {
+	struct dsync_worker *worker;
+	bool failed;
+};
+
+void dsync_worker_set_failure(struct dsync_worker *worker);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-worker.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,285 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "dsync-worker-private.h"
+
+void dsync_worker_deinit(struct dsync_worker **_worker)
+{
+	struct dsync_worker *worker = *_worker;
+
+	*_worker = NULL;
+	worker->v.deinit(worker);
+}
+
+void dsync_worker_set_readonly(struct dsync_worker *worker)
+{
+	worker->readonly = TRUE;
+}
+
+void dsync_worker_set_verbose(struct dsync_worker *worker)
+{
+	worker->verbose = TRUE;
+}
+
+void dsync_worker_set_input_callback(struct dsync_worker *worker,
+				     io_callback_t *callback, void *context)
+{
+	worker->input_callback = callback;
+	worker->input_context = context;
+}
+
+bool dsync_worker_is_output_full(struct dsync_worker *worker)
+{
+	return worker->v.is_output_full(worker);
+}
+
+void dsync_worker_set_output_callback(struct dsync_worker *worker,
+				      io_callback_t *callback, void *context)
+{
+	worker->output_callback = callback;
+	worker->output_context = context;
+}
+
+int dsync_worker_output_flush(struct dsync_worker *worker)
+{
+	return worker->v.output_flush(worker);
+}
+
+struct dsync_worker_mailbox_iter *
+dsync_worker_mailbox_iter_init(struct dsync_worker *worker)
+{
+	return worker->v.mailbox_iter_init(worker);
+}
+
+int dsync_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *iter,
+				   struct dsync_mailbox *dsync_box_r)
+{
+	int ret;
+
+	T_BEGIN {
+		ret = iter->worker->v.mailbox_iter_next(iter, dsync_box_r);
+	} T_END;
+	return ret;
+}
+
+int dsync_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter **_iter)
+{
+	struct dsync_worker_mailbox_iter *iter = *_iter;
+
+	*_iter = NULL;
+	return iter->worker->v.mailbox_iter_deinit(iter);
+}
+
+struct dsync_worker_subs_iter *
+dsync_worker_subs_iter_init(struct dsync_worker *worker)
+{
+	return worker->v.subs_iter_init(worker);
+}
+
+int dsync_worker_subs_iter_next(struct dsync_worker_subs_iter *iter,
+				struct dsync_worker_subscription *rec_r)
+{
+	return iter->worker->v.subs_iter_next(iter, rec_r);
+}
+
+int dsync_worker_subs_iter_next_un(struct dsync_worker_subs_iter *iter,
+				   struct dsync_worker_unsubscription *rec_r)
+{
+	return iter->worker->v.subs_iter_next_un(iter, rec_r);
+}
+
+int dsync_worker_subs_iter_deinit(struct dsync_worker_subs_iter **_iter)
+{
+	struct dsync_worker_subs_iter *iter = *_iter;
+
+	*_iter = NULL;
+	return iter->worker->v.subs_iter_deinit(iter);
+}
+
+void dsync_worker_set_subscribed(struct dsync_worker *worker,
+				 const char *name, time_t last_change, bool set)
+{
+	worker->v.set_subscribed(worker, name, last_change, set);
+}
+
+struct dsync_worker_msg_iter *
+dsync_worker_msg_iter_init(struct dsync_worker *worker,
+			   const mailbox_guid_t mailboxes[],
+			   unsigned int mailbox_count)
+{
+	i_assert(mailbox_count > 0);
+	return worker->v.msg_iter_init(worker, mailboxes, mailbox_count);
+}
+
+int dsync_worker_msg_iter_next(struct dsync_worker_msg_iter *iter,
+			       unsigned int *mailbox_idx_r,
+			       struct dsync_message *msg_r)
+{
+	int ret;
+
+	T_BEGIN {
+		ret = iter->worker->v.msg_iter_next(iter, mailbox_idx_r, msg_r);
+	} T_END;
+	return ret;
+}
+
+int dsync_worker_msg_iter_deinit(struct dsync_worker_msg_iter **_iter)
+{
+	struct dsync_worker_msg_iter *iter = *_iter;
+
+	*_iter = NULL;
+	return iter->worker->v.msg_iter_deinit(iter);
+}
+
+void dsync_worker_create_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *dsync_box)
+{
+	i_assert(dsync_box->uid_validity != 0 ||
+		 dsync_mailbox_is_noselect(dsync_box));
+
+	if (!worker->readonly) T_BEGIN {
+		worker->v.create_mailbox(worker, dsync_box);
+	} T_END;
+}
+
+void dsync_worker_delete_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *dsync_box)
+{
+	if (!worker->readonly) T_BEGIN {
+		worker->v.delete_mailbox(worker, dsync_box);
+	} T_END;
+}
+
+void dsync_worker_delete_dir(struct dsync_worker *worker,
+			     const struct dsync_mailbox *dsync_box)
+{
+	if (!worker->readonly) T_BEGIN {
+		worker->v.delete_dir(worker, dsync_box);
+	} T_END;
+}
+
+void dsync_worker_rename_mailbox(struct dsync_worker *worker,
+				 const mailbox_guid_t *mailbox,
+				 const struct dsync_mailbox *dsync_box)
+{
+	if (!worker->readonly) T_BEGIN {
+		worker->v.rename_mailbox(worker, mailbox, dsync_box);
+	} T_END;
+}
+
+void dsync_worker_update_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *dsync_box)
+{
+	if (!worker->readonly) T_BEGIN {
+		worker->v.update_mailbox(worker, dsync_box);
+	} T_END;
+}
+
+void dsync_worker_select_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *box)
+{
+	T_BEGIN {
+		worker->v.select_mailbox(worker, &box->mailbox_guid,
+					 &box->cache_fields);
+	} T_END;
+}
+
+void dsync_worker_msg_update_metadata(struct dsync_worker *worker,
+				      const struct dsync_message *msg)
+{
+	if (!worker->failed && !worker->readonly)
+		worker->v.msg_update_metadata(worker, msg);
+}
+
+void dsync_worker_msg_update_uid(struct dsync_worker *worker,
+				 uint32_t old_uid, uint32_t new_uid)
+{
+	if (!worker->failed && !worker->readonly)
+		worker->v.msg_update_uid(worker, old_uid, new_uid);
+}
+
+void dsync_worker_msg_expunge(struct dsync_worker *worker, uint32_t uid)
+{
+	if (!worker->failed && !worker->readonly)
+		worker->v.msg_expunge(worker, uid);
+}
+
+void dsync_worker_msg_copy(struct dsync_worker *worker,
+			   const mailbox_guid_t *src_mailbox, uint32_t src_uid,
+			   const struct dsync_message *dest_msg,
+			   dsync_worker_copy_callback_t *callback,
+			   void *context)
+{
+	if (!worker->failed && !worker->readonly) {
+		T_BEGIN {
+			worker->v.msg_copy(worker, src_mailbox, src_uid,
+					   dest_msg, callback, context);
+		} T_END;
+	} else {
+		callback(FALSE, context);
+	}
+}
+
+void dsync_worker_msg_save(struct dsync_worker *worker,
+			   const struct dsync_message *msg,
+			   const struct dsync_msg_static_data *data,
+			   dsync_worker_save_callback_t *callback,
+			   void *context)
+{
+	if (!worker->readonly) {
+		if (worker->failed)
+			callback(context);
+		else T_BEGIN {
+			worker->v.msg_save(worker, msg, data,
+					   callback, context);
+		} T_END;
+	} else {
+		const unsigned char *d;
+		size_t size;
+
+		while ((i_stream_read_data(data->input, &d, &size, 0)) > 0)
+			i_stream_skip(data->input, size);
+		callback(context);
+	}
+}
+
+void dsync_worker_msg_save_cancel(struct dsync_worker *worker)
+{
+	worker->v.msg_save_cancel(worker);
+}
+
+void dsync_worker_msg_get(struct dsync_worker *worker,
+			  const mailbox_guid_t *mailbox, uint32_t uid,
+			  dsync_worker_msg_callback_t *callback, void *context)
+{
+	i_assert(uid != 0);
+
+	if (worker->failed)
+		callback(DSYNC_MSG_GET_RESULT_FAILED, NULL, context);
+	else T_BEGIN {
+		worker->v.msg_get(worker, mailbox, uid, callback, context);
+	} T_END;
+}
+
+void dsync_worker_finish(struct dsync_worker *worker,
+			 dsync_worker_finish_callback_t *callback,
+			 void *context)
+{
+	worker->v.finish(worker, callback, context);
+}
+
+void dsync_worker_set_failure(struct dsync_worker *worker)
+{
+	worker->failed = TRUE;
+}
+
+bool dsync_worker_has_failed(struct dsync_worker *worker)
+{
+	return worker->failed;
+}
+
+bool dsync_worker_has_unexpected_changes(struct dsync_worker *worker)
+{
+	return worker->unexpected_changes;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/dsync-worker.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,164 @@
+#ifndef DSYNC_WORKER_H
+#define DSYNC_WORKER_H
+
+#include "ioloop.h"
+#include "dsync-data.h"
+
+enum dsync_msg_get_result {
+	DSYNC_MSG_GET_RESULT_SUCCESS,
+	DSYNC_MSG_GET_RESULT_EXPUNGED,
+	DSYNC_MSG_GET_RESULT_FAILED
+};
+
+struct dsync_worker_subscription {
+	const char *vname, *storage_name, *ns_prefix;
+	time_t last_change;
+};
+struct dsync_worker_unsubscription {
+	/* SHA1 sum of the mailbox's storage name, i.e. without namespace
+	   prefix */
+	mailbox_guid_t name_sha1;
+	const char *ns_prefix;
+	time_t last_change;
+};
+
+typedef void dsync_worker_copy_callback_t(bool success, void *context);
+typedef void dsync_worker_save_callback_t(void *context);
+typedef void dsync_worker_msg_callback_t(enum dsync_msg_get_result result,
+					 const struct dsync_msg_static_data *data,
+					 void *context);
+typedef void dsync_worker_finish_callback_t(bool success, void *context);
+
+struct dsync_worker *
+dsync_worker_init_local(struct mail_user *user, char alt_char);
+struct dsync_worker *dsync_worker_init_proxy_client(int fd_in, int fd_out);
+void dsync_worker_deinit(struct dsync_worker **worker);
+
+/* Set this worker as read-only. All attempted changes are ignored. */
+void dsync_worker_set_readonly(struct dsync_worker *worker);
+/* Log verbosely */
+void dsync_worker_set_verbose(struct dsync_worker *worker);
+
+/* If any function returns with "waiting for more data", the given callback
+   gets called when more data is available. */
+void dsync_worker_set_input_callback(struct dsync_worker *worker,
+				     io_callback_t *callback, void *context);
+
+/* Returns TRUE if command queue is full and caller should stop sending
+   more commands. */
+bool dsync_worker_is_output_full(struct dsync_worker *worker);
+/* The given callback gets called when more commands can be queued. */
+void dsync_worker_set_output_callback(struct dsync_worker *worker,
+				      io_callback_t *callback, void *context);
+/* Try to flush command queue. Returns 1 if all flushed, 0 if something is
+   still in queue, -1 if failed. */
+int dsync_worker_output_flush(struct dsync_worker *worker);
+
+/* Iterate though all mailboxes */
+struct dsync_worker_mailbox_iter *
+dsync_worker_mailbox_iter_init(struct dsync_worker *worker);
+/* Get the next available mailbox. Returns 1 if ok, 0 if waiting for more data,
+   -1 if there are no more mailboxes. */
+int dsync_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *iter,
+				   struct dsync_mailbox *dsync_box_r);
+/* Finish mailbox iteration. Returns 0 if ok, -1 if iteration failed. */
+int dsync_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter **iter);
+
+/* Iterate though all subscriptions */
+struct dsync_worker_subs_iter *
+dsync_worker_subs_iter_init(struct dsync_worker *worker);
+/* Get the next subscription. Returns 1 if ok, 0 if waiting for more data,
+   -1 if there are no more subscriptions. */
+int dsync_worker_subs_iter_next(struct dsync_worker_subs_iter *iter,
+				struct dsync_worker_subscription *rec_r);
+/* Like _iter_next(), but list known recent unsubscriptions. */
+int dsync_worker_subs_iter_next_un(struct dsync_worker_subs_iter *iter,
+				   struct dsync_worker_unsubscription *rec_r);
+/* Finish subscription iteration. Returns 0 if ok, -1 if iteration failed. */
+int dsync_worker_subs_iter_deinit(struct dsync_worker_subs_iter **iter);
+/* Subscribe/unsubscribe mailbox */
+void dsync_worker_set_subscribed(struct dsync_worker *worker,
+				 const char *name, time_t last_change,
+				 bool set);
+
+/* Iterate through all messages in given mailboxes. The mailboxes are iterated
+   in the given order. */
+struct dsync_worker_msg_iter *
+dsync_worker_msg_iter_init(struct dsync_worker *worker,
+			   const mailbox_guid_t mailboxes[],
+			   unsigned int mailbox_count);
+/* Get the next available message. Also returns all expunged messages from
+   the end of mailbox (if next_uid-1 message exists, nothing is returned).
+   mailbox_idx_r contains the mailbox's index in mailboxes[] array given
+   to _iter_init(). Returns 1 if ok, 0 if waiting for more data, -1 if there
+   are no more messages. */
+int dsync_worker_msg_iter_next(struct dsync_worker_msg_iter *iter,
+			       unsigned int *mailbox_idx_r,
+			       struct dsync_message *msg_r);
+/* Finish message iteration. Returns 0 if ok, -1 if iteration failed. */
+int dsync_worker_msg_iter_deinit(struct dsync_worker_msg_iter **iter);
+
+/* Create mailbox with given name, GUID and UIDVALIDITY. */
+void dsync_worker_create_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *dsync_box);
+/* Delete mailbox/dir with given GUID. */
+void dsync_worker_delete_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *dsync_box);
+/* Delete mailbox's directory. Fail if it would also delete mailbox. */
+void dsync_worker_delete_dir(struct dsync_worker *worker,
+			     const struct dsync_mailbox *dsync_box);
+/* Change a mailbox and its childrens' name. The name is taken from the given
+   dsync_box (applying name_sep if necessary). */
+void dsync_worker_rename_mailbox(struct dsync_worker *worker,
+				 const mailbox_guid_t *mailbox,
+				 const struct dsync_mailbox *dsync_box);
+/* Find mailbox with given GUID and make sure its uid_next and
+   highest_modseq are up to date (but don't shrink them). */
+void dsync_worker_update_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *dsync_box);
+
+/* The following message syncing functions access the this selected mailbox. */
+void dsync_worker_select_mailbox(struct dsync_worker *worker,
+				 const struct dsync_mailbox *box);
+/* Update message's metadata (flags, keywords, modseq). */
+void dsync_worker_msg_update_metadata(struct dsync_worker *worker,
+				      const struct dsync_message *msg);
+/* Change message's UID. */
+void dsync_worker_msg_update_uid(struct dsync_worker *worker,
+				 uint32_t old_uid, uint32_t new_uid);
+/* Expunge given message. */
+void dsync_worker_msg_expunge(struct dsync_worker *worker, uint32_t uid);
+/* Copy given message. */
+void dsync_worker_msg_copy(struct dsync_worker *worker,
+			   const mailbox_guid_t *src_mailbox, uint32_t src_uid,
+			   const struct dsync_message *dest_msg,
+			   dsync_worker_copy_callback_t *callback,
+			   void *context);
+/* Save given message from the given input stream. Once saving is finished,
+   the given callback is called and the stream is destroyed. */
+void dsync_worker_msg_save(struct dsync_worker *worker,
+			   const struct dsync_message *msg,
+			   const struct dsync_msg_static_data *data,
+			   dsync_worker_save_callback_t *callback,
+			   void *context);
+/* Cancel any pending saves */
+void dsync_worker_msg_save_cancel(struct dsync_worker *worker);
+/* Get message data for saving. The callback is called once when the static
+   data has been received. The whole message may not have been downloaded yet,
+   so the caller must read the input stream until it returns EOF and then
+   unreference it. */
+void dsync_worker_msg_get(struct dsync_worker *worker,
+			  const mailbox_guid_t *mailbox, uint32_t uid,
+			  dsync_worker_msg_callback_t *callback, void *context);
+/* Call the callback once all the pending commands are finished. */
+void dsync_worker_finish(struct dsync_worker *worker,
+			 dsync_worker_finish_callback_t *callback,
+			 void *context);
+
+/* Returns TRUE if some commands have failed. */
+bool dsync_worker_has_failed(struct dsync_worker *worker);
+/* Returns TRUE if some UID or modseq changes didn't get assigned as
+   requested. */
+bool dsync_worker_has_unexpected_changes(struct dsync_worker *worker);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-brain-msgs.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,670 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "crc32.h"
+#include "dsync-brain-private.h"
+#include "test-dsync-worker.h"
+#include "test-dsync-common.h"
+
+enum test_box_add_type {
+	ADD_SRC,
+	ADD_DEST,
+	ADD_BOTH
+};
+
+struct test_dsync_mailbox {
+	struct dsync_brain_mailbox box;
+	ARRAY_DEFINE(src_msgs, struct dsync_message);
+	ARRAY_DEFINE(dest_msgs, struct dsync_message);
+};
+ARRAY_DEFINE_TYPE(test_dsync_mailbox, struct test_dsync_mailbox);
+
+static ARRAY_TYPE(test_dsync_mailbox) mailboxes;
+static struct test_dsync_worker *test_src_worker, *test_dest_worker;
+
+void dsync_brain_fail(struct dsync_brain *brain ATTR_UNUSED) {}
+void dsync_brain_msg_sync_new_msgs(struct dsync_brain_mailbox_sync *sync ATTR_UNUSED) {}
+
+static struct test_dsync_mailbox *test_box_find(const char *name)
+{
+	struct test_dsync_mailbox *boxes;
+	unsigned int i, count;
+
+	boxes = array_get_modifiable(&mailboxes, &count);
+	for (i = 0; i < count; i++) {
+		if (strcmp(boxes[i].box.box.name, name) == 0)
+			return &boxes[i];
+	}
+	return NULL;
+}
+
+static bool
+test_box_has_guid(const char *name, const mailbox_guid_t *guid)
+{
+	const struct test_dsync_mailbox *box;
+
+	box = test_box_find(name);
+	return box != NULL &&
+		memcmp(box->box.box.mailbox_guid.guid, guid->guid,
+		       sizeof(box->box.box.mailbox_guid.guid)) == 0;
+}
+
+static struct test_dsync_mailbox *
+test_box_add(enum test_box_add_type type, const char *name)
+{
+	struct test_dsync_mailbox *tbox;
+	struct dsync_mailbox *box;
+
+	tbox = test_box_find(name);
+	if (tbox == NULL) {
+		tbox = array_append_space(&mailboxes);
+		i_array_init(&tbox->src_msgs, 16);
+		i_array_init(&tbox->dest_msgs, 16);
+	}
+
+	box = i_new(struct dsync_mailbox, 1);
+	box->name = i_strdup(name);
+
+	dsync_str_sha_to_guid(t_strconcat("box-", name, NULL),
+			      &box->mailbox_guid);
+	dsync_str_sha_to_guid(name, &box->name_sha1);
+
+	box->uid_validity = crc32_str(name);
+	box->highest_modseq = 1;
+
+	switch (type) {
+	case ADD_SRC:
+		tbox->box.src = box;
+		break;
+	case ADD_DEST:
+		tbox->box.dest = box;
+		break;
+	case ADD_BOTH:
+		tbox->box.src = box;
+		tbox->box.dest = box;
+		break;
+	}
+	tbox->box.box.name = box->name;
+	tbox->box.box.mailbox_guid = box->mailbox_guid;
+	tbox->box.box.name_sha1 = box->name_sha1;
+	tbox->box.box.uid_validity = box->uid_validity;
+	return tbox;
+}
+
+static void test_msg_add(struct test_dsync_mailbox *box,
+			 enum test_box_add_type type,
+			 const char *guid, uint32_t uid)
+{
+	static int msg_date = 0;
+	struct dsync_message msg;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.guid = i_strdup(guid);
+	msg.uid = uid;
+	msg.modseq = ++box->box.box.highest_modseq;
+	msg.save_date = ++msg_date;
+
+	switch (type) {
+	case ADD_SRC:
+		box->box.src->highest_modseq++;
+		box->box.src->uid_next = uid + 1;
+		array_append(&box->src_msgs, &msg, 1);
+		break;
+	case ADD_DEST:
+		box->box.dest->highest_modseq++;
+		box->box.dest->uid_next = uid + 1;
+		array_append(&box->dest_msgs, &msg, 1);
+		break;
+	case ADD_BOTH:
+		box->box.src->highest_modseq++;
+		box->box.dest->highest_modseq++;
+		box->box.src->uid_next = uid + 1;
+		box->box.dest->uid_next = uid + 1;
+		array_append(&box->src_msgs, &msg, 1);
+		array_append(&box->dest_msgs, &msg, 1);
+		break;
+	}
+	if (box->box.box.uid_next <= uid)
+		box->box.box.uid_next = uid + 1;
+}
+
+static void test_msg_set_modseq(struct test_dsync_mailbox *box,
+				enum test_box_add_type type,
+				uint32_t uid, uint64_t modseq)
+{
+	struct dsync_message *msgs;
+	unsigned int i, count;
+
+	i_assert(modseq <= box->box.box.highest_modseq);
+	if (type != ADD_DEST) {
+		msgs = array_get_modifiable(&box->src_msgs, &count);
+		for (i = 0; i < count; i++) {
+			if (msgs[i].uid == uid) {
+				msgs[i].modseq = modseq;
+				break;
+			}
+		}
+		i_assert(i < count);
+	}
+	if (type != ADD_SRC) {
+		msgs = array_get_modifiable(&box->dest_msgs, &count);
+		for (i = 0; i < count; i++) {
+			if (msgs[i].uid == uid) {
+				msgs[i].modseq = modseq;
+				break;
+			}
+		}
+		i_assert(i < count);
+	}
+}
+
+static void test_msg_set_flags(struct test_dsync_mailbox *box,
+			       enum test_box_add_type type,
+			       uint32_t uid, enum mail_flags flags)
+{
+	unsigned char guid_128_data[GUID_128_SIZE * 2 + 1];
+	struct dsync_message *msgs;
+	unsigned int i, count;
+
+	box->box.box.highest_modseq++;
+	if (type != ADD_DEST) {
+		box->box.src->highest_modseq = box->box.box.highest_modseq;
+		msgs = array_get_modifiable(&box->src_msgs, &count);
+		for (i = 0; i < count; i++) {
+			if (msgs[i].uid == uid) {
+				if ((flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0) {
+					msgs[i].guid = i_strdup(dsync_get_guid_128_str(msgs[i].guid,
+						guid_128_data, sizeof(guid_128_data)));
+				}
+				msgs[i].flags = flags;
+				msgs[i].modseq = box->box.src->highest_modseq;
+				break;
+			}
+		}
+		i_assert(i < count);
+	}
+	if (type != ADD_SRC) {
+		box->box.dest->highest_modseq = box->box.box.highest_modseq;
+		msgs = array_get_modifiable(&box->dest_msgs, &count);
+		for (i = 0; i < count; i++) {
+			if (msgs[i].uid == uid) {
+				if ((flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0) {
+					msgs[i].guid = i_strdup(dsync_get_guid_128_str(msgs[i].guid,
+						guid_128_data, sizeof(guid_128_data)));
+				}
+				msgs[i].flags = flags;
+				msgs[i].modseq = box->box.dest->highest_modseq;
+				break;
+			}
+		}
+		i_assert(i < count);
+	}
+}
+
+static void ATTR_SENTINEL
+test_msg_set_keywords(struct test_dsync_mailbox *box,
+		      enum test_box_add_type type,
+		      uint32_t uid, const char *kw, ...)
+{
+	struct dsync_message *msgs;
+	unsigned int i, count;
+	va_list va;
+	ARRAY_TYPE(const_string) keywords;
+
+	t_array_init(&keywords, 8);
+	array_append(&keywords, &kw, 1);
+	va_start(va, kw);
+	while ((kw = va_arg(va, const char *)) != NULL)
+		array_append(&keywords, &kw, 1);
+	va_end(va);
+	(void)array_append_space(&keywords);
+
+	box->box.box.highest_modseq++;
+	if (type != ADD_DEST) {
+		box->box.src->highest_modseq = box->box.box.highest_modseq;
+		msgs = array_get_modifiable(&box->src_msgs, &count);
+		for (i = 0; i < count; i++) {
+			if (msgs[i].uid == uid) {
+				msgs[i].keywords = array_idx(&keywords, 0);
+				msgs[i].modseq = box->box.src->highest_modseq;
+				break;
+			}
+		}
+		i_assert(i < count);
+	}
+	if (type != ADD_SRC) {
+		box->box.dest->highest_modseq = box->box.box.highest_modseq;
+		msgs = array_get_modifiable(&box->dest_msgs, &count);
+		for (i = 0; i < count; i++) {
+			if (msgs[i].uid == uid) {
+				msgs[i].keywords = array_idx(&keywords, 0);
+				msgs[i].modseq = box->box.src->highest_modseq;
+				break;
+			}
+		}
+		i_assert(i < count);
+	}
+}
+
+static void
+test_dsync_sync_msgs(struct test_dsync_worker *worker, bool dest)
+{
+	const struct test_dsync_mailbox *boxes;
+	const struct dsync_message *msgs;
+	struct test_dsync_worker_msg test_msg;
+	unsigned int i, j, box_count, msg_count;
+
+	boxes = array_get(&mailboxes, &box_count);
+	for (i = 0; i < box_count; i++) {
+		msgs = dest ? array_get(&boxes[i].dest_msgs, &msg_count) :
+			array_get(&boxes[i].src_msgs, &msg_count);
+		for (j = 0; j < msg_count; j++) {
+			test_msg.msg = msgs[j];
+			test_msg.mailbox_idx = i;
+			array_append(&worker->msg_iter.msgs, &test_msg, 1);
+			worker->worker.input_callback(worker->worker.input_context);
+		}
+	}
+
+	worker->msg_iter.last = TRUE;
+	worker->worker.input_callback(worker->worker.input_context);
+}
+
+static struct dsync_brain *test_dsync_brain_init(void)
+{
+	struct dsync_brain *brain;
+
+	brain = i_new(struct dsync_brain, 1);
+	brain->src_worker = dsync_worker_init_test();
+	brain->dest_worker = dsync_worker_init_test();
+
+	test_src_worker = (struct test_dsync_worker *)brain->src_worker;
+	test_dest_worker = (struct test_dsync_worker *)brain->dest_worker;
+	return brain;
+}
+
+static struct dsync_brain_mailbox_sync *
+test_dsync_brain_sync_init(void)
+{
+	ARRAY_TYPE(dsync_brain_mailbox) brain_boxes;
+	struct dsync_brain_mailbox_sync *sync;
+	const struct test_dsync_mailbox *tboxes;
+	unsigned int i, count;
+
+	tboxes = array_get(&mailboxes, &count);
+	t_array_init(&brain_boxes, count);
+	for (i = 0; i < count; i++)
+		array_append(&brain_boxes, &tboxes[i].box, 1);
+
+	sync = dsync_brain_msg_sync_init(test_dsync_brain_init(), &brain_boxes);
+	dsync_brain_msg_sync_more(sync);
+	test_dsync_sync_msgs(test_dest_worker, TRUE);
+	test_dsync_sync_msgs(test_src_worker, FALSE);
+	return sync;
+}
+
+static void test_dsync_brain_msg_sync_box_multi(void)
+{
+	struct test_dsync_mailbox *box;
+	struct dsync_brain_mailbox_sync *sync;
+	struct test_dsync_msg_event msg_event;
+	const struct dsync_brain_new_msg *new_msgs;
+	unsigned int count;
+
+	/* test that msg syncing finds and syncs all mailboxes */
+	test_begin("dsync brain msg sync box multi");
+
+	i_array_init(&mailboxes, 32);
+	box = test_box_add(ADD_BOTH, "both");
+	test_msg_add(box, ADD_BOTH, "guid1", 1);
+	test_msg_set_flags(box, ADD_SRC, 1, MAIL_SEEN);
+	test_msg_set_flags(box, ADD_DEST, 1, MAIL_DRAFT);
+	test_msg_set_flags(box, ADD_SRC, 1, MAIL_ANSWERED);
+	box = test_box_add(ADD_SRC, "src");
+	test_msg_add(box, ADD_SRC, "guid2", 5);
+	box = test_box_add(ADD_DEST, "dest");
+	test_msg_add(box, ADD_DEST, "guid3", 3);
+
+	sync = test_dsync_brain_sync_init();
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(test_box_has_guid("both", &msg_event.mailbox));
+	test_assert(msg_event.msg.uid == 1);
+	test_assert(msg_event.msg.flags == MAIL_ANSWERED);
+	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+
+	new_msgs = array_get(&sync->dest_msg_iter->new_msgs, &count);
+	test_assert(count == 1);
+	test_assert(new_msgs[0].mailbox_idx == 1);
+	test_assert(new_msgs[0].msg->uid == 5);
+	test_assert(strcmp(new_msgs[0].msg->guid, "guid2") == 0);
+
+	new_msgs = array_get(&sync->src_msg_iter->new_msgs, &count);
+	test_assert(count == 1);
+	test_assert(new_msgs[0].mailbox_idx == 2);
+	test_assert(new_msgs[0].msg->uid == 3);
+	test_assert(strcmp(new_msgs[0].msg->guid, "guid3") == 0);
+
+	test_end();
+}
+
+static void test_dsync_brain_msg_sync_box(enum test_box_add_type type)
+{
+	struct test_dsync_mailbox *box;
+	struct dsync_brain_mailbox_sync *sync;
+	struct test_dsync_msg_event msg_event;
+	const struct dsync_brain_new_msg *new_msgs;
+	unsigned int count;
+
+	i_array_init(&mailboxes, 32);
+	box = test_box_add(type, "box1");
+	test_msg_add(box, type, "guid1", 1);
+	box = test_box_add(type, "box2");
+	test_msg_add(box, type, "guid2", 2);
+
+	sync = test_dsync_brain_sync_init();
+
+	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+
+	new_msgs = array_get(type == ADD_DEST ? &sync->src_msg_iter->new_msgs :
+			     &sync->dest_msg_iter->new_msgs, &count);
+	test_assert(count == 2);
+	test_assert(new_msgs[0].mailbox_idx == 0);
+	test_assert(new_msgs[0].msg->uid == 1);
+	test_assert(strcmp(new_msgs[0].msg->guid, "guid1") == 0);
+	test_assert(new_msgs[1].mailbox_idx == 1);
+	test_assert(new_msgs[1].msg->uid == 2);
+	test_assert(strcmp(new_msgs[1].msg->guid, "guid2") == 0);
+}
+
+static void test_dsync_brain_msg_sync_box_single(void)
+{
+	test_begin("dsync brain msg sync box src");
+	test_dsync_brain_msg_sync_box(ADD_SRC);
+	test_end();
+
+	test_begin("dsync brain msg sync box dest");
+	test_dsync_brain_msg_sync_box(ADD_DEST);
+	test_end();
+}
+
+static void test_dsync_brain_msg_sync_existing(void)
+{
+	struct test_dsync_mailbox *box;
+	struct dsync_brain_mailbox_sync *sync;
+	struct test_dsync_msg_event msg_event;
+
+	test_begin("dsync brain msg sync existing");
+
+	i_array_init(&mailboxes, 1);
+	box = test_box_add(ADD_BOTH, "box");
+	test_msg_add(box, ADD_BOTH, "guid1", 1);
+	test_msg_add(box, ADD_BOTH, "guid2", 2);
+	test_msg_add(box, ADD_BOTH, "guid3", 3);
+	test_msg_add(box, ADD_BOTH, "guid5", 5);
+	test_msg_add(box, ADD_BOTH, "guid6", 6);
+	test_msg_add(box, ADD_BOTH, "guid9", 9);
+	test_msg_add(box, ADD_BOTH, "guid10", 10);
+	test_msg_add(box, ADD_BOTH, "guid11", 11);
+	test_msg_add(box, ADD_BOTH, "guid12", 12);
+
+	/* unchanged */
+	test_msg_set_flags(box, ADD_BOTH, 1, MAIL_SEEN);
+
+	/* changed, same modseq - dest has more flags so it will be used */
+	test_msg_set_flags(box, ADD_SRC, 2, MAIL_ANSWERED);
+	test_msg_set_flags(box, ADD_DEST, 2, MAIL_ANSWERED | MAIL_SEEN);
+	test_msg_set_modseq(box, ADD_BOTH, 2, 2);
+
+	/* changed, same modseq - src has more flags so it will be used */
+	test_msg_set_flags(box, ADD_SRC, 3, MAIL_ANSWERED | MAIL_SEEN);
+	test_msg_set_flags(box, ADD_DEST, 3, MAIL_ANSWERED);
+	test_msg_set_modseq(box, ADD_BOTH, 3, 3);
+
+	/* changed, dest has higher modseq */
+	test_msg_set_flags(box, ADD_BOTH, 5, MAIL_DRAFT);
+	test_msg_set_flags(box, ADD_DEST, 5, MAIL_FLAGGED);
+
+	/* changed, src has higher modseq */
+	test_msg_set_flags(box, ADD_DEST, 6, MAIL_FLAGGED);
+	test_msg_set_flags(box, ADD_SRC, 6, 0);
+
+	/* keywords changed, src has higher modseq */
+	test_msg_set_keywords(box, ADD_SRC, 9, "hello", "world", NULL);
+
+	/* flag/keyword conflict, same modseq - src has more so it
+	   will be used */
+	test_msg_set_keywords(box, ADD_SRC, 10, "foo", NULL);
+	test_msg_set_flags(box, ADD_SRC, 10, MAIL_SEEN);
+	test_msg_set_flags(box, ADD_DEST, 10, MAIL_DRAFT);
+	test_msg_set_modseq(box, ADD_BOTH, 10, 5);
+
+	/* flag/keyword conflict, same modseq - dest has more so it
+	   will be used */
+	test_msg_set_keywords(box, ADD_DEST, 11, "foo", NULL);
+	test_msg_set_flags(box, ADD_SRC, 11, MAIL_SEEN);
+	test_msg_set_flags(box, ADD_DEST, 11, MAIL_DRAFT);
+	test_msg_set_modseq(box, ADD_BOTH, 11, 5);
+
+	/* flag/keyword conflict, same modseq - both have same number of
+	   flags so src will be used */
+	test_msg_set_keywords(box, ADD_SRC, 12, "bar", NULL);
+	test_msg_set_keywords(box, ADD_DEST, 12, "foo", NULL);
+	test_msg_set_flags(box, ADD_SRC, 12, MAIL_SEEN);
+	test_msg_set_flags(box, ADD_DEST, 12, MAIL_DRAFT);
+	test_msg_set_modseq(box, ADD_BOTH, 12, 5);
+
+	sync = test_dsync_brain_sync_init();
+	test_assert(array_count(&sync->src_msg_iter->new_msgs) == 0);
+	test_assert(array_count(&sync->dest_msg_iter->new_msgs) == 0);
+
+	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 2);
+	test_assert(msg_event.msg.flags == (MAIL_ANSWERED | MAIL_SEEN));
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 3);
+	test_assert(msg_event.msg.flags == (MAIL_ANSWERED | MAIL_SEEN));
+
+	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 5);
+	test_assert(msg_event.msg.flags == MAIL_FLAGGED);
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 6);
+	test_assert(msg_event.msg.flags == 0);
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 9);
+	test_assert(msg_event.msg.flags == 0);
+	test_assert(strcmp(msg_event.msg.keywords[0], "hello") == 0);
+	test_assert(strcmp(msg_event.msg.keywords[1], "world") == 0);
+	test_assert(msg_event.msg.keywords[2] == NULL);
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 10);
+	test_assert(msg_event.msg.flags == MAIL_SEEN);
+	test_assert(strcmp(msg_event.msg.keywords[0], "foo") == 0);
+	test_assert(msg_event.msg.keywords[1] == NULL);
+
+	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 11);
+	test_assert(msg_event.msg.flags == MAIL_DRAFT);
+	test_assert(strcmp(msg_event.msg.keywords[0], "foo") == 0);
+	test_assert(msg_event.msg.keywords[1] == NULL);
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(msg_event.msg.uid == 12);
+	test_assert(msg_event.msg.flags == MAIL_SEEN);
+	test_assert(strcmp(msg_event.msg.keywords[0], "bar") == 0);
+	test_assert(msg_event.msg.keywords[1] == NULL);
+
+	test_assert(!test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_end();
+}
+
+static void test_dsync_brain_msg_sync_expunges(void)
+{
+	struct test_dsync_mailbox *box;
+	struct dsync_brain_mailbox_sync *sync;
+	struct test_dsync_msg_event msg_event;
+
+	test_begin("dsync brain msg sync expunges");
+
+	i_array_init(&mailboxes, 1);
+	box = test_box_add(ADD_BOTH, "box");
+
+	/* expunged from dest */
+	test_msg_add(box, ADD_SRC, "guid1", 1);
+	/* expunged from src */
+	test_msg_add(box, ADD_DEST, "guid2", 2);
+	/* expunged from dest with expunge record */
+	test_msg_add(box, ADD_BOTH, "guid3", 3);
+	test_msg_set_flags(box, ADD_DEST, 3, DSYNC_MAIL_FLAG_EXPUNGED);
+	/* expunged from src with expunge record */
+	test_msg_add(box, ADD_BOTH, "guid4", 4);
+	test_msg_set_flags(box, ADD_SRC, 4, DSYNC_MAIL_FLAG_EXPUNGED);
+	/* expunged from both, with expunge record in src */
+	test_msg_add(box, ADD_SRC, "guid5", 5);
+	test_msg_set_flags(box, ADD_SRC, 5, DSYNC_MAIL_FLAG_EXPUNGED);
+	/* expunged from both, with expunge record in dest */
+	test_msg_add(box, ADD_DEST, "guid6", 6);
+	test_msg_set_flags(box, ADD_DEST, 6, DSYNC_MAIL_FLAG_EXPUNGED);
+	/* expunged from both, with expunge record in both */
+	test_msg_add(box, ADD_BOTH, "guid7", 7);
+	test_msg_set_flags(box, ADD_BOTH, 7, DSYNC_MAIL_FLAG_EXPUNGED);
+
+	sync = test_dsync_brain_sync_init();
+	test_assert(array_count(&sync->src_msg_iter->new_msgs) == 0);
+	test_assert(array_count(&sync->dest_msg_iter->new_msgs) == 0);
+
+	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
+	test_assert(msg_event.msg.uid == 1);
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
+	test_assert(msg_event.msg.uid == 2);
+
+	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
+	test_assert(msg_event.msg.uid == 3);
+
+	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
+	test_assert(msg_event.msg.uid == 4);
+
+	test_assert(!test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+	test_end();
+}
+
+static void test_dsync_brain_msg_sync_uid_conflicts(void)
+{
+	struct test_dsync_mailbox *box;
+	struct dsync_brain_mailbox_sync *sync;
+	struct test_dsync_msg_event msg_event;
+	const struct dsync_brain_uid_conflict *conflicts;
+	const struct dsync_brain_new_msg *src_msgs, *dest_msgs;
+	unsigned int src_count, dest_count;
+
+	test_begin("dsync brain msg sync uid conflicts");
+
+	i_array_init(&mailboxes, 16);
+
+	/* existing guid mismatch */
+	box = test_box_add(ADD_BOTH, "box1");
+	test_msg_add(box, ADD_SRC, "guid1", 1);
+	test_msg_add(box, ADD_DEST, "guid2", 1);
+
+	/* preserve uid */
+	test_msg_add(box, ADD_BOTH, "guid3", 3);
+	/* extra message in src */
+	test_msg_add(box, ADD_SRC, "guid4", 4);
+	/* extra message in dest */
+	test_msg_add(box, ADD_DEST, "guid5", 5);
+
+	/* conflict in expunged message expunged in dest */
+	test_msg_add(box, ADD_SRC, "guid6", 6);
+	test_msg_add(box, ADD_DEST, "guid7", 6);
+	test_msg_set_flags(box, ADD_DEST, 6, DSYNC_MAIL_FLAG_EXPUNGED);
+
+	/* conflict in expunged message expunged in src */
+	test_msg_add(box, ADD_SRC, "guid8", 8);
+	test_msg_set_flags(box, ADD_SRC, 8, DSYNC_MAIL_FLAG_EXPUNGED);
+	test_msg_add(box, ADD_DEST, "guid9", 8);
+
+	/* conflict in expunged message expunged in both */
+	test_msg_add(box, ADD_SRC, "guid10", 10);
+	test_msg_set_flags(box, ADD_SRC, 10, DSYNC_MAIL_FLAG_EXPUNGED);
+	test_msg_add(box, ADD_DEST, "guid11", 10);
+	test_msg_set_flags(box, ADD_DEST, 10, DSYNC_MAIL_FLAG_EXPUNGED);
+
+	sync = test_dsync_brain_sync_init();
+
+	conflicts = array_get(&sync->src_msg_iter->uid_conflicts, &src_count);
+	test_assert(src_count == 3);
+	test_assert(conflicts[0].old_uid == 1);
+	test_assert(conflicts[0].new_uid == 12);
+	test_assert(conflicts[1].old_uid == 4);
+	test_assert(conflicts[1].new_uid == 13);
+	test_assert(conflicts[2].old_uid == 6);
+	test_assert(conflicts[2].new_uid == 15);
+
+	conflicts = array_get(&sync->dest_msg_iter->uid_conflicts, &dest_count);
+	test_assert(dest_count == 3);
+	test_assert(conflicts[0].old_uid == 1);
+	test_assert(conflicts[0].new_uid == 11);
+	test_assert(conflicts[1].old_uid == 5);
+	test_assert(conflicts[1].new_uid == 14);
+	test_assert(conflicts[2].old_uid == 8);
+	test_assert(conflicts[2].new_uid == 16);
+
+	test_assert(!test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
+	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
+
+	src_msgs = array_get(&sync->src_msg_iter->new_msgs, &src_count);
+	dest_msgs = array_get(&sync->dest_msg_iter->new_msgs, &dest_count);
+	test_assert(src_count == 3);
+	test_assert(dest_count == 3);
+
+	test_assert(dest_msgs[0].msg->uid == 12);
+	test_assert(strcmp(dest_msgs[0].msg->guid, "guid1") == 0);
+	test_assert(src_msgs[0].msg->uid == 11);
+	test_assert(strcmp(src_msgs[0].msg->guid, "guid2") == 0);
+	test_assert(dest_msgs[1].msg->uid == 13);
+	test_assert(strcmp(dest_msgs[1].msg->guid, "guid4") == 0);
+	test_assert(src_msgs[1].msg->uid == 14);
+	test_assert(strcmp(src_msgs[1].msg->guid, "guid5") == 0);
+	test_assert(dest_msgs[2].msg->uid == 15);
+	test_assert(strcmp(dest_msgs[2].msg->guid, "guid6") == 0);
+	test_assert(src_msgs[2].msg->uid == 16);
+	test_assert(strcmp(src_msgs[2].msg->guid, "guid9") == 0);
+
+	test_end();
+}
+
+int main(void)
+{
+	static void (*test_functions[])(void) = {
+		test_dsync_brain_msg_sync_box_multi,
+		test_dsync_brain_msg_sync_box_single,
+		test_dsync_brain_msg_sync_existing,
+		test_dsync_brain_msg_sync_expunges,
+		test_dsync_brain_msg_sync_uid_conflicts,
+		NULL
+	};
+
+	return test_run(test_functions);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-brain.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,289 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "dsync-brain-private.h"
+#include "test-dsync-worker.h"
+#include "test-dsync-common.h"
+
+struct master_service *master_service;
+static struct test_dsync_worker *src_test_worker, *dest_test_worker;
+
+struct dsync_brain_mailbox_sync *
+dsync_brain_msg_sync_init(struct dsync_brain *brain,
+			  const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes)
+{
+	struct dsync_brain_mailbox_sync *sync;
+
+	sync = i_new(struct dsync_brain_mailbox_sync, 1);
+	sync->brain = brain;
+	i_array_init(&sync->mailboxes, array_count(mailboxes));
+	array_append_array(&sync->mailboxes, mailboxes);
+	return sync;
+}
+void dsync_brain_msg_sync_more(struct dsync_brain_mailbox_sync *sync ATTR_UNUSED) {}
+
+void dsync_brain_msg_sync_deinit(struct dsync_brain_mailbox_sync **_sync)
+{
+	array_free(&(*_sync)->mailboxes);
+	i_free(*_sync);
+}
+
+static void mailboxes_set_guids(struct dsync_mailbox *boxes)
+{
+	for (; boxes->name != NULL; boxes++) {
+		dsync_str_sha_to_guid(t_strconcat("box-", boxes->name, NULL),
+				      &boxes->mailbox_guid);
+		dsync_str_sha_to_guid(boxes->name, &boxes->name_sha1);
+	}
+}
+
+static void mailboxes_send_to_worker(struct test_dsync_worker *test_worker,
+				     struct dsync_mailbox *boxes)
+{
+	unsigned int i;
+
+	for (i = 0; boxes[i].name != NULL; i++) {
+		test_worker->box_iter.next_box = &boxes[i];
+		test_worker->worker.input_callback(test_worker->worker.input_context);
+	}
+	test_worker->box_iter.last = TRUE;
+	test_worker->worker.input_callback(test_worker->worker.input_context);
+}
+
+static void subscriptions_send_to_worker(struct test_dsync_worker *test_worker)
+{
+	test_worker->subs_iter.last_subs = TRUE;
+	test_worker->subs_iter.last_unsubs = TRUE;
+	test_worker->worker.input_callback(test_worker->worker.input_context);
+}
+
+static bool
+test_dsync_mailbox_create_equals(const struct dsync_mailbox *cbox,
+				 const struct dsync_mailbox *obox)
+{
+	return strcmp(cbox->name, obox->name) == 0 &&
+		memcmp(cbox->mailbox_guid.guid, obox->mailbox_guid.guid,
+		       sizeof(cbox->mailbox_guid.guid)) == 0 &&
+		memcmp(cbox->name_sha1.guid, obox->name_sha1.guid,
+		       sizeof(cbox->name_sha1.guid)) == 0 &&
+		cbox->uid_validity == obox->uid_validity &&
+		cbox->uid_next == 1 && cbox->highest_modseq == 0;
+}
+
+static bool
+test_dsync_mailbox_delete_equals(const struct dsync_mailbox *dbox,
+				 const struct dsync_mailbox *obox)
+{
+	return memcmp(dbox->mailbox_guid.guid, obox->mailbox_guid.guid,
+		      sizeof(dbox->mailbox_guid.guid)) == 0 &&
+		dbox->last_change == obox->last_change;
+}
+
+static void
+test_dsync_mailbox_update(const struct dsync_mailbox *bbox,
+			  const struct dsync_mailbox *box)
+{
+	struct test_dsync_box_event src_event, dest_event;
+
+	test_assert(test_dsync_worker_next_box_event(src_test_worker, &src_event));
+	test_assert(test_dsync_worker_next_box_event(dest_test_worker, &dest_event));
+	test_assert(src_event.type == dest_event.type &&
+		    dsync_mailboxes_equal(&src_event.box, &dest_event.box));
+
+	test_assert(src_event.type == LAST_BOX_TYPE_UPDATE);
+	test_assert(dsync_mailboxes_equal(&src_event.box, box));
+	test_assert(dsync_mailboxes_equal(bbox, box));
+}
+
+static int
+dsync_brain_mailbox_name_cmp(const struct dsync_brain_mailbox *box1,
+			     const struct dsync_brain_mailbox *box2)
+{
+	return strcmp(box1->box.name, box2->box.name);
+}
+
+static void test_dsync_brain(void)
+{
+	static struct dsync_mailbox src_boxes[] = {
+		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123124ULL, 3636, 0, ARRAY_INIT },
+		{ "boxx", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
+		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
+	};
+	static struct dsync_mailbox dest_boxes[] = {
+		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567891, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123124ULL, 3636, 0, ARRAY_INIT },
+		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxy", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
+		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
+	};
+	struct dsync_brain *brain;
+	struct dsync_worker *src_worker, *dest_worker;
+	struct test_dsync_box_event box_event;
+	const struct dsync_brain_mailbox *brain_boxes;
+	unsigned int i, count;
+
+	test_begin("dsync brain");
+
+	mailboxes_set_guids(src_boxes);
+	mailboxes_set_guids(dest_boxes);
+
+	src_worker = dsync_worker_init_test();
+	dest_worker = dsync_worker_init_test();
+	src_test_worker = (struct test_dsync_worker *)src_worker;
+	dest_test_worker = (struct test_dsync_worker *)dest_worker;
+
+	brain = dsync_brain_init(src_worker, dest_worker, NULL,
+				 DSYNC_BRAIN_FLAG_LOCAL);
+	dsync_brain_sync(brain);
+
+	/* have brain read the mailboxes */
+	mailboxes_send_to_worker(src_test_worker, src_boxes);
+	mailboxes_send_to_worker(dest_test_worker, dest_boxes);
+
+	subscriptions_send_to_worker(src_test_worker);
+	subscriptions_send_to_worker(dest_test_worker);
+
+	test_assert(brain->state == DSYNC_STATE_SYNC_MSGS);
+
+	/* check that it created/deleted missing mailboxes */
+	test_assert(test_dsync_worker_next_box_event(dest_test_worker, &box_event));
+	test_assert(box_event.type == LAST_BOX_TYPE_DELETE);
+	test_assert(test_dsync_mailbox_delete_equals(&box_event.box, &dest_boxes[8]));
+
+	test_assert(test_dsync_worker_next_box_event(src_test_worker, &box_event));
+	test_assert(box_event.type == LAST_BOX_TYPE_DELETE);
+	test_assert(test_dsync_mailbox_delete_equals(&box_event.box, &src_boxes[7]));
+
+	test_assert(test_dsync_worker_next_box_event(dest_test_worker, &box_event));
+	test_assert(box_event.type == LAST_BOX_TYPE_CREATE);
+	test_assert(test_dsync_mailbox_create_equals(&box_event.box, &src_boxes[6]));
+
+	test_assert(test_dsync_worker_next_box_event(src_test_worker, &box_event));
+	test_assert(box_event.type == LAST_BOX_TYPE_CREATE);
+	test_assert(test_dsync_mailbox_create_equals(&box_event.box, &dest_boxes[6]));
+
+	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
+	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
+
+	array_sort(&brain->mailbox_sync->mailboxes,
+		   dsync_brain_mailbox_name_cmp);
+
+	/* check mailbox updates */
+	brain->state++;
+	dsync_brain_sync(brain);
+	test_assert(brain->state == DSYNC_STATE_SYNC_UPDATE_MAILBOXES);
+	dsync_brain_sync(brain);
+	test_assert(brain->state == DSYNC_STATE_SYNC_END);
+
+	brain_boxes = array_get(&brain->mailbox_sync->mailboxes, &count);
+	test_assert(count == 7);
+	for (i = 0; i < 5; i++) {
+		test_assert(dsync_mailboxes_equal(brain_boxes[i].src, &src_boxes[i+1]));
+		test_assert(dsync_mailboxes_equal(brain_boxes[i].dest, &dest_boxes[i+1]));
+	}
+	test_assert(dsync_mailboxes_equal(brain_boxes[5].src, &src_boxes[6]));
+	test_assert(brain_boxes[5].dest == NULL);
+	test_assert(brain_boxes[6].src == NULL);
+	test_assert(dsync_mailboxes_equal(brain_boxes[6].dest, &dest_boxes[6]));
+
+	test_dsync_mailbox_update(&brain_boxes[0].box, &src_boxes[1]);
+	test_dsync_mailbox_update(&brain_boxes[1].box, &dest_boxes[2]);
+	test_dsync_mailbox_update(&brain_boxes[2].box, &dest_boxes[3]);
+	test_dsync_mailbox_update(&brain_boxes[3].box, &src_boxes[4]);
+	test_dsync_mailbox_update(&brain_boxes[4].box, &src_boxes[5]);
+	test_dsync_mailbox_update(&brain_boxes[5].box, &src_boxes[6]);
+	test_dsync_mailbox_update(&brain_boxes[6].box, &dest_boxes[6]);
+
+	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
+	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
+
+	dsync_brain_deinit(&brain);
+	dsync_worker_deinit(&src_worker);
+	dsync_worker_deinit(&dest_worker);
+
+	test_end();
+}
+
+static void test_dsync_brain_full(void)
+{
+	static struct dsync_mailbox boxes[] = {
+		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 2352, 0, ARRAY_INIT },
+		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
+	};
+	struct dsync_brain *brain;
+	struct dsync_worker *src_worker, *dest_worker;
+	struct test_dsync_box_event box_event;
+	const struct dsync_brain_mailbox *brain_boxes;
+	unsigned int count;
+
+	test_begin("dsync brain full");
+
+	mailboxes_set_guids(boxes);
+
+	src_worker = dsync_worker_init_test();
+	dest_worker = dsync_worker_init_test();
+	src_test_worker = (struct test_dsync_worker *)src_worker;
+	dest_test_worker = (struct test_dsync_worker *)dest_worker;
+
+	brain = dsync_brain_init(src_worker, dest_worker, NULL,
+				 DSYNC_BRAIN_FLAG_FULL_SYNC |
+				 DSYNC_BRAIN_FLAG_LOCAL);
+	dsync_brain_sync(brain);
+
+	/* have brain read the mailboxes */
+	mailboxes_send_to_worker(src_test_worker, boxes);
+	mailboxes_send_to_worker(dest_test_worker, boxes);
+
+	subscriptions_send_to_worker(src_test_worker);
+	subscriptions_send_to_worker(dest_test_worker);
+
+	test_assert(brain->state == DSYNC_STATE_SYNC_MSGS);
+
+	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
+	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
+
+	/* check mailbox updates */
+	brain->state++;
+	dsync_brain_sync(brain);
+	test_assert(brain->state == DSYNC_STATE_SYNC_UPDATE_MAILBOXES);
+	dsync_brain_sync(brain);
+	test_assert(brain->state == DSYNC_STATE_SYNC_END);
+
+	brain_boxes = array_get(&brain->mailbox_sync->mailboxes, &count);
+	test_assert(count == 1);
+	test_assert(dsync_mailboxes_equal(brain_boxes[0].src, &boxes[0]));
+	test_assert(dsync_mailboxes_equal(brain_boxes[0].dest, &boxes[0]));
+	test_dsync_mailbox_update(&brain_boxes[0].box, &boxes[0]);
+
+	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
+	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
+
+	dsync_brain_deinit(&brain);
+	dsync_worker_deinit(&src_worker);
+	dsync_worker_deinit(&dest_worker);
+
+	test_end();
+}
+
+int main(void)
+{
+	static void (*test_functions[])(void) = {
+		test_dsync_brain,
+		test_dsync_brain_full,
+		NULL
+	};
+	return test_run(test_functions);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-common.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,80 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hex-binary.h"
+#include "sha1.h"
+#include "dsync-data.h"
+#include "test-dsync-common.h"
+
+const guid_128_t test_mailbox_guid1 = {
+	0x12, 0x34, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+	0x21, 0x43, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe
+};
+
+const guid_128_t test_mailbox_guid2 = {
+	0xa3, 0xbd, 0x78, 0x24, 0xde, 0xfe, 0x08, 0xf7,
+	0xac, 0xc7, 0xca, 0x8c, 0xe7, 0x39, 0xdb, 0xca
+};
+
+bool dsync_messages_equal(const struct dsync_message *m1,
+			  const struct dsync_message *m2)
+{
+	unsigned int i;
+
+	if (strcmp(m1->guid, m2->guid) != 0 ||
+	    m1->uid != m2->uid || m1->flags != m2->flags ||
+	    m1->modseq != m2->modseq || m1->save_date != m2->save_date)
+		return FALSE;
+
+	if (m1->keywords == m2->keywords)
+		return TRUE;
+	if (m1->keywords == NULL)
+		return m2->keywords == NULL || m2->keywords[0] == NULL;
+	if (m2->keywords == NULL)
+		return m1->keywords[0] == NULL;
+
+	for (i = 0; m1->keywords[i] != NULL && m2->keywords[i] != NULL; i++) {
+		if (strcasecmp(m1->keywords[i], m2->keywords[i]) != 0)
+			return FALSE;
+	}
+	return m1->keywords[i] == NULL && m2->keywords[i] == NULL;
+}
+
+bool dsync_mailboxes_equal(const struct dsync_mailbox *box1,
+			   const struct dsync_mailbox *box2)
+{
+	const struct mailbox_cache_field *f1 = NULL, *f2 = NULL;
+	unsigned int i, f1_count = 0, f2_count = 0;
+
+	if (strcmp(box1->name, box2->name) != 0 ||
+	    box1->name_sep != box2->name_sep ||
+	    memcmp(box1->mailbox_guid.guid, box2->mailbox_guid.guid,
+		   sizeof(box1->mailbox_guid.guid)) != 0 ||
+	    box1->uid_validity != box2->uid_validity ||
+	    box1->uid_next != box2->uid_next ||
+	    box1->highest_modseq != box2->highest_modseq)
+		return FALSE;
+
+	if (array_is_created(&box1->cache_fields))
+		f1 = array_get(&box1->cache_fields, &f1_count);
+	if (array_is_created(&box2->cache_fields))
+		f2 = array_get(&box2->cache_fields, &f2_count);
+	if (f1_count != f2_count)
+		return FALSE;
+	for (i = 0; i < f1_count; i++) {
+		if (strcmp(f1[i].name, f2[i].name) != 0 ||
+		    f1[i].decision != f2[i].decision ||
+		    f1[i].last_used != f2[i].last_used)
+			return FALSE;
+	}
+	return TRUE;
+}
+
+void mail_generate_guid_128_hash(const char *guid, guid_128_t guid_128_r)
+{
+	unsigned char sha1_sum[SHA1_RESULTLEN];
+
+	sha1_get_digest(guid, strlen(guid), sha1_sum);
+	memcpy(guid_128_r, sha1_sum, GUID_128_SIZE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-common.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,18 @@
+#ifndef TEST_DSYNC_COMMON_H
+#define TEST_DSYNC_COMMON_H
+
+#include "test-common.h"
+#include "dsync-data.h"
+
+#define TEST_MAILBOX_GUID1 "1234456789abcdef2143547698badcfe"
+#define TEST_MAILBOX_GUID2 "a3bd7824defe08f7acc7ca8ce739dbca"
+
+extern const guid_128_t test_mailbox_guid1;
+extern const guid_128_t test_mailbox_guid2;
+
+bool dsync_messages_equal(const struct dsync_message *m1,
+			  const struct dsync_message *m2);
+bool dsync_mailboxes_equal(const struct dsync_mailbox *box1,
+			   const struct dsync_mailbox *box2);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-proxy-server-cmd.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,482 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "istream.h"
+#include "ostream.h"
+#include "test-common.h"
+#include "dsync-proxy-server.h"
+#include "test-dsync-worker.h"
+#include "test-dsync-common.h"
+
+#define ALL_MAIL_FLAGS "\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent"
+
+struct master_service *master_service;
+static string_t *out;
+static struct dsync_proxy_server *server;
+static struct test_dsync_worker *test_worker;
+static struct dsync_proxy_server_command *cur_cmd;
+static const char *cur_cmd_args[20];
+
+static void out_clear(void)
+{
+	o_stream_seek(server->output, 0);
+	str_truncate(out, 0);
+}
+
+static int run_more(void)
+{
+	int ret;
+
+	ret = cur_cmd->func(server, cur_cmd_args);
+	if (ret == 0)
+		return 0;
+
+	cur_cmd = NULL;
+	return ret;
+}
+
+static int ATTR_SENTINEL
+run_cmd(const char *cmd_name, ...)
+{
+	va_list va;
+	const char *str;
+	unsigned int i = 0;
+
+	i_assert(cur_cmd == NULL);
+
+	va_start(va, cmd_name);
+	while ((str = va_arg(va, const char *)) != NULL) {
+		i_assert(i < N_ELEMENTS(cur_cmd_args)+1);
+		cur_cmd_args[i++] = str;
+	}
+	cur_cmd_args[i] = NULL;
+	va_end(va);
+
+	cur_cmd = dsync_proxy_server_command_find(cmd_name);
+	i_assert(cur_cmd != NULL);
+	return run_more();
+}
+
+static void test_dsync_proxy_box_list(void)
+{
+	struct dsync_mailbox box;
+
+	test_begin("proxy server box list");
+
+	test_assert(run_cmd("BOX-LIST", NULL) == 0);
+
+	/* \noselect mailbox */
+	memset(&box, 0, sizeof(box));
+	box.name = "\t\001\r\nname\t\001\n\r";
+	box.name_sep = '/';
+	box.last_change = 992;
+	box.flags = DSYNC_MAILBOX_FLAG_NOSELECT;
+	test_worker->box_iter.next_box = &box;
+	test_assert(run_more() == 0);
+	test_assert(strcmp(str_c(out), t_strconcat(str_tabescape(box.name),
+		"\t/\t992\t1\n", NULL)) == 0);
+	out_clear();
+
+	/* selectable mailbox */
+	memset(&box, 0, sizeof(box));
+	box.name = "foo/bar";
+	box.name_sep = '/';
+	memcpy(box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE);
+	box.uid_validity = 4275878552;
+	box.uid_next = 4023233417;
+	box.message_count = 4525;
+	box.highest_modseq = 18080787909545915012ULL;
+	box.first_recent_uid = 353;
+	test_worker->box_iter.next_box = &box;
+
+	test_assert(run_more() == 0);
+
+	test_assert(strcmp(str_c(out), "foo/bar\t/\t0\t0\t"
+			   TEST_MAILBOX_GUID1"\t"
+			   "4275878552\t"
+			   "4023233417\t"
+			   "4525\t"
+			   "18080787909545915012\t"
+			   "353\n") == 0);
+	out_clear();
+
+	/* last mailbox */
+	test_worker->box_iter.last = TRUE;
+	test_assert(run_more() == 1);
+	test_assert(strcmp(str_c(out), "+\n") == 0);
+	out_clear();
+
+	test_end();
+}
+
+static void test_dsync_proxy_subs_list(void)
+{
+	const char *name;
+	struct dsync_worker_subscription subs;
+	struct dsync_worker_unsubscription unsubs;
+
+	test_begin("proxy server subs list");
+
+	test_assert(run_cmd("SUBS-LIST", NULL) == 0);
+
+	/* subscription */
+	name = "\t\001\r\nname\t\001\n\r";
+	subs.vname = name;
+	subs.storage_name = "\tstorage_name\n";
+	subs.last_change = 1234567890;
+	subs.ns_prefix = "\t\001\r\nprefix\t\001\n\r";
+	test_worker->subs_iter.next_subscription = &subs;
+	test_assert(run_more() == 0);
+	test_assert(strcmp(str_c(out), t_strconcat(
+		str_tabescape(name), "\t",
+		str_tabescape(subs.storage_name), "\t",
+		str_tabescape(subs.ns_prefix),
+		"\t1234567890\n", NULL)) == 0);
+	out_clear();
+
+	test_worker->subs_iter.last_subs = TRUE;
+	test_assert(run_more() == 0);
+	test_assert(strcmp(str_c(out), "+\n") == 0);
+	out_clear();
+
+	/* unsubscription */
+	memcpy(unsubs.name_sha1.guid, test_mailbox_guid1,
+	       sizeof(unsubs.name_sha1.guid));
+	unsubs.ns_prefix = "\t\001\r\nprefix2\t\001\n\r";
+	unsubs.last_change = 987654321;
+	test_worker->subs_iter.next_unsubscription = &unsubs;
+	test_assert(run_more() == 0);
+	test_assert(strcmp(str_c(out), t_strconcat(TEST_MAILBOX_GUID1, "\t",
+		str_tabescape(unsubs.ns_prefix), "\t987654321\n", NULL)) == 0);
+	out_clear();
+
+	test_worker->subs_iter.last_unsubs = TRUE;
+	test_assert(run_more() == 1);
+	test_assert(strcmp(str_c(out), "+\n") == 0);
+	out_clear();
+
+	test_end();
+}
+
+static void test_dsync_proxy_msg_list(void)
+{
+	static const char *test_keywords[] = {
+		"kw1", "kw2", NULL
+	};
+	struct dsync_message msg;
+	struct test_dsync_worker_msg test_msg;
+
+	test_begin("proxy server msg list");
+
+	test_assert(run_cmd("MSG-LIST", TEST_MAILBOX_GUID1, TEST_MAILBOX_GUID2, NULL) == 0);
+
+	memset(&msg, 0, sizeof(msg));
+	msg.guid = "\t\001\r\nguid\t\001\n\r";
+	msg.uid = 123;
+	msg.modseq = 98765432101234;
+	msg.save_date = 1234567890;
+
+	/* no flags */
+	test_msg.msg = msg;
+	test_msg.mailbox_idx = 98;
+	array_append(&test_worker->msg_iter.msgs, &test_msg, 1);
+	test_assert(run_more() == 0);
+	test_assert(strcmp(str_c(out), t_strconcat(
+		"98\t", str_tabescape(msg.guid),
+		"\t123\t98765432101234\t\t1234567890\n", NULL)) == 0);
+	out_clear();
+
+	/* all flags, some keywords */
+	msg.modseq = 1;
+	msg.save_date = 2;
+	msg.guid = "guid";
+	msg.flags = MAIL_FLAGS_MASK;
+	msg.keywords = test_keywords;
+	test_msg.msg = msg;
+	test_msg.mailbox_idx = 76;
+	array_append(&test_worker->msg_iter.msgs, &test_msg, 1);
+	test_assert(run_more() == 0);
+	test_assert(strcmp(str_c(out), "76\tguid\t123\t1\t"
+			   ALL_MAIL_FLAGS" kw1 kw2\t2\n") == 0);
+	out_clear();
+
+	/* last message */
+	test_worker->msg_iter.last = TRUE;
+	test_assert(run_more() == 1);
+	test_assert(strcmp(str_c(out), "+\n") == 0);
+	out_clear();
+
+	test_end();
+}
+
+static void test_dsync_proxy_box_create(void)
+{
+	struct test_dsync_box_event event;
+
+	test_begin("proxy server box create");
+
+	test_assert(run_cmd("BOX-CREATE", "noselect", "/",
+			    "553", "1", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_CREATE);
+	test_assert(strcmp(event.box.name, "noselect") == 0);
+	test_assert(event.box.name_sep == '/');
+	test_assert(event.box.last_change == 553);
+	test_assert(event.box.flags == DSYNC_MAILBOX_FLAG_NOSELECT);
+	test_assert(event.box.uid_validity == 0);
+
+	test_assert(run_cmd("BOX-CREATE", "selectable", "?",
+			    "61", "2", TEST_MAILBOX_GUID2, "1234567890", "9876",
+			    "4610", "28427847284728", "853", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_CREATE);
+	test_assert(strcmp(event.box.name, "selectable") == 0);
+	test_assert(event.box.name_sep == '?');
+	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
+	test_assert(event.box.flags == 2);
+	test_assert(event.box.uid_validity == 1234567890);
+	test_assert(event.box.uid_next == 9876);
+	test_assert(event.box.message_count == 4610);
+	test_assert(event.box.highest_modseq == 28427847284728);
+	test_assert(event.box.first_recent_uid == 853);
+	test_assert(event.box.last_change == 61);
+
+	test_end();
+}
+
+static void test_dsync_proxy_box_delete(void)
+{
+	struct test_dsync_box_event event;
+
+	test_begin("proxy server box delete");
+
+	test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID1, "4351", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_DELETE);
+	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
+	test_assert(event.box.last_change == 4351);
+
+	test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID2, "653", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_DELETE);
+	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
+	test_assert(event.box.last_change == 653);
+
+	test_end();
+}
+
+static void test_dsync_proxy_box_rename(void)
+{
+	struct test_dsync_box_event event;
+
+	test_begin("proxy server box rename");
+
+	test_assert(run_cmd("BOX-RENAME", TEST_MAILBOX_GUID1, "name\t1", "/", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_RENAME);
+	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
+	test_assert(strcmp(event.box.name, "name\t1") == 0);
+	test_assert(event.box.name_sep == '/');
+
+	test_assert(run_cmd("BOX-RENAME", TEST_MAILBOX_GUID2, "", "?", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_RENAME);
+	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
+	test_assert(strcmp(event.box.name, "") == 0);
+	test_assert(event.box.name_sep == '?');
+
+	test_end();
+}
+
+static void test_dsync_proxy_box_update(void)
+{
+	struct test_dsync_box_event event;
+
+	test_begin("proxy server box update");
+
+	test_assert(run_cmd("BOX-UPDATE", "updated", "/",
+			    "53", "2", TEST_MAILBOX_GUID1, "34343", "22",
+			    "58293", "2238427847284728", "2482", NULL) == 1);
+	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
+	test_assert(event.type == LAST_BOX_TYPE_UPDATE);
+	test_assert(strcmp(event.box.name, "updated") == 0);
+	test_assert(event.box.name_sep == '/');
+	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
+	test_assert(event.box.flags == DSYNC_MAILBOX_FLAG_DELETED_MAILBOX);
+	test_assert(event.box.uid_validity == 34343);
+	test_assert(event.box.uid_next == 22);
+	test_assert(event.box.message_count == 58293);
+	test_assert(event.box.highest_modseq == 2238427847284728);
+	test_assert(event.box.first_recent_uid == 2482);
+	test_assert(event.box.last_change == 53);
+
+	test_end();
+}
+
+static void test_dsync_proxy_box_select(void)
+{
+	test_begin("proxy server box select");
+
+	test_assert(run_cmd("BOX-SELECT", TEST_MAILBOX_GUID1, NULL) == 1);
+	test_assert(memcmp(test_worker->selected_mailbox.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
+
+	test_assert(run_cmd("BOX-SELECT", TEST_MAILBOX_GUID2, NULL) == 1);
+	test_assert(memcmp(test_worker->selected_mailbox.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
+
+	test_end();
+}
+
+static void test_dsync_proxy_msg_update(void)
+{
+	struct test_dsync_msg_event event;
+
+	test_begin("proxy server msg update");
+
+	test_assert(run_cmd("MSG-UPDATE", "123", "4782782842924",
+			    "kw1 "ALL_MAIL_FLAGS" kw2", NULL) == 1);
+	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
+	test_assert(event.type == LAST_MSG_TYPE_UPDATE);
+	test_assert(event.msg.uid == 123);
+	test_assert(event.msg.modseq == 4782782842924);
+	test_assert(event.msg.flags == MAIL_FLAGS_MASK);
+	test_assert(strcmp(event.msg.keywords[0], "kw1") == 0);
+	test_assert(strcmp(event.msg.keywords[1], "kw2") == 0);
+	test_assert(event.msg.keywords[2] == NULL);
+
+	test_end();
+}
+
+static void test_dsync_proxy_msg_uid_change(void)
+{
+	struct test_dsync_msg_event event;
+
+	test_begin("proxy server msg uid change");
+
+	test_assert(run_cmd("MSG-UID-CHANGE", "454", "995", NULL) == 1);
+	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
+	test_assert(event.type == LAST_MSG_TYPE_UPDATE_UID);
+	test_assert(event.msg.uid == 454);
+	test_assert(event.msg.modseq == 995);
+
+	test_end();
+}
+
+static void test_dsync_proxy_msg_expunge(void)
+{
+	struct test_dsync_msg_event event;
+
+	test_begin("proxy server msg expunge");
+
+	test_assert(run_cmd("MSG-EXPUNGE", "8585", NULL) == 1);
+	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
+	test_assert(event.type == LAST_MSG_TYPE_EXPUNGE);
+	test_assert(event.msg.uid == 8585);
+
+	test_end();
+}
+
+static void test_dsync_proxy_msg_copy(void)
+{
+	struct test_dsync_msg_event msg_event;
+
+	test_begin("proxy server msg copy");
+
+	test_assert(run_cmd("MSG-COPY", TEST_MAILBOX_GUID1, "5454",
+			    "copyguid", "5678", "74782482882924", "\\Seen foo \\Draft",
+			    "8294284", NULL) == 1);
+	test_assert(test_dsync_worker_next_msg_event(test_worker, &msg_event));
+	test_assert(msg_event.type == LAST_MSG_TYPE_COPY);
+	test_assert(memcmp(msg_event.copy_src_mailbox.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
+	test_assert(msg_event.copy_src_uid == 5454);
+	test_assert(strcmp(msg_event.msg.guid, "copyguid") == 0);
+	test_assert(msg_event.msg.uid == 5678);
+	test_assert(msg_event.msg.modseq == 74782482882924);
+	test_assert(msg_event.msg.flags == (MAIL_SEEN | MAIL_DRAFT));
+	test_assert(strcmp(msg_event.msg.keywords[0], "foo") == 0);
+	test_assert(msg_event.msg.keywords[1] == NULL);
+	test_assert(msg_event.msg.save_date == 8294284);
+
+	test_end();
+}
+
+static void test_dsync_proxy_msg_save(void)
+{
+	static const char *input = "..dotty\n..behavior\nfrom you\n.\nstop";
+	struct test_dsync_msg_event event;
+	const unsigned char *data;
+	size_t size;
+
+	test_begin("proxy server msg save");
+
+	server->input = i_stream_create_from_data(input, strlen(input));
+
+	test_assert(run_cmd("MSG-SAVE", "28492428", "pop3uidl",
+			    "saveguid", "874", "33982482882924", "\\Flagged bar \\Answered",
+			    "8294284", NULL) == 1);
+	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
+	test_assert(event.type == LAST_MSG_TYPE_SAVE);
+	test_assert(event.save_data.received_date == 28492428);
+	test_assert(strcmp(event.save_data.pop3_uidl, "pop3uidl") == 0);
+	test_assert(strcmp(event.save_body, ".dotty\n.behavior\nfrom you") == 0);
+
+	test_assert(strcmp(event.msg.guid, "saveguid") == 0);
+	test_assert(event.msg.uid == 874);
+	test_assert(event.msg.modseq == 33982482882924);
+	test_assert(event.msg.flags == (MAIL_FLAGGED | MAIL_ANSWERED));
+	test_assert(strcmp(event.msg.keywords[0], "bar") == 0);
+	test_assert(event.msg.keywords[1] == NULL);
+	test_assert(event.msg.save_date == 8294284);
+
+	data = i_stream_get_data(server->input, &size);
+	test_assert(size == 4 && memcmp(data, "stop", 4) == 0);
+	i_stream_destroy(&server->input);
+
+	test_end();
+}
+
+static struct dsync_proxy_server *
+dsync_proxy_server_init_test(buffer_t *outbuf)
+{
+	struct dsync_proxy_server *server;
+
+	server = i_new(struct dsync_proxy_server, 1);
+	server->worker = dsync_worker_init_test();
+	server->fd_in = 0;
+	server->fd_out = 0;
+
+	server->cmd_pool = pool_alloconly_create("worker server cmd", 1024);
+	server->output = o_stream_create_buffer(outbuf);
+	return server;
+}
+
+int main(void)
+{
+	static void (*test_functions[])(void) = {
+		test_dsync_proxy_box_list,
+		test_dsync_proxy_subs_list,
+		test_dsync_proxy_msg_list,
+		test_dsync_proxy_box_create,
+		test_dsync_proxy_box_delete,
+		test_dsync_proxy_box_rename,
+		test_dsync_proxy_box_update,
+		test_dsync_proxy_box_select,
+		test_dsync_proxy_msg_update,
+		test_dsync_proxy_msg_uid_change,
+		test_dsync_proxy_msg_expunge,
+		test_dsync_proxy_msg_copy,
+		test_dsync_proxy_msg_save,
+		NULL
+	};
+
+	test_init();
+
+	out = buffer_create_dynamic(default_pool, 1024);
+	server = dsync_proxy_server_init_test(out);
+	test_worker = (struct test_dsync_worker *)server->worker;
+
+	test_run_funcs(test_functions);
+	return test_deinit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-proxy.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,184 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mail-cache.h"
+#include "dsync-proxy.h"
+#include "test-dsync-common.h"
+#include "test-common.h"
+
+static void test_dsync_proxy_msg(void)
+{
+	static const char *test_keywords[] = {
+		"kw1", "kw2", NULL
+	};
+	string_t *str;
+	struct dsync_message msg_in, msg_out;
+	const char *error;
+	pool_t pool;
+
+	memset(&msg_in, 0, sizeof(msg_in));
+	memset(&msg_out, 0, sizeof(msg_out));
+
+	pool = pool_alloconly_create("msg pool", 1024);
+	str = t_str_new(256);
+	msg_in.guid = "\t\001\r\nguid\t\001\n\r";
+	msg_in.uid = (uint32_t)-1;
+	msg_in.modseq = (uint64_t)-1;
+	msg_in.save_date = (1U << 31)-1;
+
+	test_begin("dsync proxy msg");
+
+	/* no flags */
+	dsync_proxy_msg_export(str, &msg_in);
+	test_assert(dsync_proxy_msg_import(pool, str_c(str),
+					   &msg_out, &error) == 0);
+	test_assert(dsync_messages_equal(&msg_in, &msg_out));
+
+	/* expunged flag */
+	msg_in.flags = DSYNC_MAIL_FLAG_EXPUNGED;
+	str_truncate(str, 0);
+	dsync_proxy_msg_export(str, &msg_in);
+	test_assert(dsync_proxy_msg_import(pool, str_c(str),
+					   &msg_out, &error) == 0);
+	test_assert(dsync_messages_equal(&msg_in, &msg_out));
+
+	/* expunged flag and another flag */
+	msg_in.flags = DSYNC_MAIL_FLAG_EXPUNGED | MAIL_DRAFT;
+	str_truncate(str, 0);
+	dsync_proxy_msg_export(str, &msg_in);
+	test_assert(dsync_proxy_msg_import(pool, str_c(str),
+					   &msg_out, &error) == 0);
+	test_assert(dsync_messages_equal(&msg_in, &msg_out));
+
+	/* all flags, some keywords */
+	msg_in.flags = MAIL_FLAGS_MASK;
+	msg_in.keywords = test_keywords;
+	str_truncate(str, 0);
+	dsync_proxy_msg_export(str, &msg_in);
+	test_assert(dsync_proxy_msg_import(pool, str_c(str),
+					   &msg_out, &error) == 0);
+	test_assert(dsync_messages_equal(&msg_in, &msg_out));
+
+	/* errors */
+	test_assert(dsync_proxy_msg_import(pool, "0", &msg_out, &error) < 0);
+	test_assert(dsync_proxy_msg_import(pool, "0\t0", &msg_out, &error) < 0);
+	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0", &msg_out, &error) < 0);
+	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0\t0", &msg_out, &error) < 0);
+	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0\t\\\t0", &msg_out, &error) < 0);
+	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0\t\\seen foo \\foo\t0", &msg_out, &error) < 0);
+
+	/* flags */
+	test_assert(dsync_proxy_msg_parse_flags(pool, "\\seen \\draft", &msg_out) == 0);
+	test_assert(msg_out.flags == (MAIL_SEEN | MAIL_DRAFT));
+	test_assert(dsync_proxy_msg_parse_flags(pool, "\\answered \\flagged", &msg_out) == 0);
+	test_assert(msg_out.flags == (MAIL_ANSWERED | MAIL_FLAGGED));
+	test_assert(dsync_proxy_msg_parse_flags(pool, "\\deleted \\recent", &msg_out) == 0);
+	test_assert(msg_out.flags == (MAIL_DELETED | MAIL_RECENT));
+	test_assert(dsync_proxy_msg_parse_flags(pool, "\\draft draft \\seen", &msg_out) == 0);
+	test_assert(msg_out.flags == (MAIL_DRAFT | MAIL_SEEN));
+	test_assert(strcasecmp(msg_out.keywords[0], "draft") == 0 && msg_out.keywords[1] == NULL);
+
+	test_end();
+	pool_unref(&pool);
+}
+
+static void test_dsync_proxy_mailbox(void)
+{
+	static struct mailbox_cache_field cache1 =
+		{ "cache1", MAIL_CACHE_DECISION_NO, 1234 };
+	static struct mailbox_cache_field cache2 =
+		{ "cache2", MAIL_CACHE_DECISION_TEMP | MAIL_CACHE_DECISION_FORCED, 0 };
+	string_t *str;
+	struct dsync_mailbox box_in, box_out;
+	const char *error;
+	pool_t pool;
+
+	memset(&box_in, 0, sizeof(box_in));
+	memset(&box_out, 0, sizeof(box_out));
+
+	pool = pool_alloconly_create("mailbox pool", 1024);
+	str = t_str_new(256);
+
+	test_begin("dsync proxy mailbox");
+
+	/* test \noselect mailbox */
+	box_in.name = "\t\001\r\nname\t\001\n\r";
+	box_in.name_sep = '/';
+	box_in.flags = DSYNC_MAILBOX_FLAG_NOSELECT;
+	dsync_proxy_mailbox_export(str, &box_in);
+	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
+					       &box_out, &error) == 0);
+	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
+
+	/* real mailbox */
+	i_assert(sizeof(box_in.mailbox_guid.guid) == sizeof(test_mailbox_guid1));
+	memcpy(box_in.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE);
+	box_in.flags = 24242 & ~DSYNC_MAILBOX_FLAG_NOSELECT;
+	box_in.uid_validity = 0xf74d921b;
+	box_in.uid_next = 73529472;
+	box_in.highest_modseq = 0x123456789abcdef0ULL;
+
+	str_truncate(str, 0);
+	dsync_proxy_mailbox_export(str, &box_in);
+	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
+					       &box_out, &error) == 0);
+	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
+
+	/* limits */
+	box_in.uid_next = (uint32_t)-1;
+	box_in.highest_modseq = (uint64_t)-1;
+
+	str_truncate(str, 0);
+	dsync_proxy_mailbox_export(str, &box_in);
+	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
+					       &box_out, &error) == 0);
+	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
+
+	/* mailbox with cache fields */
+	t_array_init(&box_in.cache_fields, 10);
+	array_append(&box_in.cache_fields, &cache1, 1);
+	array_append(&box_in.cache_fields, &cache2, 1);
+
+	str_truncate(str, 0);
+	dsync_proxy_mailbox_export(str, &box_in);
+	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
+					       &box_out, &error) == 0);
+	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
+
+	test_end();
+	pool_unref(&pool);
+}
+
+static void test_dsync_proxy_guid(void)
+{
+	mailbox_guid_t guid_in, guid_out;
+	string_t *str;
+
+	test_begin("dsync proxy mailbox guid");
+
+	str = t_str_new(128);
+	memcpy(guid_in.guid, test_mailbox_guid1, sizeof(guid_in.guid));
+	dsync_proxy_mailbox_guid_export(str, &guid_in);
+	test_assert(dsync_proxy_mailbox_guid_import(str_c(str), &guid_out) == 0);
+	test_assert(memcmp(guid_in.guid, guid_out.guid, sizeof(guid_in.guid)) == 0);
+
+	test_assert(dsync_proxy_mailbox_guid_import("12345678901234567890123456789012", &guid_out) == 0);
+	test_assert(dsync_proxy_mailbox_guid_import("1234567890123456789012345678901", &guid_out) < 0);
+	test_assert(dsync_proxy_mailbox_guid_import("1234567890123456789012345678901g", &guid_out) < 0);
+	test_assert(dsync_proxy_mailbox_guid_import("", &guid_out) < 0);
+
+	test_end();
+}
+
+int main(void)
+{
+	static void (*test_functions[])(void) = {
+		test_dsync_proxy_msg,
+		test_dsync_proxy_mailbox,
+		test_dsync_proxy_guid,
+		NULL
+	};
+	return test_run(test_functions);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-worker.c	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,481 @@
+/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "istream.h"
+#include "test-dsync-worker.h"
+
+extern struct dsync_worker_vfuncs test_dsync_worker;
+
+struct dsync_worker *dsync_worker_init_test(void)
+{
+	struct test_dsync_worker *worker;
+
+	worker = i_new(struct test_dsync_worker, 1);
+	worker->worker.v = test_dsync_worker;
+	worker->tmp_pool = pool_alloconly_create("test worker", 256);
+	i_array_init(&worker->box_events, 64);
+	i_array_init(&worker->msg_events, 64);
+	i_array_init(&worker->results, 64);
+	worker->body_stream = i_stream_create_from_data("hdr\n\nbody", 9);
+	return &worker->worker;
+}
+
+static void test_worker_deinit(struct dsync_worker *_worker)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+
+	pool_unref(&worker->tmp_pool);
+	array_free(&worker->box_events);
+	array_free(&worker->msg_events);
+	array_free(&worker->results);
+	i_stream_unref(&worker->body_stream);
+	i_free(worker);
+}
+
+static bool test_worker_is_output_full(struct dsync_worker *worker ATTR_UNUSED)
+{
+	return FALSE;
+}
+
+static int test_worker_output_flush(struct dsync_worker *worker ATTR_UNUSED)
+{
+	return 1;
+}
+
+static struct dsync_worker_mailbox_iter *
+test_worker_mailbox_iter_init(struct dsync_worker *_worker)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+
+	i_assert(worker->box_iter.iter.worker == NULL);
+
+	worker->box_iter.iter.worker = _worker;
+	return &worker->box_iter.iter;
+}
+
+static int
+test_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *_iter,
+			      struct dsync_mailbox *dsync_box_r)
+{
+	struct test_dsync_worker_mailbox_iter *iter =
+		(struct test_dsync_worker_mailbox_iter *)_iter;
+
+	if (iter->next_box == NULL)
+		return iter->last ? -1 : 0;
+
+	*dsync_box_r = *iter->next_box;
+	iter->next_box = NULL;
+	return 1;
+}
+
+static int
+test_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter *iter)
+{
+	struct test_dsync_worker *worker =
+		(struct test_dsync_worker *)iter->worker;
+
+	memset(&worker->box_iter, 0, sizeof(worker->box_iter));
+	return 0;
+}
+
+static struct dsync_worker_subs_iter *
+test_worker_subs_iter_init(struct dsync_worker *_worker)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+
+	i_assert(worker->subs_iter.iter.worker == NULL);
+
+	worker->subs_iter.iter.worker = _worker;
+	return &worker->subs_iter.iter;
+}
+
+static int
+test_worker_subs_iter_next(struct dsync_worker_subs_iter *_iter,
+			   struct dsync_worker_subscription *rec_r)
+{
+	struct test_dsync_worker_subs_iter *iter =
+		(struct test_dsync_worker_subs_iter *)_iter;
+
+	if (iter->next_subscription == NULL)
+		return iter->last_subs ? -1 : 0;
+
+	*rec_r = *iter->next_subscription;
+	iter->next_subscription = NULL;
+	return 1;
+}
+
+static int
+test_worker_subs_iter_next_un(struct dsync_worker_subs_iter *_iter,
+			      struct dsync_worker_unsubscription *rec_r)
+{
+	struct test_dsync_worker_subs_iter *iter =
+		(struct test_dsync_worker_subs_iter *)_iter;
+
+	if (iter->next_unsubscription == NULL)
+		return iter->last_unsubs ? -1 : 0;
+
+	*rec_r = *iter->next_unsubscription;
+	iter->next_unsubscription = NULL;
+	return 1;
+}
+
+static int
+test_worker_subs_iter_deinit(struct dsync_worker_subs_iter *iter)
+{
+	struct test_dsync_worker *worker =
+		(struct test_dsync_worker *)iter->worker;
+
+	memset(&worker->subs_iter, 0, sizeof(worker->subs_iter));
+	return 0;
+}
+
+static struct dsync_worker_msg_iter *
+test_worker_msg_iter_init(struct dsync_worker *_worker,
+			  const mailbox_guid_t mailboxes[],
+			  unsigned int mailbox_count)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+
+	i_assert(worker->msg_iter.iter.worker == NULL);
+
+	worker->msg_iter_mailboxes =
+		p_new(worker->tmp_pool, mailbox_guid_t, mailbox_count);
+	memcpy(worker->msg_iter_mailboxes, mailboxes,
+	       sizeof(mailboxes[0]) * mailbox_count);
+	worker->msg_iter_mailbox_count = mailbox_count;
+	i_array_init(&worker->msg_iter.msgs, 64);
+
+	worker->msg_iter.iter.worker = _worker;
+	return &worker->msg_iter.iter;
+}
+
+static int
+test_worker_msg_iter_next(struct dsync_worker_msg_iter *_iter,
+			  unsigned int *mailbox_idx_r,
+			  struct dsync_message *msg_r)
+{
+	struct test_dsync_worker_msg_iter *iter =
+		(struct test_dsync_worker_msg_iter *)_iter;
+	const struct test_dsync_worker_msg *msg;
+
+	if (iter->idx == array_count(&iter->msgs))
+		return iter->last ? -1 : 0;
+
+	msg = array_idx(&iter->msgs, iter->idx++);
+	*msg_r = msg->msg;
+	*mailbox_idx_r = msg->mailbox_idx;
+	return 1;
+}
+
+static int
+test_worker_msg_iter_deinit(struct dsync_worker_msg_iter *iter)
+{
+	struct test_dsync_worker *worker =
+		(struct test_dsync_worker *)iter->worker;
+
+	array_free(&worker->msg_iter.msgs);
+	memset(&worker->msg_iter, 0, sizeof(worker->msg_iter));
+	return 0;
+}
+
+static void
+test_worker_set_last_box(struct dsync_worker *_worker,
+			 const struct dsync_mailbox *dsync_box,
+			 enum test_dsync_last_box_type type)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct test_dsync_box_event event;
+
+	event.type = type;
+
+	event.box = *dsync_box;
+	event.box.name = p_strdup(worker->tmp_pool, dsync_box->name);
+	array_append(&worker->box_events, &event, 1);
+}
+
+bool test_dsync_worker_next_box_event(struct test_dsync_worker *worker,
+				      struct test_dsync_box_event *event_r)
+{
+	const struct test_dsync_box_event *events;
+	unsigned int count;
+
+	events = array_get(&worker->box_events, &count);
+	if (count == 0)
+		return FALSE;
+
+	*event_r = events[0];
+	array_delete(&worker->box_events, 0, 1);
+	return TRUE;
+}
+
+static void
+test_worker_set_subscribed(struct dsync_worker *_worker,
+			   const char *name, time_t last_change, bool set)
+{
+	struct dsync_mailbox dsync_box;
+
+	memset(&dsync_box, 0, sizeof(dsync_box));
+	dsync_box.name = name;
+	dsync_box.last_change = last_change;
+	test_worker_set_last_box(_worker, &dsync_box,
+				 set ? LAST_BOX_TYPE_SUBSCRIBE :
+				 LAST_BOX_TYPE_UNSUBSCRIBE);
+}
+
+static void
+test_worker_create_mailbox(struct dsync_worker *_worker,
+			   const struct dsync_mailbox *dsync_box)
+{
+	test_worker_set_last_box(_worker, dsync_box, LAST_BOX_TYPE_CREATE);
+}
+
+static void
+test_worker_delete_mailbox(struct dsync_worker *_worker,
+			   const struct dsync_mailbox *dsync_box)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct test_dsync_box_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type = LAST_BOX_TYPE_DELETE;
+
+	event.box = *dsync_box;
+	array_append(&worker->box_events, &event, 1);
+}
+
+static void
+test_worker_delete_dir(struct dsync_worker *_worker,
+		       const struct dsync_mailbox *dsync_box)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct test_dsync_box_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type = LAST_BOX_TYPE_DELETE_DIR;
+
+	event.box = *dsync_box;
+	array_append(&worker->box_events, &event, 1);
+}
+
+static void
+test_worker_rename_mailbox(struct dsync_worker *_worker,
+			   const mailbox_guid_t *mailbox,
+			   const struct dsync_mailbox *dsync_box)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct test_dsync_box_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type = LAST_BOX_TYPE_RENAME;
+
+	event.box = *dsync_box;
+	event.box.mailbox_guid = *mailbox;
+	array_append(&worker->box_events, &event, 1);
+}
+
+static void
+test_worker_update_mailbox(struct dsync_worker *_worker,
+			   const struct dsync_mailbox *dsync_box)
+{
+	test_worker_set_last_box(_worker, dsync_box, LAST_BOX_TYPE_UPDATE);
+}
+
+static void
+test_worker_select_mailbox(struct dsync_worker *_worker,
+			   const mailbox_guid_t *mailbox,
+			   const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct dsync_mailbox box;
+
+	worker->selected_mailbox = *mailbox;
+	worker->cache_fields = cache_fields;
+
+	memset(&box, 0, sizeof(box));
+	memcpy(box.mailbox_guid.guid, mailbox, sizeof(box.mailbox_guid.guid));
+}
+
+static struct test_dsync_msg_event *
+test_worker_set_last_msg(struct test_dsync_worker *worker,
+			 const struct dsync_message *msg,
+			 enum test_dsync_last_msg_type type)
+{
+	struct test_dsync_msg_event *event;
+	const char **keywords;
+	unsigned int i, count;
+
+	event = array_append_space(&worker->msg_events);
+	event->type = type;
+	event->msg = *msg;
+	event->mailbox = worker->selected_mailbox;
+	event->msg.guid = p_strdup(worker->tmp_pool, msg->guid);
+	if (msg->keywords != NULL) {
+		count = str_array_length(msg->keywords);
+		keywords = p_new(worker->tmp_pool, const char *, count+1);
+		for (i = 0; i < count; i++) {
+			keywords[i] = p_strdup(worker->tmp_pool,
+					       msg->keywords[i]);
+		}
+		event->msg.keywords = keywords;
+	}
+	return event;
+}
+
+bool test_dsync_worker_next_msg_event(struct test_dsync_worker *worker,
+				      struct test_dsync_msg_event *event_r)
+{
+	const struct test_dsync_msg_event *events;
+	unsigned int count;
+
+	events = array_get(&worker->msg_events, &count);
+	if (count == 0)
+		return FALSE;
+
+	*event_r = events[0];
+	array_delete(&worker->msg_events, 0, 1);
+	return TRUE;
+}
+
+static void
+test_worker_msg_update_metadata(struct dsync_worker *_worker,
+				const struct dsync_message *msg)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+
+	test_worker_set_last_msg(worker, msg, LAST_MSG_TYPE_UPDATE);
+}
+
+static void
+test_worker_msg_update_uid(struct dsync_worker *_worker,
+			   uint32_t old_uid, uint32_t new_uid)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct dsync_message msg;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.uid = old_uid;
+	msg.modseq = new_uid;
+	test_worker_set_last_msg(worker, &msg, LAST_MSG_TYPE_UPDATE_UID);
+}
+
+static void test_worker_msg_expunge(struct dsync_worker *_worker, uint32_t uid)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct dsync_message msg;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.uid = uid;
+	test_worker_set_last_msg(worker, &msg, LAST_MSG_TYPE_EXPUNGE);
+}
+
+static void
+test_worker_msg_copy(struct dsync_worker *_worker,
+		     const mailbox_guid_t *src_mailbox,
+		     uint32_t src_uid, const struct dsync_message *dest_msg,
+		     dsync_worker_copy_callback_t *callback, void *context)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct test_dsync_msg_event *event;
+
+	event = test_worker_set_last_msg(worker, dest_msg, LAST_MSG_TYPE_COPY);
+	event->copy_src_mailbox = *src_mailbox;
+	event->copy_src_uid = src_uid;
+	callback(TRUE, context);
+}
+
+static void
+test_worker_msg_save(struct dsync_worker *_worker,
+		     const struct dsync_message *msg,
+		     const struct dsync_msg_static_data *data,
+		     dsync_worker_save_callback_t *callback,
+		     void *context)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct test_dsync_msg_event *event;
+	const unsigned char *d;
+	size_t size;
+	ssize_t ret;
+	string_t *body;
+
+	event = test_worker_set_last_msg(worker, msg, LAST_MSG_TYPE_SAVE);
+	event->save_data.pop3_uidl = p_strdup(worker->tmp_pool, data->pop3_uidl);
+	event->save_data.received_date = data->received_date;
+
+	body = t_str_new(256);
+	while ((ret = i_stream_read_data(data->input, &d, &size, 0)) > 0) {
+		str_append_n(body, d, size);
+		i_stream_skip(data->input, size);
+	}
+	i_assert(ret == -1);
+	event->save_body = p_strdup(worker->tmp_pool, str_c(body));
+
+	callback(context);
+}
+
+static void
+test_worker_msg_save_cancel(struct dsync_worker *_worker ATTR_UNUSED)
+{
+}
+
+static void
+test_worker_msg_get(struct dsync_worker *_worker,
+		    const mailbox_guid_t *mailbox ATTR_UNUSED,
+		    uint32_t uid ATTR_UNUSED,
+		    dsync_worker_msg_callback_t *callback, void *context)
+{
+	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
+	struct dsync_msg_static_data data;
+
+	memset(&data, 0, sizeof(data));
+	data.pop3_uidl = "uidl";
+	data.received_date = 123456;
+	data.input = worker->body_stream;
+	i_stream_seek(data.input, 0);
+	callback(DSYNC_MSG_GET_RESULT_SUCCESS, &data, context);
+}
+
+static void
+test_worker_finish(struct dsync_worker *_worker ATTR_UNUSED,
+		   dsync_worker_finish_callback_t *callback, void *context)
+{
+	callback(TRUE, context);
+}
+
+struct dsync_worker_vfuncs test_dsync_worker = {
+	test_worker_deinit,
+
+	test_worker_is_output_full,
+	test_worker_output_flush,
+
+	test_worker_mailbox_iter_init,
+	test_worker_mailbox_iter_next,
+	test_worker_mailbox_iter_deinit,
+
+	test_worker_subs_iter_init,
+	test_worker_subs_iter_next,
+	test_worker_subs_iter_next_un,
+	test_worker_subs_iter_deinit,
+	test_worker_set_subscribed,
+
+	test_worker_msg_iter_init,
+	test_worker_msg_iter_next,
+	test_worker_msg_iter_deinit,
+
+	test_worker_create_mailbox,
+	test_worker_delete_mailbox,
+	test_worker_delete_dir,
+	test_worker_rename_mailbox,
+	test_worker_update_mailbox,
+
+	test_worker_select_mailbox,
+	test_worker_msg_update_metadata,
+	test_worker_msg_update_uid,
+	test_worker_msg_expunge,
+	test_worker_msg_copy,
+	test_worker_msg_save,
+	test_worker_msg_save_cancel,
+	test_worker_msg_get,
+	test_worker_finish
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/dsync/test-dsync-worker.h	Thu Dec 29 14:43:45 2011 +0200
@@ -0,0 +1,96 @@
+#ifndef TEST_DSYNC_WORKER_H
+#define TEST_DSYNC_WORKER_H
+
+#include "dsync-worker-private.h"
+
+enum test_dsync_last_box_type {
+	LAST_BOX_TYPE_CREATE,
+	LAST_BOX_TYPE_DELETE,
+	LAST_BOX_TYPE_DELETE_DIR,
+	LAST_BOX_TYPE_RENAME,
+	LAST_BOX_TYPE_UPDATE,
+	LAST_BOX_TYPE_SUBSCRIBE,
+	LAST_BOX_TYPE_UNSUBSCRIBE
+};
+
+enum test_dsync_last_msg_type {
+	LAST_MSG_TYPE_UPDATE,
+	LAST_MSG_TYPE_UPDATE_UID,
+	LAST_MSG_TYPE_EXPUNGE,
+	LAST_MSG_TYPE_COPY,
+	LAST_MSG_TYPE_SAVE
+};
+
+struct test_dsync_worker_mailbox_iter {
+	struct dsync_worker_mailbox_iter iter;
+	struct dsync_mailbox *next_box;
+	bool last;
+};
+
+struct test_dsync_worker_subs_iter {
+	struct dsync_worker_subs_iter iter;
+	struct dsync_worker_subscription *next_subscription;
+	struct dsync_worker_unsubscription *next_unsubscription;
+	bool last_subs, last_unsubs;
+};
+
+struct test_dsync_worker_msg {
+	struct dsync_message msg;
+	unsigned int mailbox_idx;
+};
+
+struct test_dsync_worker_msg_iter {
+	struct dsync_worker_msg_iter iter;
+	ARRAY_DEFINE(msgs, struct test_dsync_worker_msg);
+	unsigned int idx;
+	bool last;
+};
+
+struct test_dsync_worker_result {
+	uint32_t tag;
+	int result;
+};
+
+struct test_dsync_box_event {
+	enum test_dsync_last_box_type type;
+	struct dsync_mailbox box;
+};
+
+struct test_dsync_msg_event {
+	enum test_dsync_last_msg_type type;
+	struct dsync_message msg;
+
+	mailbox_guid_t mailbox, copy_src_mailbox;
+	uint32_t copy_src_uid;
+	struct dsync_msg_static_data save_data;
+	const char *save_body;
+};
+
+struct test_dsync_worker {
+	struct dsync_worker worker;
+	struct istream *body_stream;
+
+	struct test_dsync_worker_mailbox_iter box_iter;
+	struct test_dsync_worker_subs_iter subs_iter;
+	struct test_dsync_worker_msg_iter msg_iter;
+	ARRAY_DEFINE(results, struct test_dsync_worker_result);
+
+	pool_t tmp_pool;
+
+	ARRAY_DEFINE(box_events, struct test_dsync_box_event);
+	ARRAY_DEFINE(msg_events, struct test_dsync_msg_event);
+
+	mailbox_guid_t selected_mailbox;
+	mailbox_guid_t *msg_iter_mailboxes;
+	unsigned int msg_iter_mailbox_count;
+	const ARRAY_TYPE(mailbox_cache_field) *cache_fields;
+};
+
+struct dsync_worker *dsync_worker_init_test(void);
+
+bool test_dsync_worker_next_box_event(struct test_dsync_worker *worker,
+				      struct test_dsync_box_event *event_r);
+bool test_dsync_worker_next_msg_event(struct test_dsync_worker *worker,
+				      struct test_dsync_msg_event *event_r);
+
+#endif
--- a/src/dsync/Makefile.am	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-bin_PROGRAMS = dsync
-
-AM_CPPFLAGS = \
-	-I$(top_srcdir)/src/lib \
-	-I$(top_srcdir)/src/lib-test \
-	-I$(top_srcdir)/src/lib-settings \
-	-I$(top_srcdir)/src/lib-master \
-	-I$(top_srcdir)/src/lib-mail \
-	-I$(top_srcdir)/src/lib-imap \
-	-I$(top_srcdir)/src/lib-index \
-	-I$(top_srcdir)/src/lib-storage
-
-if !BUILD_SHARED_LIBS
-unused_objects = \
-	../lib/mountpoint.o \
-	../lib-storage/mail-search-parser-imap.o
-endif
-
-libs = \
-	$(LIBDOVECOT_STORAGE) \
-	$(unused_objects)
-
-dsync_LDADD = $(libs) $(LIBDOVECOT) $(MODULE_LIBS)
-dsync_DEPENDENCIES = $(libs) $(LIBDOVECOT_DEPS)
-dsync_SOURCES = \
-	dsync.c \
-	dsync-brain.c \
-	dsync-brain-msgs.c \
-	dsync-brain-msgs-new.c \
-	dsync-data.c \
-	dsync-proxy.c \
-	dsync-proxy-client.c \
-	dsync-proxy-server.c \
-	dsync-proxy-server-cmd.c \
-	dsync-worker.c \
-	dsync-worker-local.c
-
-noinst_HEADERS = \
-	dsync-brain.h \
-	dsync-brain-private.h \
-	dsync-data.h \
-	dsync-proxy.h \
-	dsync-proxy-server.h \
-	dsync-worker.h \
-	dsync-worker-private.h \
-	test-dsync-common.h \
-	test-dsync-worker.h
-
-test_programs = \
-	test-dsync-brain \
-	test-dsync-brain-msgs \
-	test-dsync-proxy \
-	test-dsync-proxy-server-cmd
-
-noinst_PROGRAMS = $(test_programs)
-
-test_libs = \
-	../lib-test/libtest.la \
-	../lib-mail/libmail.la \
-	../lib-imap/libimap.la \
-	../lib-charset/libcharset.la \
-	../lib/liblib.la
-
-test_ldadd = \
-	$(test_libs) \
-	$(LIBICONV)
-
-test_dsync_brain_SOURCES = test-dsync-brain.c test-dsync-worker.c test-dsync-common.c
-test_dsync_brain_LDADD = dsync-data.o dsync-brain.o dsync-worker.o $(test_ldadd)
-test_dsync_brain_DEPENDENCIES = dsync-data.o dsync-brain.o dsync-worker.o $(test_libs)
-
-test_dsync_brain_msgs_SOURCES = test-dsync-brain-msgs.c test-dsync-worker.c test-dsync-common.c
-test_dsync_brain_msgs_LDADD = dsync-data.o dsync-brain-msgs.o dsync-worker.o $(test_ldadd)
-test_dsync_brain_msgs_DEPENDENCIES = dsync-data.o dsync-brain-msgs.o dsync-worker.o $(test_libs)
-
-test_dsync_proxy_SOURCES = test-dsync-proxy.c test-dsync-common.c
-test_dsync_proxy_LDADD = dsync-proxy.o dsync-data.o $(test_ldadd)
-test_dsync_proxy_DEPENDENCIES = dsync-proxy.o dsync-data.o $(test_libs)
-
-test_dsync_proxy_server_cmd_SOURCES = test-dsync-proxy-server-cmd.c test-dsync-worker.c test-dsync-common.c
-test_dsync_proxy_server_cmd_LDADD = dsync-worker.o dsync-proxy.o dsync-proxy-server-cmd.o dsync-data.o $(test_ldadd)
-test_dsync_proxy_server_cmd_DEPENDENCIES = dsync-worker.o dsync-proxy.o dsync-proxy-server-cmd.o dsync-data.o $(test_libs)
-
-check: check-am check-test
-check-test: all-am
-	for bin in $(test_programs); do \
-	  if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
-	done
--- a/src/dsync/dsync-brain-msgs-new.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,392 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-/*
-   This code contains the step 6 explained in dsync-brain-msgs.c:
-   It saves/copies new messages and gives new UIDs for conflicting messages.
-
-   The input is both workers' msg iterators' new_msgs and uid_conflicts
-   variables. They're first sorted by mailbox and secondarily by wanted
-   destination UID. Destination UIDs of conflicts should always be higher
-   than new messages'.
-
-   Mailboxes are handled one at a time:
-
-   1. Go through all saved messages. If we've already seen an instance of this
-      message, try to copy it. Otherwise save a new instance of it.
-   2. Some of the copies may fail because they're already expunged by that
-      time. A list of these failed copies are saved to copy_retry_indexes.
-   3. UID conflicts are resolved by assigning a new UID to the message.
-      To avoid delays with remote dsync, this is done via worker API.
-      Internally the local worker copies the message to its new UID and
-      once the copy succeeds, the old UID is expunged. If the copy fails, it's
-      either due to message already being expunged or something more fatal.
-   4. Once all messages are saved/copied, see if there are any failed copies.
-      If so, goto 1, but going through only the failed messages.
-   5. If there are more mailboxes left, go to next one and goto 1.
-
-   Step 4 may require waiting for remote worker to send all replies.
-*/
-
-#include "lib.h"
-#include "array.h"
-#include "istream.h"
-#include "hash.h"
-#include "dsync-worker.h"
-#include "dsync-brain-private.h"
-
-struct dsync_brain_msg_copy_context {
-	struct dsync_brain_msg_iter *iter;
-	unsigned int msg_idx;
-};
-
-struct dsync_brain_msg_save_context {
-	struct dsync_brain_msg_iter *iter;
-	const struct dsync_message *msg;
-	unsigned int mailbox_idx;
-};
-
-static void
-dsync_brain_msg_sync_add_new_msgs(struct dsync_brain_msg_iter *iter);
-
-static void msg_save_callback(void *context)
-{
-	struct dsync_brain_msg_save_context *ctx = context;
-
-	if (--ctx->iter->save_results_left == 0 && !ctx->iter->adding_msgs)
-		dsync_brain_msg_sync_add_new_msgs(ctx->iter);
-	i_free(ctx);
-}
-
-static void msg_get_callback(enum dsync_msg_get_result result,
-			     const struct dsync_msg_static_data *data,
-			     void *context)
-{
-	struct dsync_brain_msg_save_context *ctx = context;
-	const struct dsync_brain_mailbox *mailbox;
-	struct istream *input;
-
-	i_assert(ctx->iter->save_results_left > 0);
-
-	mailbox = array_idx(&ctx->iter->sync->mailboxes, ctx->mailbox_idx);
-	switch (result) {
-	case DSYNC_MSG_GET_RESULT_SUCCESS:
-		/* the mailbox may have changed, make sure we've the
-		   right one */
-		dsync_worker_select_mailbox(ctx->iter->worker, &mailbox->box);
-
-		input = data->input;
-		dsync_worker_msg_save(ctx->iter->worker, ctx->msg, data,
-				      msg_save_callback, ctx);
-		i_stream_unref(&input);
-		break;
-	case DSYNC_MSG_GET_RESULT_EXPUNGED:
-		/* mail got expunged during sync. just skip this. */
-		msg_save_callback(ctx);
-		break;
-	case DSYNC_MSG_GET_RESULT_FAILED:
-		i_error("msg-get failed: box=%s uid=%u guid=%s",
-			mailbox->box.name, ctx->msg->uid, ctx->msg->guid);
-		dsync_brain_fail(ctx->iter->sync->brain);
-		msg_save_callback(ctx);
-		break;
-	}
-}
-
-static void
-dsync_brain_sync_remove_guid_instance(struct dsync_brain_msg_iter *iter,
-				      const struct dsync_brain_new_msg *msg)
-{
-	struct dsync_brain_guid_instance *inst;
-	void *orig_key, *orig_value;
-
-	if (!hash_table_lookup_full(iter->guid_hash, msg->msg->guid,
-				    &orig_key, &orig_value)) {
-		/* another failed copy already removed it */
-		return;
-	}
-	inst = orig_value;
-
-	if (inst->next == NULL)
-		hash_table_remove(iter->guid_hash, orig_key);
-	else
-		hash_table_update(iter->guid_hash, orig_key, inst->next);
-}
-
-static void dsync_brain_copy_callback(bool success, void *context)
-{
-	struct dsync_brain_msg_copy_context *ctx = context;
-	struct dsync_brain_new_msg *msg;
-
-	if (!success) {
-		/* mark the guid instance invalid and try again later */
-		msg = array_idx_modifiable(&ctx->iter->new_msgs, ctx->msg_idx);
-		i_assert(msg->saved);
-		msg->saved = FALSE;
-
-		if (ctx->iter->next_new_msg > ctx->msg_idx)
-			ctx->iter->next_new_msg = ctx->msg_idx;
-
-		dsync_brain_sync_remove_guid_instance(ctx->iter, msg);
-	}
-
-	if (--ctx->iter->copy_results_left == 0 && !ctx->iter->adding_msgs)
-		dsync_brain_msg_sync_add_new_msgs(ctx->iter);
-	i_free(ctx);
-}
-
-static int
-dsync_brain_msg_sync_add_new_msg(struct dsync_brain_msg_iter *dest_iter,
-				 const mailbox_guid_t *src_mailbox,
-				 unsigned int msg_idx,
-				 struct dsync_brain_new_msg *msg)
-{
-	struct dsync_brain_msg_save_context *save_ctx;
-	struct dsync_brain_msg_copy_context *copy_ctx;
-	struct dsync_brain_msg_iter *src_iter;
-	const struct dsync_brain_guid_instance *inst;
-	const struct dsync_brain_mailbox *inst_box;
-
-	msg->saved = TRUE;
-
-	inst = hash_table_lookup(dest_iter->guid_hash, msg->msg->guid);
-	if (inst != NULL) {
-		/* we can save this by copying an existing message */
-		inst_box = array_idx(&dest_iter->sync->mailboxes,
-				     inst->mailbox_idx);
-
-		copy_ctx = i_new(struct dsync_brain_msg_copy_context, 1);
-		copy_ctx->iter = dest_iter;
-		copy_ctx->msg_idx = msg_idx;
-
-		dest_iter->copy_results_left++;
-		dest_iter->adding_msgs = TRUE;
-		dsync_worker_msg_copy(dest_iter->worker,
-				      &inst_box->box.mailbox_guid,
-				      inst->uid, msg->msg,
-				      dsync_brain_copy_callback, copy_ctx);
-		dest_iter->adding_msgs = FALSE;
-	} else {
-		src_iter = dest_iter == dest_iter->sync->dest_msg_iter ?
-			dest_iter->sync->src_msg_iter :
-			dest_iter->sync->dest_msg_iter;
-
-		save_ctx = i_new(struct dsync_brain_msg_save_context, 1);
-		save_ctx->iter = dest_iter;
-		save_ctx->msg = msg->msg;
-		save_ctx->mailbox_idx = dest_iter->mailbox_idx;
-
-		dest_iter->save_results_left++;
-		dest_iter->adding_msgs = TRUE;
-		dsync_worker_msg_get(src_iter->worker, src_mailbox,
-				     msg->orig_uid, msg_get_callback, save_ctx);
-		dest_iter->adding_msgs = FALSE;
-		if (dsync_worker_output_flush(src_iter->worker) < 0)
-			return -1;
-		if (dsync_worker_is_output_full(dest_iter->worker)) {
-			/* see if the output becomes less full by flushing */
-			if (dsync_worker_output_flush(dest_iter->worker) < 0)
-				return -1;
-		}
-	}
-	return dsync_worker_is_output_full(dest_iter->worker) ? 0 : 1;
-}
-
-static bool
-dsync_brain_mailbox_add_new_msgs(struct dsync_brain_msg_iter *iter,
-				 const mailbox_guid_t *mailbox_guid)
-{
-	struct dsync_brain_new_msg *msgs;
-	unsigned int msg_count;
-	bool ret = TRUE;
-
-	msgs = array_get_modifiable(&iter->new_msgs, &msg_count);
-	while (iter->next_new_msg < msg_count) {
-		struct dsync_brain_new_msg *msg = &msgs[iter->next_new_msg];
-
-		if (msg->mailbox_idx != iter->mailbox_idx) {
-			i_assert(msg->mailbox_idx > iter->mailbox_idx);
-			ret = FALSE;
-			break;
-		}
-		iter->next_new_msg++;
-
-		if (msg->saved)
-			continue;
-		if (dsync_brain_msg_sync_add_new_msg(iter, mailbox_guid,
-						     iter->next_new_msg - 1,
-						     msg) <= 0) {
-			/* failed / continue later */
-			break;
-		}
-	}
-	if (iter->next_new_msg == msg_count)
-		ret = FALSE;
-
-	/* flush copy commands */
-	if (dsync_worker_output_flush(iter->worker) > 0 && ret) {
-		/* we have more space again, continue */
-		return dsync_brain_mailbox_add_new_msgs(iter, mailbox_guid);
-	} else {
-		return ret;
-	}
-}
-
-static void
-dsync_brain_mailbox_save_conflicts(struct dsync_brain_msg_iter *iter)
-{
-	const struct dsync_brain_uid_conflict *conflicts;
-	unsigned int i, count;
-
-	conflicts = array_get(&iter->uid_conflicts, &count);
-	for (i = iter->next_conflict; i < count; i++) {
-		if (conflicts[i].mailbox_idx != iter->mailbox_idx)
-			break;
-
-		dsync_worker_msg_update_uid(iter->worker, conflicts[i].old_uid,
-					    conflicts[i].new_uid);
-	}
-	iter->next_conflict = i;
-}
-
-static void
-dsync_brain_msg_sync_finish(struct dsync_brain_msg_iter *iter)
-{
-	struct dsync_brain_mailbox_sync *sync = iter->sync;
-
-	i_assert(sync->brain->state == DSYNC_STATE_SYNC_MSGS);
-
-	iter->msgs_sent = TRUE;
-
-	/* done with all mailboxes from this iter */
-	dsync_worker_set_input_callback(iter->worker, NULL, NULL);
-
-	if (sync->src_msg_iter->msgs_sent &&
-	    sync->dest_msg_iter->msgs_sent &&
-	    sync->src_msg_iter->save_results_left == 0 &&
-	    sync->dest_msg_iter->save_results_left == 0 &&
-	    dsync_worker_output_flush(sync->dest_worker) > 0 &&
-	    dsync_worker_output_flush(sync->src_worker) > 0) {
-		dsync_worker_set_output_callback(sync->src_msg_iter->worker,
-						 NULL, NULL);
-		dsync_worker_set_output_callback(sync->dest_msg_iter->worker,
-						 NULL, NULL);
-		sync->brain->state++;
-		dsync_brain_sync(sync->brain);
-	}
-}
-
-static bool
-dsync_brain_msg_sync_select_mailbox(struct dsync_brain_msg_iter *iter)
-{
-	const struct dsync_brain_mailbox *mailbox;
-
-	while (iter->mailbox_idx < array_count(&iter->sync->mailboxes)) {
-		if (array_count(&iter->new_msgs) == 0 &&
-		    array_count(&iter->uid_conflicts) == 0) {
-			/* optimization: don't even bother selecting this
-			   mailbox */
-			iter->mailbox_idx++;
-			continue;
-		}
-
-		mailbox = array_idx(&iter->sync->mailboxes, iter->mailbox_idx);
-		dsync_worker_select_mailbox(iter->worker, &mailbox->box);
-		return TRUE;
-	}
-	dsync_brain_msg_sync_finish(iter);
-	return FALSE;
-}
-
-static void
-dsync_brain_msg_sync_add_new_msgs(struct dsync_brain_msg_iter *iter)
-{
-	const struct dsync_brain_mailbox *mailbox;
-	const mailbox_guid_t *mailbox_guid;
-
-	if (iter->msgs_sent) {
-		dsync_brain_msg_sync_finish(iter);
-		return;
-	}
-
-	do {
-		mailbox = array_idx(&iter->sync->mailboxes, iter->mailbox_idx);
-		mailbox_guid = &mailbox->box.mailbox_guid;
-		if (dsync_brain_mailbox_add_new_msgs(iter, mailbox_guid)) {
-			/* continue later */
-			return;
-		}
-
-		/* all messages saved for this mailbox. continue with saving
-		   its conflicts and waiting for copies to finish. */
-		dsync_brain_mailbox_save_conflicts(iter);
-		if (iter->save_results_left > 0 ||
-		    iter->copy_results_left > 0) {
-			/* wait for saves/copies to finish */
-			return;
-		}
-
-		/* done with this mailbox, try the next one */
-		iter->mailbox_idx++;
-	} while (dsync_brain_msg_sync_select_mailbox(iter));
-}
-
-static void dsync_worker_new_msg_output(void *context)
-{
-	struct dsync_brain_msg_iter *iter = context;
-
-	dsync_brain_msg_sync_add_new_msgs(iter);
-}
-
-static int dsync_brain_new_msg_cmp(const struct dsync_brain_new_msg *m1,
-				   const struct dsync_brain_new_msg *m2)
-{
-	if (m1->mailbox_idx < m2->mailbox_idx)
-		return -1;
-	if (m1->mailbox_idx > m2->mailbox_idx)
-		return 1;
-
-	if (m1->msg->uid < m2->msg->uid)
-		return -1;
-	if (m1->msg->uid > m2->msg->uid)
-		return 1;
-	return 0;
-}
-
-static int
-dsync_brain_uid_conflict_cmp(const struct dsync_brain_uid_conflict *c1,
-			     const struct dsync_brain_uid_conflict *c2)
-{
-	if (c1->mailbox_idx < c2->mailbox_idx)
-	       return -1;
-	if (c1->mailbox_idx < c2->mailbox_idx)
-		return 1;
-
-	if (c1->new_uid < c2->new_uid)
-		return -1;
-	if (c1->new_uid > c2->new_uid)
-		return 1;
-	return 0;
-}
-
-static void
-dsync_brain_msg_iter_sync_new_msgs(struct dsync_brain_msg_iter *iter)
-{
-	iter->mailbox_idx = 0;
-
-	/* sort input by 1) mailbox, 2) new message UID */
-	array_sort(&iter->new_msgs, dsync_brain_new_msg_cmp);
-	array_sort(&iter->uid_conflicts, dsync_brain_uid_conflict_cmp);
-
-	dsync_worker_set_input_callback(iter->worker, NULL, iter);
-	dsync_worker_set_output_callback(iter->worker,
-					 dsync_worker_new_msg_output, iter);
-
-	if (dsync_brain_msg_sync_select_mailbox(iter))
-		dsync_brain_msg_sync_add_new_msgs(iter);
-}
-
-void dsync_brain_msg_sync_new_msgs(struct dsync_brain_mailbox_sync *sync)
-{
-	dsync_brain_msg_iter_sync_new_msgs(sync->src_msg_iter);
-	dsync_brain_msg_iter_sync_new_msgs(sync->dest_msg_iter);
-}
--- a/src/dsync/dsync-brain-msgs.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,537 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-/* This code synchronizes messages in all mailboxes between two workers.
-   The "src" and "dest" terms don't really have anything to do with reality,
-   they're both treated equal.
-
-   1. Iterate through all messages in all (wanted) mailboxes. The mailboxes
-      are iterated in the same order and messages in ascending order.
-      All of the expunged messages at the end of mailbox (i.e.
-      last_existing_uid+1 .. next_uid-1) are also returned with
-      DSYNC_MAIL_FLAG_EXPUNGED set. We only care about the end of the mailbox,
-      because we can detect UID conflicts for messages in the middle by looking
-      at the next existing message and seeing if it has UID conflict.
-   2. For each seen non-expunged message, save it to GUID instance hash table:
-      message GUID => linked list of { uid, mailbox }
-   3. Each message in a mailbox is matched between the two workers as long as
-      both have messages left (the last ones may be expunged).
-      The possibilities are:
-
-      i) We don't know the GUIDs of both messages:
-
-	a) Message is expunged in both. Do nothing.
-	b) Message is expunged in only one of them. If there have been no UID
-	   conflicts seen so far, expunge the message in the other one.
-	   Otherwise, give the existing a message a new UID (at step 6).
-
-      ii) We know GUIDs of both messages (one/both of them may be expunged):
-
-	a) Messages have conflicting GUIDs. Give new UIDs for the non-expunged
-	   message(s) (at step 6).
-	b) Messages have matching GUIDs and one of them is expunged.
-	   Expunge also the other one. (We don't need to care about previous
-	   UID conflicts here, because we know this message is the same with
-	   both workers, since they have the same GUID.)
-	c) Messages have matching GUIDs and both of them exist. Sync flags from
-	   whichever has the higher modseq. If both modseqs equal but flags
-	   don't, pick the one that has more flags. If even the flag count is
-	   the same, just pick one of them.
-   4. One of the workers may messages left in the mailbox. Copy these
-      (non-expunged) messages to the other worker (at step 6).
-   5. If there are more mailboxes left, go to next one and goto 2.
-
-   6. Copy the new messages and give new UIDs to conflicting messages.
-      This code exists in dsync-brain-msgs-new.c
-*/
-
-#include "lib.h"
-#include "array.h"
-#include "hash.h"
-#include "dsync-worker.h"
-#include "dsync-brain-private.h"
-
-static void dsync_brain_guid_add(struct dsync_brain_msg_iter *iter)
-{
-	struct dsync_brain_guid_instance *inst, *prev_inst;
-
-	if ((iter->msg.flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0)
-		return;
-
-	inst = p_new(iter->sync->pool, struct dsync_brain_guid_instance, 1);
-	inst->mailbox_idx = iter->mailbox_idx;
-	inst->uid = iter->msg.uid;
-
-	prev_inst = hash_table_lookup(iter->guid_hash, iter->msg.guid);
-	if (prev_inst == NULL) {
-		hash_table_insert(iter->guid_hash,
-				  p_strdup(iter->sync->pool, iter->msg.guid),
-				  inst);
-	} else {
-		inst->next = prev_inst->next;
-		prev_inst->next = inst;
-	}
-}
-
-static int dsync_brain_msg_iter_next(struct dsync_brain_msg_iter *iter)
-{
-	int ret = 1;
-
-	if (iter->msg.guid == NULL) {
-		ret = dsync_worker_msg_iter_next(iter->iter,
-						 &iter->mailbox_idx,
-						 &iter->msg);
-		if (ret > 0)
-			dsync_brain_guid_add(iter);
-	}
-
-	if (iter->sync->wanted_mailbox_idx != iter->mailbox_idx) {
-		/* finished with this mailbox */
-		return -1;
-	}
-	return ret;
-}
-
-static int
-dsync_brain_msg_iter_skip_mailbox(struct dsync_brain_mailbox_sync *sync)
-{
-	int ret;
-
-	while ((ret = dsync_brain_msg_iter_next(sync->src_msg_iter)) > 0)
-		sync->src_msg_iter->msg.guid = NULL;
-	if (ret == 0)
-		return 0;
-
-	while ((ret = dsync_brain_msg_iter_next(sync->dest_msg_iter)) > 0)
-		sync->dest_msg_iter->msg.guid = NULL;
-	if (ret == 0)
-		return 0;
-
-	sync->skip_mailbox = FALSE;
-	return -1;
-}
-
-static int dsync_brain_msg_iter_next_pair(struct dsync_brain_mailbox_sync *sync)
-{
-	int ret1, ret2;
-
-	if (sync->skip_mailbox) {
-		if (dsync_brain_msg_iter_skip_mailbox(sync) == 0)
-			return 0;
-	}
-
-	ret1 = dsync_brain_msg_iter_next(sync->src_msg_iter);
-	ret2 = dsync_brain_msg_iter_next(sync->dest_msg_iter);
-	if (ret1 == 0 || ret2 == 0) {
-		/* make sure we iterate through everything in both iterators
-		   (even if it might not seem necessary, because proxy
-		   requires it) */
-		return 0;
-	}
-	if (ret1 < 0 || ret2 < 0)
-		return -1;
-	return 1;
-}
-
-static void
-dsync_brain_msg_sync_save(struct dsync_brain_msg_iter *iter,
-			  unsigned int mailbox_idx,
-			  const struct dsync_message *msg)
-{
-	struct dsync_brain_new_msg *new_msg;
-
-	if ((msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0)
-		return;
-
-	new_msg = array_append_space(&iter->new_msgs);
-	new_msg->mailbox_idx = mailbox_idx;
-	new_msg->orig_uid = msg->uid;
-	new_msg->msg = dsync_message_dup(iter->sync->pool, msg);
-}
-
-static void
-dsync_brain_msg_sync_conflict(struct dsync_brain_msg_iter *conflict_iter,
-			      struct dsync_brain_msg_iter *save_iter,
-			      const struct dsync_message *msg)
-{
-	struct dsync_brain_uid_conflict *conflict;
-	struct dsync_brain_new_msg *new_msg;
-	struct dsync_brain_mailbox *brain_box;
-	uint32_t new_uid;
-
-	brain_box = array_idx_modifiable(&save_iter->sync->mailboxes,
-					 save_iter->mailbox_idx);
-
-	if (save_iter->sync->brain->backup) {
-		i_warning("Destination mailbox %s has been modified, "
-			  "need to recreate it before we can continue syncing",
-			  brain_box->box.name);
-		dsync_worker_delete_mailbox(save_iter->sync->brain->dest_worker,
-					    &brain_box->box);
-		save_iter->sync->brain->unexpected_changes = TRUE;
-		save_iter->sync->skip_mailbox = TRUE;
-		return;
-	}
-
-	new_uid = brain_box->box.uid_next++;
-
-	conflict = array_append_space(&conflict_iter->uid_conflicts);
-	conflict->mailbox_idx = conflict_iter->mailbox_idx;
-	conflict->old_uid = msg->uid;
-	conflict->new_uid = new_uid;
-
-	new_msg = array_append_space(&save_iter->new_msgs);
-	new_msg->mailbox_idx = save_iter->mailbox_idx;
-	new_msg->orig_uid = msg->uid;
-	new_msg->msg = dsync_message_dup(save_iter->sync->pool, msg);
-	new_msg->msg->uid = new_uid;
-}
-
-static int
-dsync_message_flag_importance_cmp(const struct dsync_message *m1,
-				  const struct dsync_message *m2)
-{
-	unsigned int i, count1, count2;
-
-	if (m1->modseq > m2->modseq)
-		return -1;
-	else if (m1->modseq < m2->modseq)
-		return 1;
-
-	if (m1->flags == m2->flags &&
-	    dsync_keyword_list_equals(m1->keywords, m2->keywords))
-		return 0;
-
-	/* modseqs match, but flags aren't the same. pick the one that
-	   has more flags. */
-	count1 = str_array_length(m1->keywords);
-	count2 = str_array_length(m2->keywords);
-	for (i = 1; i != MAIL_RECENT; i <<= 1) {
-		if ((m1->flags & i) != 0)
-			count1++;
-		if ((m2->flags & i) != 0)
-			count2++;
-	}
-	if (count1 > count2)
-		return -1;
-	else if (count1 < count2)
-		return 1;
-
-	/* they even have the same number of flags. don't bother with further
-	   guessing, just pick the first one. */
-	return -1;
-}
-
-static void dsync_brain_msg_sync_existing(struct dsync_brain_mailbox_sync *sync,
-					  struct dsync_message *src_msg,
-					  struct dsync_message *dest_msg)
-{
-	int ret;
-
-	ret = dsync_message_flag_importance_cmp(src_msg, dest_msg);
-	if (ret < 0 || (sync->brain->backup && ret > 0))
-		dsync_worker_msg_update_metadata(sync->dest_worker, src_msg);
-	else if (ret > 0)
-		dsync_worker_msg_update_metadata(sync->src_worker, dest_msg);
-}
-
-static int dsync_brain_msg_sync_pair(struct dsync_brain_mailbox_sync *sync)
-{
-	struct dsync_message *src_msg = &sync->src_msg_iter->msg;
-	struct dsync_message *dest_msg = &sync->dest_msg_iter->msg;
-	const char *src_guid, *dest_guid;
-	unsigned char guid_128_data[GUID_128_SIZE * 2 + 1];
-	bool src_expunged, dest_expunged;
-
-	i_assert(sync->src_msg_iter->mailbox_idx ==
-		 sync->dest_msg_iter->mailbox_idx);
-
-	src_expunged = (src_msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0;
-	dest_expunged = (dest_msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0;
-
-	/* If a message is expunged, it's guaranteed to have a 128bit GUID.
-	   If the other message isn't expunged, we'll need to convert its GUID
-	   to the 128bit GUID form (if it's not already) so that we can compare
-	   them. */
-	if (src_expunged) {
-		src_guid = src_msg->guid;
-		dest_guid = dsync_get_guid_128_str(dest_msg->guid,
-						   guid_128_data,
-						   sizeof(guid_128_data));
-	} else if (dest_expunged) {
-		src_guid = dsync_get_guid_128_str(src_msg->guid, guid_128_data,
-						  sizeof(guid_128_data));
-		dest_guid = dest_msg->guid;
-	} else {
-		src_guid = src_msg->guid;
-		dest_guid = dest_msg->guid;
-	}
-
-	/* FIXME: checking for sync->uid_conflict isn't fully reliable here.
-	   we should be checking if the next matching message pair has a
-	   conflict, not if the previous pair had one. */
-	if (src_msg->uid < dest_msg->uid) {
-		/* message has been expunged from dest. */
-		if (src_expunged) {
-			/* expunged from source already */
-		} else if (sync->uid_conflict || sync->brain->backup) {
-			/* update uid src, copy to dest */
-			dsync_brain_msg_sync_conflict(sync->src_msg_iter,
-						      sync->dest_msg_iter,
-						      src_msg);
-		} else {
-			/* expunge from source */
-			dsync_worker_msg_expunge(sync->src_worker,
-						 src_msg->uid);
-		}
-		src_msg->guid = NULL;
-		return 0;
-	} else if (src_msg->uid > dest_msg->uid) {
-		/* message has been expunged from src. */
-		if (dest_expunged) {
-			/* expunged from dest already */
-		} else if (sync->uid_conflict && !sync->brain->backup) {
-			/* update uid in dest, copy to src */
-			dsync_brain_msg_sync_conflict(sync->dest_msg_iter,
-						      sync->src_msg_iter,
-						      dest_msg);
-		} else {
-			/* expunge from dest */
-			dsync_worker_msg_expunge(sync->dest_worker,
-						 dest_msg->uid);
-		}
-		dest_msg->guid = NULL;
-		return 0;
-	}
-
-	/* UIDs match, but do GUIDs? If either of the GUIDs aren't set, it
-	   means that either the storage doesn't support GUIDs or we're
-	   handling an old-style expunge record. In that case just assume
-	   they match. */
-	if (strcmp(src_guid, dest_guid) != 0 &&
-	    *src_guid != '\0' && *dest_guid != '\0') {
-		/* UID conflict. give new UIDs to messages in both src and
-		   dest (if they're not expunged already) */
-		sync->uid_conflict = TRUE;
-		if (!dest_expunged) {
-			dsync_brain_msg_sync_conflict(sync->dest_msg_iter,
-						      sync->src_msg_iter,
-						      dest_msg);
-		}
-		if (!src_expunged) {
-			dsync_brain_msg_sync_conflict(sync->src_msg_iter,
-						      sync->dest_msg_iter,
-						      src_msg);
-		}
-	} else if (dest_expunged) {
-		/* message expunged from destination */
-		if (src_expunged) {
-			/* expunged from source already */
-		} else if (sync->brain->backup) {
-			dsync_brain_msg_sync_conflict(sync->src_msg_iter,
-						      sync->dest_msg_iter,
-						      src_msg);
-		} else {
-			dsync_worker_msg_expunge(sync->src_worker,
-						 src_msg->uid);
-		}
-	} else if (src_expunged) {
-		/* message expunged from source, expunge from destination too */
-		dsync_worker_msg_expunge(sync->dest_worker, dest_msg->uid);
-	} else {
-		/* message exists in both source and dest, sync metadata */
-		dsync_brain_msg_sync_existing(sync, src_msg, dest_msg);
-	}
-	src_msg->guid = NULL;
-	dest_msg->guid = NULL;
-	return 0;
-}
-
-static bool dsync_brain_msg_sync_mailbox_end(struct dsync_brain_msg_iter *iter1,
-					     struct dsync_brain_msg_iter *iter2)
-{
-	int ret;
-
-	while ((ret = dsync_brain_msg_iter_next(iter1)) > 0) {
-		dsync_brain_msg_sync_save(iter2, iter1->mailbox_idx,
-					  &iter1->msg);
-		iter1->msg.guid = NULL;
-	}
-	return ret < 0;
-}
-
-static bool
-dsync_brain_msg_sync_mailbox_more(struct dsync_brain_mailbox_sync *sync)
-{
-	int ret;
-
-	while ((ret = dsync_brain_msg_iter_next_pair(sync)) > 0) {
-		if (dsync_brain_msg_sync_pair(sync) < 0)
-			break;
-		if (dsync_worker_is_output_full(sync->dest_worker)) {
-			if (dsync_worker_output_flush(sync->dest_worker) <= 0)
-				return FALSE;
-		}
-	}
- 	if (ret == 0)
-		return FALSE;
-
-	/* finished syncing messages in this mailbox that exist in both source
-	   and destination. if there are messages left, we can't reliably know
-	   if they should be expunged, so just copy them to the other side. */
-	if (!sync->brain->backup) {
-		if (!dsync_brain_msg_sync_mailbox_end(sync->dest_msg_iter,
-						      sync->src_msg_iter))
-			return FALSE;
-	}
-	if (!dsync_brain_msg_sync_mailbox_end(sync->src_msg_iter,
-					      sync->dest_msg_iter))
-		return FALSE;
-
-	/* done with this mailbox. the same iterator is still used for
-	   getting messages from other mailboxes. */
-	return TRUE;
-}
-
-void dsync_brain_msg_sync_more(struct dsync_brain_mailbox_sync *sync)
-{
-	const struct dsync_brain_mailbox *mailboxes;
-	unsigned int count, mailbox_idx = 0;
-
-	mailboxes = array_get(&sync->mailboxes, &count);
-	while (dsync_brain_msg_sync_mailbox_more(sync)) {
-		/* sync the next mailbox */
-		sync->uid_conflict = FALSE;
-		mailbox_idx = ++sync->wanted_mailbox_idx;
-		if (mailbox_idx >= count)
-			break;
-
-		dsync_worker_select_mailbox(sync->src_worker,
-			&mailboxes[mailbox_idx].box);
-		dsync_worker_select_mailbox(sync->dest_worker,
-			&mailboxes[mailbox_idx].box);
-	}
-	if (mailbox_idx < count) {
-		/* output buffer is full */
-		return;
-	}
-
-	/* finished with all mailboxes. */
-	dsync_worker_set_input_callback(sync->src_msg_iter->worker, NULL, NULL);
-	dsync_worker_set_output_callback(sync->src_msg_iter->worker, NULL, NULL);
-	dsync_worker_set_input_callback(sync->dest_msg_iter->worker, NULL, NULL);
-	dsync_worker_set_output_callback(sync->dest_msg_iter->worker, NULL, NULL);
-
-	if (dsync_worker_msg_iter_deinit(&sync->src_msg_iter->iter) < 0 ||
-	    dsync_worker_msg_iter_deinit(&sync->dest_msg_iter->iter) < 0) {
-		dsync_brain_fail(sync->brain);
-		return;
-	}
-
-	dsync_brain_msg_sync_new_msgs(sync);
-}
-
-static void dsync_worker_msg_callback(void *context)
-{
-	struct dsync_brain_mailbox_sync *sync = context;
-
-	dsync_brain_msg_sync_more(sync);
-}
-
-static struct dsync_brain_msg_iter *
-dsync_brain_msg_iter_init(struct dsync_brain_mailbox_sync *sync,
-			  struct dsync_worker *worker,
-			  const mailbox_guid_t mailboxes[],
-			  unsigned int mailbox_count)
-{
-	struct dsync_brain_msg_iter *iter;
-
-	iter = p_new(sync->pool, struct dsync_brain_msg_iter, 1);
-	iter->sync = sync;
-	iter->worker = worker;
-	i_array_init(&iter->uid_conflicts, 128);
-	i_array_init(&iter->new_msgs, 128);
-	iter->guid_hash = hash_table_create(default_pool, sync->pool, 10000,
-					    strcase_hash,
-					    (hash_cmp_callback_t *)strcasecmp);
-
-	iter->iter = dsync_worker_msg_iter_init(worker, mailboxes,
-						mailbox_count);
-	dsync_worker_set_input_callback(worker,
-					dsync_worker_msg_callback, sync);
-	dsync_worker_set_output_callback(worker,
-					 dsync_worker_msg_callback, sync);
-	if (mailbox_count > 0) {
-		const struct dsync_brain_mailbox *first;
-
-		first = array_idx(&sync->mailboxes, 0);
-		dsync_worker_select_mailbox(worker, &first->box);
-	}
-	return iter;
-}
-
-static void dsync_brain_msg_iter_deinit(struct dsync_brain_msg_iter *iter)
-{
-	if (iter->iter != NULL)
-		(void)dsync_worker_msg_iter_deinit(&iter->iter);
-
-	hash_table_destroy(&iter->guid_hash);
-	array_free(&iter->uid_conflicts);
-	array_free(&iter->new_msgs);
-}
-
-static void
-get_mailbox_guids(const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes,
-		  ARRAY_TYPE(mailbox_guid) *guids)
-{
-	const struct dsync_brain_mailbox *brain_box;
-
-	t_array_init(guids, array_count(mailboxes));
-	array_foreach(mailboxes, brain_box)
-		array_append(guids, &brain_box->box.mailbox_guid, 1);
-}
-
-struct dsync_brain_mailbox_sync *
-dsync_brain_msg_sync_init(struct dsync_brain *brain,
-			  const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes)
-{
-	struct dsync_brain_mailbox_sync *sync;
-	pool_t pool;
-
-	pool = pool_alloconly_create("dsync brain mailbox sync", 1024*256);
-	sync = p_new(pool, struct dsync_brain_mailbox_sync, 1);
-	sync->pool = pool;
-	sync->brain = brain;
-	sync->src_worker = brain->src_worker;
-	sync->dest_worker = brain->dest_worker;
-
-	p_array_init(&sync->mailboxes, pool, array_count(mailboxes));
-	array_append_array(&sync->mailboxes, mailboxes);
-	T_BEGIN {
-		ARRAY_TYPE(mailbox_guid) guids_arr;
-		const mailbox_guid_t *guids;
-		unsigned int count;
-
-		get_mailbox_guids(mailboxes, &guids_arr);
-
-		/* initialize message iteration on both workers */
-		guids = array_get(&guids_arr, &count);
-		sync->src_msg_iter =
-			dsync_brain_msg_iter_init(sync, brain->src_worker,
-						  guids, count);
-		sync->dest_msg_iter =
-			dsync_brain_msg_iter_init(sync, brain->dest_worker,
-						  guids, count);
-	} T_END;
-	return sync;
-}
-
-void dsync_brain_msg_sync_deinit(struct dsync_brain_mailbox_sync **_sync)
-{
-	struct dsync_brain_mailbox_sync *sync = *_sync;
-
-	*_sync = NULL;
-
-	dsync_brain_msg_iter_deinit(sync->src_msg_iter);
-	dsync_brain_msg_iter_deinit(sync->dest_msg_iter);
-	pool_unref(&sync->pool);
-}
--- a/src/dsync/dsync-brain-private.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-#ifndef DSYNC_BRAIN_PRIVATE_H
-#define DSYNC_BRAIN_PRIVATE_H
-
-#include "dsync-data.h"
-#include "dsync-brain.h"
-
-enum dsync_state {
-	DSYNC_STATE_GET_MAILBOXES = 0,
-	DSYNC_STATE_GET_SUBSCRIPTIONS,
-	DSYNC_STATE_SYNC_MAILBOXES,
-	DSYNC_STATE_SYNC_SUBSCRIPTIONS,
-	DSYNC_STATE_SYNC_MSGS,
-	DSYNC_STATE_SYNC_MSGS_FLUSH,
-	DSYNC_STATE_SYNC_MSGS_FLUSH2,
-	DSYNC_STATE_SYNC_UPDATE_MAILBOXES,
-	DSYNC_STATE_SYNC_FLUSH,
-	DSYNC_STATE_SYNC_FLUSH2,
-	DSYNC_STATE_SYNC_END
-};
-
-struct dsync_brain_mailbox_list {
-	pool_t pool;
-	struct dsync_brain *brain;
-	struct dsync_worker *worker;
-	struct dsync_worker_mailbox_iter *iter;
-	ARRAY_TYPE(dsync_mailbox) mailboxes;
-	ARRAY_TYPE(dsync_mailbox) dirs;
-};
-
-struct dsync_brain_subs_list {
-	pool_t pool;
-	struct dsync_brain *brain;
-	struct dsync_worker *worker;
-	struct dsync_worker_subs_iter *iter;
-	ARRAY_DEFINE(subscriptions, struct dsync_worker_subscription);
-	ARRAY_DEFINE(unsubscriptions, struct dsync_worker_unsubscription);
-};
-
-struct dsync_brain_guid_instance {
-	struct dsync_brain_guid_instance *next;
-	uint32_t uid;
-	/* mailbox index in dsync_brain_mailbox_list.mailboxes */
-	unsigned int mailbox_idx:31;
-	unsigned int failed:1;
-};
-
-struct dsync_brain_msg_iter {
-	struct dsync_brain_mailbox_sync *sync;
-	struct dsync_worker *worker;
-
-	struct dsync_worker_msg_iter *iter;
-	struct dsync_message msg;
-
-	unsigned int mailbox_idx;
-
-	/* char *guid -> struct dsync_brain_guid_instance* */
-	struct hash_table *guid_hash;
-
-	ARRAY_DEFINE(new_msgs, struct dsync_brain_new_msg);
-	ARRAY_DEFINE(uid_conflicts, struct dsync_brain_uid_conflict);
-	unsigned int next_new_msg, next_conflict;
-
-	/* copy operations that failed. indexes point to new_msgs array */
-	unsigned int copy_results_left;
-	unsigned int save_results_left;
-
-	unsigned int msgs_sent:1;
-	unsigned int adding_msgs:1;
-};
-
-struct dsync_brain_uid_conflict {
-	uint32_t mailbox_idx;
-	uint32_t old_uid, new_uid;
-};
-
-struct dsync_brain_new_msg {
-	unsigned int mailbox_idx:30;
-	/* TRUE if it currently looks like message has been saved/copied.
-	   if copying fails, it sets this back to FALSE and updates
-	   iter->next_new_msg. */
-	unsigned int saved:1;
-	uint32_t orig_uid;
-	struct dsync_message *msg;
-};
-
-struct dsync_brain_mailbox {
-	struct dsync_mailbox box;
-	struct dsync_mailbox *src;
-	struct dsync_mailbox *dest;
-};
-ARRAY_DEFINE_TYPE(dsync_brain_mailbox, struct dsync_brain_mailbox);
-
-struct dsync_brain_mailbox_sync {
-	struct dsync_brain *brain;
-	pool_t pool;
-
-	ARRAY_TYPE(dsync_brain_mailbox) mailboxes;
-	unsigned int wanted_mailbox_idx;
-
-	struct dsync_worker *src_worker;
-	struct dsync_worker *dest_worker;
-
-	struct dsync_brain_msg_iter *src_msg_iter;
-	struct dsync_brain_msg_iter *dest_msg_iter;
-
-	unsigned int uid_conflict:1;
-	unsigned int skip_mailbox:1;
-};
-
-struct dsync_brain {
-	struct dsync_worker *src_worker;
-	struct dsync_worker *dest_worker;
-	char *mailbox;
-	enum dsync_brain_flags flags;
-
-	enum dsync_state state;
-
-	struct dsync_brain_mailbox_list *src_mailbox_list;
-	struct dsync_brain_mailbox_list *dest_mailbox_list;
-
-	struct dsync_brain_subs_list *src_subs_list;
-	struct dsync_brain_subs_list *dest_subs_list;
-
-	struct dsync_brain_mailbox_sync *mailbox_sync;
-	struct timeout *to;
-
-	unsigned int failed:1;
-	unsigned int verbose:1;
-	unsigned int backup:1;
-	unsigned int unexpected_changes:1;
-	unsigned int stdout_tty:1;
-};
-
-void dsync_brain_fail(struct dsync_brain *brain);
-
-struct dsync_brain_mailbox_sync *
-dsync_brain_msg_sync_init(struct dsync_brain *brain,
-			  const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes);
-void dsync_brain_msg_sync_more(struct dsync_brain_mailbox_sync *sync);
-void dsync_brain_msg_sync_deinit(struct dsync_brain_mailbox_sync **_sync);
-
-void dsync_brain_msg_sync_new_msgs(struct dsync_brain_mailbox_sync *sync);
-
-#endif
--- a/src/dsync/dsync-brain.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,918 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "hash.h"
-#include "master-service.h"
-#include "dsync-worker.h"
-#include "dsync-brain-private.h"
-
-#include <unistd.h>
-
-#define DSYNC_WRONG_DIRECTION_ERROR_MSG \
-	"dsync backup: " \
-	"Looks like you're trying to run backup in wrong direction. " \
-	"Source is empty and destination is not."
-
-static void
-dsync_brain_mailbox_list_deinit(struct dsync_brain_mailbox_list **list);
-static void
-dsync_brain_subs_list_deinit(struct dsync_brain_subs_list **list);
-
-struct dsync_brain *
-dsync_brain_init(struct dsync_worker *src_worker,
-		 struct dsync_worker *dest_worker,
-		 const char *mailbox, enum dsync_brain_flags flags)
-{
-	struct dsync_brain *brain;
-
-	brain = i_new(struct dsync_brain, 1);
-	brain->src_worker = src_worker;
-	brain->dest_worker = dest_worker;
-	brain->mailbox = i_strdup(mailbox);
-	brain->flags = flags;
-	brain->verbose = (flags & DSYNC_BRAIN_FLAG_VERBOSE) != 0;
-	brain->backup = (flags & DSYNC_BRAIN_FLAG_BACKUP) != 0;
-	brain->stdout_tty = isatty(STDOUT_FILENO) > 0;
-
-	if ((flags & DSYNC_BRAIN_FLAG_VERBOSE) != 0) {
-		dsync_worker_set_verbose(src_worker);
-		dsync_worker_set_verbose(dest_worker);
-	}
-	return brain;
-}
-
-void dsync_brain_fail(struct dsync_brain *brain)
-{
-	brain->failed = TRUE;
-	master_service_stop(master_service);
-}
-
-int dsync_brain_deinit(struct dsync_brain **_brain)
-{
-	struct dsync_brain *brain = *_brain;
-	int ret = brain->failed ? -1 : 0;
-
-	if (brain->state != DSYNC_STATE_SYNC_END)
-		ret = -1;
-	if (brain->to != NULL)
-		timeout_remove(&brain->to);
-
-	if (ret < 0) {
-		/* make sure we unreference save input streams before workers
-		   are deinitialized, so they can destroy the streams */
-		dsync_worker_msg_save_cancel(brain->src_worker);
-		dsync_worker_msg_save_cancel(brain->dest_worker);
-	}
-
-	if (brain->mailbox_sync != NULL)
-		dsync_brain_msg_sync_deinit(&brain->mailbox_sync);
-
-	if (brain->src_mailbox_list != NULL)
-		dsync_brain_mailbox_list_deinit(&brain->src_mailbox_list);
-	if (brain->dest_mailbox_list != NULL)
-		dsync_brain_mailbox_list_deinit(&brain->dest_mailbox_list);
-
-	if (brain->src_subs_list != NULL)
-		dsync_brain_subs_list_deinit(&brain->src_subs_list);
-	if (brain->dest_subs_list != NULL)
-		dsync_brain_subs_list_deinit(&brain->dest_subs_list);
-
-	if (dsync_worker_has_failed(brain->src_worker) ||
-	    dsync_worker_has_failed(brain->dest_worker))
-		ret = -1;
-
-	*_brain = NULL;
-	i_free(brain->mailbox);
-	i_free(brain);
-	return ret;
-}
-
-static void dsync_brain_mailbox_list_finished(struct dsync_brain *brain)
-{
-	if (brain->src_mailbox_list->iter != NULL ||
-	    brain->dest_mailbox_list->iter != NULL)
-		return;
-
-	/* both lists are finished */
-	brain->state++;
-	dsync_brain_sync(brain);
-}
-
-static void dsync_worker_mailbox_input(void *context)
-{
-	struct dsync_brain_mailbox_list *list = context;
-	struct dsync_mailbox dsync_box, *dup_box;
-	int ret;
-
-	while ((ret = dsync_worker_mailbox_iter_next(list->iter,
-						     &dsync_box)) > 0) {
-		if (list->brain->mailbox != NULL &&
-		    strcmp(list->brain->mailbox, dsync_box.name) != 0)
-			continue;
-
-		dup_box = dsync_mailbox_dup(list->pool, &dsync_box);
-		if (!dsync_mailbox_is_noselect(dup_box))
-			array_append(&list->mailboxes, &dup_box, 1);
-		else
-			array_append(&list->dirs, &dup_box, 1);
-	}
-	if (ret < 0) {
-		/* finished listing mailboxes */
-		if (dsync_worker_mailbox_iter_deinit(&list->iter) < 0)
-			dsync_brain_fail(list->brain);
-		array_sort(&list->mailboxes, dsync_mailbox_p_guid_cmp);
-		array_sort(&list->dirs, dsync_mailbox_p_name_sha1_cmp);
-		dsync_brain_mailbox_list_finished(list->brain);
-	}
-}
-
-static struct dsync_brain_mailbox_list *
-dsync_brain_mailbox_list_init(struct dsync_brain *brain,
-			      struct dsync_worker *worker)
-{
-	struct dsync_brain_mailbox_list *list;
-	pool_t pool;
-
-	pool = pool_alloconly_create("dsync brain mailbox list", 10240);
-	list = p_new(pool, struct dsync_brain_mailbox_list, 1);
-	list->pool = pool;
-	list->brain = brain;
-	list->worker = worker;
-	list->iter = dsync_worker_mailbox_iter_init(worker);
-	p_array_init(&list->mailboxes, pool, 128);
-	p_array_init(&list->dirs, pool, 32);
-	dsync_worker_set_input_callback(worker, dsync_worker_mailbox_input,
-					list);
-	return list;
-}
-
-static void
-dsync_brain_mailbox_list_deinit(struct dsync_brain_mailbox_list **_list)
-{
-	struct dsync_brain_mailbox_list *list = *_list;
-
-	*_list = NULL;
-
-	if (list->iter != NULL)
-		(void)dsync_worker_mailbox_iter_deinit(&list->iter);
-	pool_unref(&list->pool);
-}
-
-static void dsync_brain_subs_list_finished(struct dsync_brain *brain)
-{
-	if (brain->src_subs_list->iter != NULL ||
-	    brain->dest_subs_list->iter != NULL)
-		return;
-
-	/* both lists are finished */
-	brain->state++;
-	dsync_brain_sync(brain);
-}
-
-static int
-dsync_worker_subscription_cmp(const struct dsync_worker_subscription *s1,
-			      const struct dsync_worker_subscription *s2)
-{
-	return strcmp(s1->vname, s2->vname);
-}
-
-static int
-dsync_worker_unsubscription_cmp(const struct dsync_worker_unsubscription *u1,
-				const struct dsync_worker_unsubscription *u2)
-{
-	int ret;
-
-	ret = strcmp(u1->ns_prefix, u2->ns_prefix);
-	return ret != 0 ? ret :
-		dsync_guid_cmp(&u1->name_sha1, &u2->name_sha1);
-}
-
-static void dsync_worker_subs_input(void *context)
-{
-	struct dsync_brain_subs_list *list = context;
-	struct dsync_worker_subscription subs;
-	struct dsync_worker_unsubscription unsubs;
-	int ret;
-
-	memset(&subs, 0, sizeof(subs));
-	while ((ret = dsync_worker_subs_iter_next(list->iter, &subs)) > 0) {
-		subs.vname = p_strdup(list->pool, subs.vname);
-		subs.storage_name = p_strdup(list->pool, subs.storage_name);
-		subs.ns_prefix = p_strdup(list->pool, subs.ns_prefix);
-		array_append(&list->subscriptions, &subs, 1);
-	}
-	if (ret == 0)
-		return;
-
-	memset(&unsubs, 0, sizeof(unsubs));
-	while ((ret = dsync_worker_subs_iter_next_un(list->iter,
-						     &unsubs)) > 0) {
-		unsubs.ns_prefix = p_strdup(list->pool, unsubs.ns_prefix);
-		array_append(&list->unsubscriptions, &unsubs, 1);
-	}
-
-	if (ret < 0) {
-		/* finished listing subscriptions */
-		if (dsync_worker_subs_iter_deinit(&list->iter) < 0)
-			dsync_brain_fail(list->brain);
-		array_sort(&list->subscriptions,
-			   dsync_worker_subscription_cmp);
-		array_sort(&list->unsubscriptions,
-			   dsync_worker_unsubscription_cmp);
-		dsync_brain_subs_list_finished(list->brain);
-	}
-}
-
-static struct dsync_brain_subs_list *
-dsync_brain_subs_list_init(struct dsync_brain *brain,
-			      struct dsync_worker *worker)
-{
-	struct dsync_brain_subs_list *list;
-	pool_t pool;
-
-	pool = pool_alloconly_create("dsync brain subs list", 1024*4);
-	list = p_new(pool, struct dsync_brain_subs_list, 1);
-	list->pool = pool;
-	list->brain = brain;
-	list->worker = worker;
-	list->iter = dsync_worker_subs_iter_init(worker);
-	p_array_init(&list->subscriptions, pool, 128);
-	p_array_init(&list->unsubscriptions, pool, 64);
-	dsync_worker_set_input_callback(worker, dsync_worker_subs_input, list);
-	return list;
-}
-
-static void
-dsync_brain_subs_list_deinit(struct dsync_brain_subs_list **_list)
-{
-	struct dsync_brain_subs_list *list = *_list;
-
-	*_list = NULL;
-
-	if (list->iter != NULL)
-		(void)dsync_worker_subs_iter_deinit(&list->iter);
-	pool_unref(&list->pool);
-}
-
-enum dsync_brain_mailbox_action {
-	DSYNC_BRAIN_MAILBOX_ACTION_NONE,
-	DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
-	DSYNC_BRAIN_MAILBOX_ACTION_DELETE
-};
-
-static void
-dsync_brain_mailbox_action(struct dsync_brain *brain,
-			   enum dsync_brain_mailbox_action action,
-			   struct dsync_worker *action_worker,
-			   struct dsync_mailbox *action_box)
-{
-	struct dsync_mailbox new_box;
-
-	if (brain->backup && action_worker == brain->src_worker) {
-		/* backup mode: switch actions */
-		action_worker = brain->dest_worker;
-		switch (action) {
-		case DSYNC_BRAIN_MAILBOX_ACTION_NONE:
-			break;
-		case DSYNC_BRAIN_MAILBOX_ACTION_CREATE:
-			action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
-			break;
-		case DSYNC_BRAIN_MAILBOX_ACTION_DELETE:
-			action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
-			break;
-		}
-	}
-
-	switch (action) {
-	case DSYNC_BRAIN_MAILBOX_ACTION_NONE:
-		break;
-	case DSYNC_BRAIN_MAILBOX_ACTION_CREATE:
-		new_box = *action_box;
-		new_box.uid_next = action_box->uid_validity == 0 ? 0 : 1;
-		new_box.first_recent_uid = 0;
-		new_box.highest_modseq = 0;
-		dsync_worker_create_mailbox(action_worker, &new_box);
-		break;
-	case DSYNC_BRAIN_MAILBOX_ACTION_DELETE:
-		if (!dsync_mailbox_is_noselect(action_box))
-			dsync_worker_delete_mailbox(action_worker, action_box);
-		else
-			dsync_worker_delete_dir(action_worker, action_box);
-		break;
-	}
-}
-
-static bool
-dsync_mailbox_list_is_empty(const ARRAY_TYPE(dsync_mailbox) *boxes_arr)
-{
-	struct dsync_mailbox *const *boxes;
-	unsigned int count;
-
-	boxes = array_get(boxes_arr, &count);
-	if (count == 0)
-		return TRUE;
-	if (count == 1 && strcasecmp(boxes[0]->name, "INBOX") == 0 &&
-	    boxes[0]->message_count == 0)
-		return TRUE;
-	return FALSE;
-}
-
-static void dsync_brain_sync_mailboxes(struct dsync_brain *brain)
-{
-	struct dsync_mailbox *const *src_boxes, *const *dest_boxes;
-	struct dsync_mailbox *action_box = NULL;
-	struct dsync_worker *action_worker = NULL;
-	unsigned int src, dest, src_count, dest_count;
-	enum dsync_brain_mailbox_action action;
-	bool src_deleted, dest_deleted;
-	int ret;
-
-	if (brain->backup &&
-	    dsync_mailbox_list_is_empty(&brain->src_mailbox_list->mailboxes) &&
-	    !dsync_mailbox_list_is_empty(&brain->dest_mailbox_list->mailboxes)) {
-		i_fatal(DSYNC_WRONG_DIRECTION_ERROR_MSG);
-	}
-
-	/* create/delete missing mailboxes. the mailboxes are sorted by
-	   GUID, so we can do this quickly. */
-	src_boxes = array_get(&brain->src_mailbox_list->mailboxes, &src_count);
-	dest_boxes = array_get(&brain->dest_mailbox_list->mailboxes, &dest_count);
-	for (src = dest = 0; src < src_count && dest < dest_count; ) {
-		action = DSYNC_BRAIN_MAILBOX_ACTION_NONE;
-		src_deleted = (src_boxes[src]->flags &
-			       DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
-		dest_deleted = (dest_boxes[dest]->flags &
-				DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
-		ret = dsync_mailbox_guid_cmp(src_boxes[src],
-					     dest_boxes[dest]);
-		if (ret < 0) {
-			/* exists only in source */
-			if (!src_deleted) {
-				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
-				action_worker = brain->dest_worker;
-				action_box = src_boxes[src];
-			}
-			src++;
-		} else if (ret > 0) {
-			/* exists only in dest */
-			if (!dest_deleted) {
-				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
-				action_worker = brain->src_worker;
-				action_box = dest_boxes[dest];
-			}
-			dest++;
-		} else if (src_deleted) {
-			/* delete from dest too */
-			if (!dest_deleted) {
-				action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
-				action_worker = brain->dest_worker;
-				action_box = dest_boxes[dest];
-			}
-			src++; dest++;
-		} else if (dest_deleted) {
-			/* delete from src too */
-			action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
-			action_worker = brain->src_worker;
-			action_box = src_boxes[src];
-			src++; dest++;
-		} else {
-			src++; dest++;
-		}
-		dsync_brain_mailbox_action(brain, action,
-					   action_worker, action_box);
-	}
-	for (; src < src_count; src++) {
-		if ((src_boxes[src]->flags &
-		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
-			continue;
-
-		dsync_brain_mailbox_action(brain,
-			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
-			brain->dest_worker, src_boxes[src]);
-	}
-	for (; dest < dest_count; dest++) {
-		if ((dest_boxes[dest]->flags &
-		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
-			continue;
-
-		dsync_brain_mailbox_action(brain,
-			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
-			brain->src_worker, dest_boxes[dest]);
-	}
-}
-
-static void dsync_brain_sync_dirs(struct dsync_brain *brain)
-{
-	struct dsync_mailbox *const *src_boxes, *const *dest_boxes, *action_box;
-	unsigned int src, dest, src_count, dest_count;
-	enum dsync_brain_mailbox_action action;
-	struct dsync_worker *action_worker = NULL;
-	bool src_deleted, dest_deleted;
-	int ret;
-
-	/* create/delete missing directories. */
-	src_boxes = array_get(&brain->src_mailbox_list->dirs, &src_count);
-	dest_boxes = array_get(&brain->dest_mailbox_list->dirs, &dest_count);
-	for (src = dest = 0; src < src_count && dest < dest_count; ) {
-		action = DSYNC_BRAIN_MAILBOX_ACTION_NONE;
-		action_box = NULL;
-
-		src_deleted = (src_boxes[src]->flags &
-			       DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0;
-		dest_deleted = (dest_boxes[dest]->flags &
-				DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0;
-		ret = memcmp(src_boxes[src]->name_sha1.guid,
-			     dest_boxes[dest]->name_sha1.guid,
-			     sizeof(src_boxes[src]->name_sha1.guid));
-		if (ret < 0) {
-			/* exists only in source */
-			if (!src_deleted) {
-				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
-				action_worker = brain->dest_worker;
-				action_box = src_boxes[src];
-			}
-			src++;
-		} else if (ret > 0) {
-			/* exists only in dest */
-			if (!dest_deleted) {
-				action = DSYNC_BRAIN_MAILBOX_ACTION_CREATE;
-				action_worker = brain->src_worker;
-				action_box = dest_boxes[dest];
-			}
-			dest++;
-		} else if (src_deleted) {
-			/* delete from dest too */
-			if (!dest_deleted) {
-				action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
-				action_worker = brain->dest_worker;
-				action_box = dest_boxes[dest];
-			}
-			src++; dest++;
-		} else if (dest_deleted) {
-			/* delete from src too */
-			action = DSYNC_BRAIN_MAILBOX_ACTION_DELETE;
-			action_worker = brain->src_worker;
-			action_box = src_boxes[src];
-			src++; dest++;
-		} else {
-			src++; dest++;
-		}
-		i_assert(action_box == NULL ||
-			 dsync_mailbox_is_noselect(action_box));
-		dsync_brain_mailbox_action(brain, action,
-					   action_worker, action_box);
-	}
-	for (; src < src_count; src++) {
-		if ((src_boxes[src]->flags &
-		     DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0)
-			continue;
-
-		dsync_brain_mailbox_action(brain,
-			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
-			brain->dest_worker, src_boxes[src]);
-	}
-	for (; dest < dest_count; dest++) {
-		if ((dest_boxes[dest]->flags &
-		     DSYNC_MAILBOX_FLAG_DELETED_DIR) != 0)
-			continue;
-
-		dsync_brain_mailbox_action(brain,
-			DSYNC_BRAIN_MAILBOX_ACTION_CREATE,
-			brain->src_worker, dest_boxes[dest]);
-	}
-}
-
-static bool
-dsync_brain_is_unsubscribed(struct dsync_brain_subs_list *list,
-			    const struct dsync_worker_subscription *subs,
-			    time_t *last_change_r)
-{
-	const struct dsync_worker_unsubscription *unsubs;
-	struct dsync_worker_unsubscription lookup;
-
-	lookup.ns_prefix = subs->ns_prefix;
-	dsync_str_sha_to_guid(subs->storage_name, &lookup.name_sha1);
-	unsubs = array_bsearch(&list->unsubscriptions, &lookup,
-			       dsync_worker_unsubscription_cmp);
-	if (unsubs == NULL) {
-		*last_change_r = 0;
-		return FALSE;
-	} else if (unsubs->last_change <= subs->last_change) {
-		*last_change_r = subs->last_change;
-		return FALSE;
-	} else {
-		*last_change_r = unsubs->last_change;
-		return TRUE;
-	}
-}
-
-static void dsync_brain_sync_subscriptions(struct dsync_brain *brain)
-{
-	const struct dsync_worker_subscription *src_subs, *dest_subs;
-	const struct dsync_worker_subscription *action_subs;
-	struct dsync_worker *action_worker;
-	unsigned int src, dest, src_count, dest_count;
-	time_t last_change;
-	bool subscribe;
-	int ret;
-
-	/* subscriptions are sorted by name. */
-	src_subs = array_get(&brain->src_subs_list->subscriptions, &src_count);
-	dest_subs = array_get(&brain->dest_subs_list->subscriptions, &dest_count);
-	for (src = dest = 0;; ) {
-		if (src == src_count) {
-			if (dest == dest_count)
-				break;
-			ret = 1;
-		} else if (dest == dest_count) {
-			ret = -1;
-		} else {
-			ret = strcmp(src_subs[src].vname,
-				     dest_subs[dest].vname);
-			if (ret == 0) {
-				src++; dest++;
-				continue;
-			}
-		}
-
-		if (ret < 0) {
-			/* subscribed only in source */
-			action_subs = &src_subs[src];
-			if (dsync_brain_is_unsubscribed(brain->dest_subs_list,
-							&src_subs[src],
-							&last_change)) {
-				action_worker = brain->src_worker;
-				subscribe = FALSE;
-			} else {
-				action_worker = brain->dest_worker;
-				subscribe = TRUE;
-			}
-			src++;
-		} else {
-			/* subscribed only in dest */
-			action_subs = &dest_subs[dest];
-			if (dsync_brain_is_unsubscribed(brain->src_subs_list,
-							&dest_subs[dest],
-							&last_change)) {
-				action_worker = brain->dest_worker;
-				subscribe = FALSE;
-			} else {
-				action_worker = brain->src_worker;
-				subscribe = TRUE;
-			}
-			dest++;
-		}
-
-		if (brain->backup && action_worker == brain->src_worker) {
-			/* backup mode: switch action */
-			action_worker = brain->dest_worker;
-			subscribe = !subscribe;
-			last_change = ioloop_time;
-		}
-		dsync_worker_set_subscribed(action_worker, action_subs->vname,
-					    last_change, subscribe);
-	}
-}
-
-static bool dsync_mailbox_has_changed_msgs(struct dsync_brain *brain,
-					   const struct dsync_mailbox *box1,
-					   const struct dsync_mailbox *box2)
-{
-	const char *name = *box1->name != '\0' ? box1->name : box2->name;
-
-	if (box1->uid_validity != box2->uid_validity) {
-		if (brain->verbose) {
-			i_info("%s: uidvalidity changed: %u != %u", name,
-			       box1->uid_validity, box2->uid_validity);
-		}
-		return TRUE;
-	}
-	if (box1->uid_next != box2->uid_next) {
-		if (brain->verbose) {
-			i_info("%s: uidnext changed: %u != %u", name,
-			       box1->uid_next, box2->uid_next);
-		}
-		return TRUE;
-	}
-	if (box1->highest_modseq != box2->highest_modseq) {
-		if (brain->verbose) {
-			i_info("%s: highest_modseq changed: %llu != %llu", name,
-			       (unsigned long long)box1->highest_modseq,
-			       (unsigned long long)box2->highest_modseq);
-		}
-		return TRUE;
-	}
-	if (box1->message_count != box2->message_count) {
-		if (brain->verbose) {
-			i_info("%s: message_count changed: %u != %u", name,
-			       box1->message_count, box2->message_count);
-		}
-		return TRUE;
-	}
-	return FALSE;
-}
-
-static bool dsync_mailbox_has_changes(struct dsync_brain *brain,
-				      const struct dsync_mailbox *box1,
-				      const struct dsync_mailbox *box2)
-{
-	if (strcmp(box1->name, box2->name) != 0)
-		return TRUE;
-	return dsync_mailbox_has_changed_msgs(brain, box1, box2);
-}
-
-static void
-dsync_brain_get_changed_mailboxes(struct dsync_brain *brain,
-				  ARRAY_TYPE(dsync_brain_mailbox) *brain_boxes,
-				  bool full_sync)
-{
-	struct dsync_mailbox *const *src_boxes, *const *dest_boxes;
-	struct dsync_brain_mailbox *brain_box;
-	unsigned int src, dest, src_count, dest_count;
-	bool src_deleted, dest_deleted;
-	int ret;
-
-	src_boxes = array_get(&brain->src_mailbox_list->mailboxes, &src_count);
-	dest_boxes = array_get(&brain->dest_mailbox_list->mailboxes, &dest_count);
-
-	for (src = dest = 0; src < src_count && dest < dest_count; ) {
-		src_deleted = (src_boxes[src]->flags &
-			       DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
-		dest_deleted = (dest_boxes[dest]->flags &
-				DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0;
-
-		ret = dsync_mailbox_guid_cmp(src_boxes[src], dest_boxes[dest]);
-		if (ret == 0) {
-			if ((full_sync ||
-			     dsync_mailbox_has_changes(brain, src_boxes[src],
-						       dest_boxes[dest])) &&
-			    !src_deleted && !dest_deleted) {
-				brain_box = array_append_space(brain_boxes);
-				brain_box->box = *src_boxes[src];
-
-				brain_box->box.highest_modseq =
-					I_MAX(src_boxes[src]->highest_modseq,
-					      dest_boxes[dest]->highest_modseq);
-				brain_box->box.uid_next =
-					I_MAX(src_boxes[src]->uid_next,
-					      dest_boxes[dest]->uid_next);
-				brain_box->src = src_boxes[src];
-				brain_box->dest = dest_boxes[dest];
-			}
-			src++; dest++;
-		} else if (ret < 0) {
-			/* exists only in source */
-			if (!src_deleted) {
-				brain_box = array_append_space(brain_boxes);
-				brain_box->box = *src_boxes[src];
-				brain_box->src = src_boxes[src];
-				if (brain->verbose) {
-					i_info("%s: only in source (guid=%s)",
-					       brain_box->box.name,
-					       dsync_guid_to_str(&brain_box->box.mailbox_guid));
-				}
-			}
- 			src++;
-		} else {
-			/* exists only in dest */
-			if (!dest_deleted) {
-				brain_box = array_append_space(brain_boxes);
-				brain_box->box = *dest_boxes[dest];
-				brain_box->dest = dest_boxes[dest];
-				if (brain->verbose) {
-					i_info("%s: only in dest (guid=%s)",
-					       brain_box->box.name,
-					       dsync_guid_to_str(&brain_box->box.mailbox_guid));
-				}
-			}
-			dest++;
-		}
-	}
-	for (; src < src_count; src++) {
-		if ((src_boxes[src]->flags &
-		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
-			continue;
-
-		brain_box = array_append_space(brain_boxes);
-		brain_box->box = *src_boxes[src];
-		brain_box->src = src_boxes[src];
-		if (brain->verbose) {
-			i_info("%s: only in source (guid=%s)",
-			       brain_box->box.name,
-			       dsync_guid_to_str(&brain_box->box.mailbox_guid));
-		}
-	}
-	for (; dest < dest_count; dest++) {
-		if ((dest_boxes[dest]->flags &
-		     DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0)
-			continue;
-
-		brain_box = array_append_space(brain_boxes);
-		brain_box->box = *dest_boxes[dest];
-		brain_box->dest = dest_boxes[dest];
-		if (brain->verbose) {
-			i_info("%s: only in dest (guid=%s)",
-			       brain_box->box.name,
-			       dsync_guid_to_str(&brain_box->box.mailbox_guid));
-		}
-	}
-}
-
-static bool dsync_brain_sync_msgs(struct dsync_brain *brain)
-{
-	ARRAY_TYPE(dsync_brain_mailbox) mailboxes;
-	pool_t pool;
-	bool ret;
-
-	pool = pool_alloconly_create("dsync changed mailboxes", 10240);
-	p_array_init(&mailboxes, pool, 128);
-	dsync_brain_get_changed_mailboxes(brain, &mailboxes,
-		(brain->flags & DSYNC_BRAIN_FLAG_FULL_SYNC) != 0);
-	if (array_count(&mailboxes) > 0) {
-		brain->mailbox_sync =
-			dsync_brain_msg_sync_init(brain, &mailboxes);
-		dsync_brain_msg_sync_more(brain->mailbox_sync);
-		ret = TRUE;
-	} else {
-		ret = FALSE;
-	}
-	pool_unref(&pool);
-	return ret;
-}
-
-static void
-dsync_brain_sync_rename_mailbox(struct dsync_brain *brain,
-				const struct dsync_brain_mailbox *mailbox)
-{
-	if (mailbox->src->last_change > mailbox->dest->last_change ||
-	    brain->backup) {
-		dsync_worker_rename_mailbox(brain->dest_worker,
-					    &mailbox->box.mailbox_guid,
-					    mailbox->src);
-	} else {
-		dsync_worker_rename_mailbox(brain->src_worker,
-					    &mailbox->box.mailbox_guid,
-					    mailbox->dest);
-	}
-}
-
-static void
-dsync_brain_sync_update_mailboxes(struct dsync_brain *brain)
-{
-	const struct dsync_brain_mailbox *mailbox;
-	bool failed_changes = dsync_brain_has_unexpected_changes(brain) ||
-		dsync_worker_has_failed(brain->src_worker) ||
-		dsync_worker_has_failed(brain->dest_worker);
-
-	if (brain->mailbox_sync == NULL) {
-		/* no mailboxes changed */
-		return;
-	}
-
-	array_foreach(&brain->mailbox_sync->mailboxes, mailbox) {
-		/* don't update mailboxes if any changes had failed.
-		   for example if some messages couldn't be saved, we don't
-		   want to increase the next_uid to jump over them */
-		if (!brain->backup && !failed_changes) {
-			dsync_worker_update_mailbox(brain->src_worker,
-						    &mailbox->box);
-		}
-		if (!failed_changes) {
-			dsync_worker_update_mailbox(brain->dest_worker,
-						    &mailbox->box);
-		}
-
-		if (mailbox->src != NULL && mailbox->dest != NULL &&
-		    strcmp(mailbox->src->name, mailbox->dest->name) != 0)
-			dsync_brain_sync_rename_mailbox(brain, mailbox);
-	}
-}
-
-static void dsync_brain_worker_finished(bool success, void *context)
-{
-	struct dsync_brain *brain = context;
-
-	switch (brain->state) {
-	case DSYNC_STATE_SYNC_MSGS_FLUSH:
-	case DSYNC_STATE_SYNC_MSGS_FLUSH2:
-	case DSYNC_STATE_SYNC_FLUSH:
-	case DSYNC_STATE_SYNC_FLUSH2:
-		break;
-	default:
-		i_panic("dsync brain state=%d", brain->state);
-	}
-
-	if (!success)
-		dsync_brain_fail(brain);
-
-	brain->state++;
-	if (brain->to == NULL && (brain->flags & DSYNC_BRAIN_FLAG_LOCAL) == 0)
-		brain->to = timeout_add(0, dsync_brain_sync, brain);
-}
-
-void dsync_brain_sync(struct dsync_brain *brain)
-{
-	if (dsync_worker_has_failed(brain->src_worker) ||
-	    dsync_worker_has_failed(brain->dest_worker)) {
-		/* we can't safely continue, especially with backup */
-		return;
-	}
-
-	if (brain->to != NULL)
-		timeout_remove(&brain->to);
-	switch (brain->state) {
-	case DSYNC_STATE_GET_MAILBOXES:
-		i_assert(brain->src_mailbox_list == NULL);
-		brain->src_mailbox_list =
-			dsync_brain_mailbox_list_init(brain, brain->src_worker);
-		brain->dest_mailbox_list =
-			dsync_brain_mailbox_list_init(brain, brain->dest_worker);
-		dsync_worker_mailbox_input(brain->src_mailbox_list);
-		dsync_worker_mailbox_input(brain->dest_mailbox_list);
-		break;
-	case DSYNC_STATE_GET_SUBSCRIPTIONS:
-		i_assert(brain->src_subs_list == NULL);
-		brain->src_subs_list =
-			dsync_brain_subs_list_init(brain, brain->src_worker);
-		brain->dest_subs_list =
-			dsync_brain_subs_list_init(brain, brain->dest_worker);
-		dsync_worker_subs_input(brain->src_subs_list);
-		dsync_worker_subs_input(brain->dest_subs_list);
-		break;
-	case DSYNC_STATE_SYNC_MAILBOXES:
-		dsync_worker_set_input_callback(brain->src_worker, NULL, NULL);
-		dsync_worker_set_input_callback(brain->dest_worker, NULL, NULL);
-
-		dsync_brain_sync_mailboxes(brain);
-		dsync_brain_sync_dirs(brain);
-		brain->state++;
-		/* fall through */
-	case DSYNC_STATE_SYNC_SUBSCRIPTIONS:
-		dsync_brain_sync_subscriptions(brain);
-		brain->state++;
-		/* fall through */
-	case DSYNC_STATE_SYNC_MSGS:
-		if (dsync_brain_sync_msgs(brain))
-			break;
-		brain->state++;
-		/* no mailboxes changed */
-	case DSYNC_STATE_SYNC_MSGS_FLUSH:
-		/* wait until all saves are done, so we don't try to close
-		   the mailbox too early */
-		dsync_worker_finish(brain->src_worker,
-				    dsync_brain_worker_finished, brain);
-		dsync_worker_finish(brain->dest_worker,
-				    dsync_brain_worker_finished, brain);
-		break;
-	case DSYNC_STATE_SYNC_MSGS_FLUSH2:
-		break;
-	case DSYNC_STATE_SYNC_UPDATE_MAILBOXES:
-		dsync_brain_sync_update_mailboxes(brain);
-		brain->state++;
-		/* fall through */
-	case DSYNC_STATE_SYNC_FLUSH:
-		dsync_worker_finish(brain->src_worker,
-				    dsync_brain_worker_finished, brain);
-		dsync_worker_finish(brain->dest_worker,
-				    dsync_brain_worker_finished, brain);
-		break;
-	case DSYNC_STATE_SYNC_FLUSH2:
-		break;
-	case DSYNC_STATE_SYNC_END:
-		master_service_stop(master_service);
-		break;
-	default:
-		i_unreached();
-	}
-}
-
-void dsync_brain_sync_all(struct dsync_brain *brain)
-{
-	enum dsync_state old_state;
-
-	while (brain->state != DSYNC_STATE_SYNC_END) {
-		old_state = brain->state;
-		dsync_brain_sync(brain);
-
-		if (dsync_worker_has_failed(brain->src_worker) ||
-		    dsync_worker_has_failed(brain->dest_worker))
-			break;
-
-		i_assert(brain->state != old_state);
-	}
-}
-
-bool dsync_brain_has_unexpected_changes(struct dsync_brain *brain)
-{
-	return brain->unexpected_changes ||
-		dsync_worker_has_unexpected_changes(brain->src_worker) ||
-		dsync_worker_has_unexpected_changes(brain->dest_worker);
-}
-
-bool dsync_brain_has_failed(struct dsync_brain *brain)
-{
-	return brain->failed ||
-		dsync_worker_has_failed(brain->src_worker) ||
-		dsync_worker_has_failed(brain->dest_worker);
-}
--- a/src/dsync/dsync-brain.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-#ifndef DSYNC_BRAIN_H
-#define DSYNC_BRAIN_H
-
-enum dsync_brain_flags {
-	DSYNC_BRAIN_FLAG_FULL_SYNC	= 0x01,
-	DSYNC_BRAIN_FLAG_VERBOSE	= 0x02,
-	/* Run in backup mode. All changes from src are forced into dest,
-	   discarding any potential changes in dest. */
-	DSYNC_BRAIN_FLAG_BACKUP		= 0x04,
-	/* Run in "local mode". Don't use ioloop. */
-	DSYNC_BRAIN_FLAG_LOCAL		= 0x08
-};
-
-struct dsync_worker;
-
-struct dsync_brain *
-dsync_brain_init(struct dsync_worker *src_worker,
-		 struct dsync_worker *dest_worker,
-		 const char *mailbox, enum dsync_brain_flags flags);
-int dsync_brain_deinit(struct dsync_brain **brain);
-
-void dsync_brain_sync(struct dsync_brain *brain);
-void dsync_brain_sync_all(struct dsync_brain *brain);
-
-bool dsync_brain_has_unexpected_changes(struct dsync_brain *brain);
-bool dsync_brain_has_failed(struct dsync_brain *brain);
-
-#endif
--- a/src/dsync/dsync-data.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "hex-binary.h"
-#include "sha1.h"
-#include "dsync-data.h"
-
-struct dsync_mailbox *
-dsync_mailbox_dup(pool_t pool, const struct dsync_mailbox *box)
-{
-	struct dsync_mailbox *dest;
-	const struct mailbox_cache_field *cache_fields = NULL;
-	struct mailbox_cache_field *dup;
-	unsigned int i, count = 0;
-
-	dest = p_new(pool, struct dsync_mailbox, 1);
-	*dest = *box;
-	dest->name = p_strdup(pool, box->name);
-
-	if (array_is_created(&box->cache_fields))
-		cache_fields = array_get(&box->cache_fields, &count);
-	if (count == 0)
-		memset(&dest->cache_fields, 0, sizeof(dest->cache_fields));
-	else {
-		p_array_init(&dest->cache_fields, pool, count);
-		for (i = 0; i < count; i++) {
-			dup = array_append_space(&dest->cache_fields);
-			*dup = cache_fields[i];
-			dup->name = p_strdup(pool, dup->name);
-		}
-	}
-	return dest;
-}
-
-struct dsync_message *
-dsync_message_dup(pool_t pool, const struct dsync_message *msg)
-{
-	struct dsync_message *dest;
-	const char **keywords;
-	unsigned int i, count;
-
-	dest = p_new(pool, struct dsync_message, 1);
-	*dest = *msg;
-	dest->guid = p_strdup(pool, msg->guid);
-	if (msg->keywords != NULL) {
-		count = str_array_length(msg->keywords);
-		keywords = p_new(pool, const char *, count+1);
-		for (i = 0; i < count; i++)
-			keywords[i] = p_strdup(pool, msg->keywords[i]);
-		dest->keywords = keywords;
-	}
-	return dest;
-}
-
-int dsync_mailbox_guid_cmp(const struct dsync_mailbox *box1,
-			   const struct dsync_mailbox *box2)
-{
-	return memcmp(box1->mailbox_guid.guid, box2->mailbox_guid.guid,
-		      sizeof(box1->mailbox_guid.guid));
-}
-
-int dsync_mailbox_p_guid_cmp(struct dsync_mailbox *const *box1,
-			     struct dsync_mailbox *const *box2)
-{
-	return dsync_mailbox_guid_cmp(*box1, *box2);
-}
-
-int dsync_mailbox_name_sha1_cmp(const struct dsync_mailbox *box1,
-				const struct dsync_mailbox *box2)
-{
-	int ret;
-
-	ret = memcmp(box1->name_sha1.guid, box2->name_sha1.guid,
-		     sizeof(box1->name_sha1.guid));
-	if (ret != 0)
-		return ret;
-
-	return strcmp(box1->name, box2->name);
-}
-
-int dsync_mailbox_p_name_sha1_cmp(struct dsync_mailbox *const *box1,
-				  struct dsync_mailbox *const *box2)
-{
-	return dsync_mailbox_name_sha1_cmp(*box1, *box2);
-}
-
-bool dsync_keyword_list_equals(const char *const *k1, const char *const *k2)
-{
-	unsigned int i;
-
-	if (k1 == NULL)
-		return k2 == NULL || k2[0] == NULL;
-	if (k2 == NULL)
-		return k1[0] == NULL;
-
-	for (i = 0;; i++) {
-		if (k1[i] == NULL)
-			return k2[i] == NULL;
-		if (k2[i] == NULL)
-			return FALSE;
-
-		if (strcasecmp(k1[i], k2[i]) != 0)
-			return FALSE;
-	}
-}
-
-bool dsync_guid_equals(const mailbox_guid_t *guid1,
-		       const mailbox_guid_t *guid2)
-{
-	return memcmp(guid1->guid, guid2->guid, sizeof(guid1->guid)) == 0;
-}
-
-int dsync_guid_cmp(const mailbox_guid_t *guid1, const mailbox_guid_t *guid2)
-{
-	return memcmp(guid1->guid, guid2->guid, sizeof(guid1->guid));
-}
-
-const char *dsync_guid_to_str(const mailbox_guid_t *guid)
-{
-	return guid_128_to_string(guid->guid);
-}
-
-const char *dsync_get_guid_128_str(const char *guid, unsigned char *dest,
-				   unsigned int dest_len)
-{
-	guid_128_t guid_128;
-	buffer_t guid_128_buf;
-
-	i_assert(dest_len >= GUID_128_SIZE * 2 + 1);
-	buffer_create_data(&guid_128_buf, dest, dest_len);
-	mail_generate_guid_128_hash(guid, guid_128);
-	if (guid_128_is_empty(guid_128))
-		return "";
-	binary_to_hex_append(&guid_128_buf, guid_128, sizeof(guid_128));
-	buffer_append_c(&guid_128_buf, '\0');
-	return guid_128_buf.data;
-}
-
-void dsync_str_sha_to_guid(const char *str, mailbox_guid_t *guid)
-{
-	unsigned char sha[SHA1_RESULTLEN];
-
-	sha1_get_digest(str, strlen(str), sha);
-	memcpy(guid->guid, sha, I_MIN(sizeof(guid->guid), sizeof(sha)));
-}
--- a/src/dsync/dsync-data.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-#ifndef DSYNC_DATA_H
-#define DSYNC_DATA_H
-
-#include "mail-storage.h"
-
-typedef struct {
-	guid_128_t guid;
-} mailbox_guid_t;
-ARRAY_DEFINE_TYPE(mailbox_guid, mailbox_guid_t);
-
-enum dsync_mailbox_flags {
-	DSYNC_MAILBOX_FLAG_NOSELECT		= 0x01,
-	DSYNC_MAILBOX_FLAG_DELETED_MAILBOX	= 0x02,
-	DSYNC_MAILBOX_FLAG_DELETED_DIR		= 0x04
-};
-
-struct dsync_mailbox {
-	const char *name;
-	char name_sep;
-	/* 128bit SHA1 sum of mailbox name */
-	mailbox_guid_t name_sha1;
-	/* Mailbox's GUID. Full of zero with \Noselect mailboxes. */
-	mailbox_guid_t mailbox_guid;
-
-	uint32_t uid_validity, uid_next, message_count, first_recent_uid;
-	uint64_t highest_modseq;
-	/* if mailbox is deleted, this is the deletion timestamp.
-	   otherwise it's the last rename timestamp. */
-	time_t last_change;
-	enum dsync_mailbox_flags flags;
-	ARRAY_TYPE(mailbox_cache_field) cache_fields;
-};
-ARRAY_DEFINE_TYPE(dsync_mailbox, struct dsync_mailbox *);
-#define dsync_mailbox_is_noselect(dsync_box) \
-	(((dsync_box)->flags & DSYNC_MAILBOX_FLAG_NOSELECT) != 0)
-
-/* dsync_worker_msg_iter_next() returns also all expunged messages from
-   the end of mailbox with this flag set. The GUIDs are 128 bit GUIDs saved
-   to transaction log (mail_generate_guid_128_hash()). */
-#define DSYNC_MAIL_FLAG_EXPUNGED 0x10000000
-
-struct dsync_message {
-	const char *guid;
-	uint32_t uid;
-	enum mail_flags flags;
-	/* keywords are sorted by name */
-	const char *const *keywords;
-	uint64_t modseq;
-	time_t save_date;
-};
-
-struct dsync_msg_static_data {
-	const char *pop3_uidl;
-	time_t received_date;
-	struct istream *input;
-};
-
-struct dsync_mailbox *
-dsync_mailbox_dup(pool_t pool, const struct dsync_mailbox *box);
-
-struct dsync_message *
-dsync_message_dup(pool_t pool, const struct dsync_message *msg);
-
-int dsync_mailbox_guid_cmp(const struct dsync_mailbox *box1,
-			   const struct dsync_mailbox *box2);
-int dsync_mailbox_p_guid_cmp(struct dsync_mailbox *const *box1,
-			     struct dsync_mailbox *const *box2);
-
-int dsync_mailbox_name_sha1_cmp(const struct dsync_mailbox *box1,
-				const struct dsync_mailbox *box2);
-int dsync_mailbox_p_name_sha1_cmp(struct dsync_mailbox *const *box1,
-				  struct dsync_mailbox *const *box2);
-
-bool dsync_keyword_list_equals(const char *const *k1, const char *const *k2);
-
-bool dsync_guid_equals(const mailbox_guid_t *guid1,
-		       const mailbox_guid_t *guid2);
-int dsync_guid_cmp(const mailbox_guid_t *guid1, const mailbox_guid_t *guid2);
-const char *dsync_guid_to_str(const mailbox_guid_t *guid);
-const char *dsync_get_guid_128_str(const char *guid, unsigned char *dest,
-				   unsigned int dest_len);
-void dsync_str_sha_to_guid(const char *str, mailbox_guid_t *guid);
-
-#endif
--- a/src/dsync/dsync-proxy-client.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1192 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "aqueue.h"
-#include "fd-set-nonblock.h"
-#include "istream.h"
-#include "istream-dot.h"
-#include "ostream.h"
-#include "str.h"
-#include "strescape.h"
-#include "master-service.h"
-#include "imap-util.h"
-#include "dsync-proxy.h"
-#include "dsync-worker-private.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-
-#define OUTBUF_THROTTLE_SIZE (1024*64)
-
-enum proxy_client_request_type {
-	PROXY_CLIENT_REQUEST_TYPE_COPY,
-	PROXY_CLIENT_REQUEST_TYPE_GET,
-	PROXY_CLIENT_REQUEST_TYPE_FINISH
-};
-
-struct proxy_client_request {
-	enum proxy_client_request_type type;
-	uint32_t uid;
-	union {
-		dsync_worker_msg_callback_t *get;
-		dsync_worker_copy_callback_t *copy;
-		dsync_worker_finish_callback_t *finish;
-	} callback;
-	void *context;
-};
-
-struct proxy_client_dsync_worker_mailbox_iter {
-	struct dsync_worker_mailbox_iter iter;
-	pool_t pool;
-};
-
-struct proxy_client_dsync_worker_subs_iter {
-	struct dsync_worker_subs_iter iter;
-	pool_t pool;
-};
-
-struct proxy_client_dsync_worker {
-	struct dsync_worker worker;
-	int fd_in, fd_out;
-	struct io *io;
-	struct istream *input;
-	struct ostream *output;
-	struct timeout *to, *to_input;
-
-	mailbox_guid_t selected_box_guid;
-
-	dsync_worker_save_callback_t *save_callback;
-	void *save_context;
-	struct istream *save_input;
-	struct io *save_io;
-	bool save_input_last_lf;
-
-	pool_t msg_get_pool;
-	struct dsync_msg_static_data msg_get_data;
-	ARRAY_DEFINE(request_array, struct proxy_client_request);
-	struct aqueue *request_queue;
-	string_t *pending_commands;
-
-	unsigned int handshake_received:1;
-	unsigned int finishing:1;
-	unsigned int finished:1;
-};
-
-extern struct dsync_worker_vfuncs proxy_client_dsync_worker;
-
-static void proxy_client_worker_input(struct proxy_client_dsync_worker *worker);
-static void proxy_client_send_stream(struct proxy_client_dsync_worker *worker);
-
-static void proxy_client_fail(struct proxy_client_dsync_worker *worker)
-{
-	i_stream_close(worker->input);
-	dsync_worker_set_failure(&worker->worker);
-	master_service_stop(master_service);
-}
-
-static int
-proxy_client_worker_read_line(struct proxy_client_dsync_worker *worker,
-			      const char **line_r)
-{
-	if (worker->worker.failed)
-		return -1;
-
-	*line_r = i_stream_read_next_line(worker->input);
-	if (*line_r == NULL) {
-		if (worker->input->stream_errno != 0) {
-			errno = worker->input->stream_errno;
-			i_error("read() from worker server failed: %m");
-			dsync_worker_set_failure(&worker->worker);
-			return -1;
-		}
-		if (worker->input->eof) {
-			if (!worker->finished)
-				i_error("read() from worker server failed: EOF");
-			dsync_worker_set_failure(&worker->worker);
-			return -1;
-		}
-	}
-	if (*line_r == NULL)
-		return 0;
-
-	if (!worker->handshake_received) {
-		if (strcmp(*line_r, DSYNC_PROXY_SERVER_GREETING_LINE) != 0) {
-			i_error("Invalid server handshake: %s", *line_r);
-			dsync_worker_set_failure(&worker->worker);
-			return -1;
-		}
-		worker->handshake_received = TRUE;
-		return proxy_client_worker_read_line(worker, line_r);
-	}
-	return 1;
-}
-
-static void
-proxy_client_worker_msg_get_finish(struct proxy_client_dsync_worker *worker)
-{
-	worker->msg_get_data.input = NULL;
-	worker->io = io_add(worker->fd_in, IO_READ,
-			    proxy_client_worker_input, worker);
-
-	/* some input may already be buffered. note that we may be coming here
-	   from the input function itself, in which case this timeout must not
-	   be called (we'll remove it later) */
-	if (worker->to_input == NULL) {
-		worker->to_input =
-			timeout_add(0, proxy_client_worker_input, worker);
-	}
-}
-
-static void
-proxy_client_worker_read_to_eof(struct proxy_client_dsync_worker *worker)
-{
-	struct istream *input = worker->msg_get_data.input;
-	const unsigned char *data;
-	size_t size;
-	int ret;
-
-	while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0)
-		i_stream_skip(input, size);
-	if (ret == -1) {
-		i_stream_unref(&input);
-		io_remove(&worker->io);
-		proxy_client_worker_msg_get_finish(worker);
-	}
-	timeout_reset(worker->to);
-}
-
-static void
-proxy_client_worker_msg_get_done(struct proxy_client_dsync_worker *worker)
-{
-	struct istream *input = worker->msg_get_data.input;
-
-	i_assert(worker->io == NULL);
-
-	if (input->eof)
-		proxy_client_worker_msg_get_finish(worker);
-	else {
-		/* saving read the message only partially. we'll need to read
-		   the input until EOF or we'll start treating the input as
-		   commands. */
-		worker->io = io_add(worker->fd_in, IO_READ,
-				    proxy_client_worker_read_to_eof, worker);
-		worker->msg_get_data.input =
-			i_stream_create_dot(worker->input, FALSE);
-	}
-}
-
-static bool
-proxy_client_worker_next_copy(struct proxy_client_dsync_worker *worker,
-			      const struct proxy_client_request *request,
-			      const char *line)
-{
-	uint32_t uid;
-	bool success;
-
-	if (line[0] == '1' && line[1] == '\t')
-		success = TRUE;
-	else if (line[0] == '0' && line[1] == '\t')
-		success = FALSE;
-	else {
-		i_error("msg-copy returned invalid input: %s", line);
-		proxy_client_fail(worker);
-		return FALSE;
-	}
-	uid = strtoul(line + 2, NULL, 10);
-	if (uid != request->uid) {
-		i_error("msg-copy returned invalid uid: %u != %u",
-			uid, request->uid);
-		proxy_client_fail(worker);
-		return FALSE;
-	}
-
-	request->callback.copy(success, request->context);
-	return TRUE;
-}
-
-static bool
-proxy_client_worker_next_msg_get(struct proxy_client_dsync_worker *worker,
-				 const struct proxy_client_request *request,
-				 const char *line)
-{
-	enum dsync_msg_get_result result = DSYNC_MSG_GET_RESULT_FAILED;
-	const char *p, *error;
-	uint32_t uid;
-
-	p_clear(worker->msg_get_pool);
-	switch (line[0]) {
-	case '1':
-		/* ok */
-		if (line[1] != '\t')
-			break;
-		line += 2;
-
-		if ((p = strchr(line, '\t')) == NULL)
-			break;
-		uid = strtoul(t_strcut(line, '\t'), NULL, 10);
-		line = p + 1;
-
-		if (uid != request->uid) {
-			i_error("msg-get returned invalid uid: %u != %u",
-				uid, request->uid);
-			proxy_client_fail(worker);
-			return FALSE;
-		}
-
-		if (dsync_proxy_msg_static_import(worker->msg_get_pool,
-						  line, &worker->msg_get_data,
-						  &error) < 0) {
-			i_error("Invalid msg-get static input: %s", error);
-			proxy_client_fail(worker);
-			return FALSE;
-		}
-		worker->msg_get_data.input =
-			i_stream_create_dot(worker->input, FALSE);
-		i_stream_set_destroy_callback(worker->msg_get_data.input,
-					      proxy_client_worker_msg_get_done,
-					      worker);
-		io_remove(&worker->io);
-		result = DSYNC_MSG_GET_RESULT_SUCCESS;
-		break;
-	case '0':
-		/* expunged */
-		result = DSYNC_MSG_GET_RESULT_EXPUNGED;
-		break;
-	default:
-		/* failure */
-		break;
-	}
-
-	request->callback.get(result, &worker->msg_get_data, request->context);
-	return worker->io != NULL && worker->msg_get_data.input == NULL;
-}
-
-static void
-proxy_client_worker_next_finish(struct proxy_client_dsync_worker *worker,
-				const struct proxy_client_request *request,
-				const char *line)
-{
-	bool success = TRUE;
-
-	i_assert(worker->finishing);
-	i_assert(!worker->finished);
-
-	worker->finishing = FALSE;
-	worker->finished = TRUE;
-
-	if (strcmp(line, "changes") == 0)
-		worker->worker.unexpected_changes = TRUE;
-	else if (strcmp(line, "fail") == 0)
-		success = FALSE;
-	else if (strcmp(line, "ok") != 0) {
-		i_error("Unexpected finish reply: %s", line);
-		success = FALSE;
-	}
-		
-	request->callback.finish(success, request->context);
-}
-
-static bool
-proxy_client_worker_next_reply(struct proxy_client_dsync_worker *worker,
-			       const char *line)
-{
-	const struct proxy_client_request *requests;
-	struct proxy_client_request request;
-	bool ret = TRUE;
-
-	i_assert(worker->msg_get_data.input == NULL);
-
-	if (aqueue_count(worker->request_queue) == 0) {
-		i_error("Unexpected reply from server: %s", line);
-		proxy_client_fail(worker);
-		return FALSE;
-	}
-
-	requests = array_idx(&worker->request_array, 0);
-	request = requests[aqueue_idx(worker->request_queue, 0)];
-	aqueue_delete_tail(worker->request_queue);
-
-	switch (request.type) {
-	case PROXY_CLIENT_REQUEST_TYPE_COPY:
-		ret = proxy_client_worker_next_copy(worker, &request, line);
-		break;
-	case PROXY_CLIENT_REQUEST_TYPE_GET:
-		ret = proxy_client_worker_next_msg_get(worker, &request, line);
-		break;
-	case PROXY_CLIENT_REQUEST_TYPE_FINISH:
-		proxy_client_worker_next_finish(worker, &request, line);
-		break;
-	}
-	return ret;
-}
-
-static void proxy_client_worker_input(struct proxy_client_dsync_worker *worker)
-{
-	const char *line;
-	int ret;
-
-	if (worker->to_input != NULL)
-		timeout_remove(&worker->to_input);
-
-	if (worker->worker.input_callback != NULL) {
-		worker->worker.input_callback(worker->worker.input_context);
-		timeout_reset(worker->to);
-		return;
-	}
-
-	while ((ret = proxy_client_worker_read_line(worker, &line)) > 0) {
-		if (!proxy_client_worker_next_reply(worker, line))
-			break;
-	}
-	if (ret < 0) {
-		/* try to continue */
-		proxy_client_worker_next_reply(worker, "");
-	}
-
-	if (worker->to_input != NULL) {
-		/* input stream's destroy callback was already called.
-		   don't get back here. */
-		timeout_remove(&worker->to_input);
-	}
-	timeout_reset(worker->to);
-}
-
-static int
-proxy_client_worker_output_real(struct proxy_client_dsync_worker *worker)
-{
-	int ret;
-
-	if ((ret = o_stream_flush(worker->output)) < 0)
-		return 1;
-
-	if (worker->save_input != NULL) {
-		/* proxy_client_worker_msg_save() hasn't finished yet. */
-		o_stream_cork(worker->output);
-		proxy_client_send_stream(worker);
-		if (worker->save_input != NULL) {
-			/* still unfinished, make sure we get called again */
-			return 0;
-		}
-	}
-
-	if (worker->worker.output_callback != NULL)
-		worker->worker.output_callback(worker->worker.output_context);
-	return ret;
-}
-
-static int proxy_client_worker_output(struct proxy_client_dsync_worker *worker)
-{
-	int ret;
-
-	ret = proxy_client_worker_output_real(worker);
-	timeout_reset(worker->to);
-	return ret;
-}
-
-static void
-proxy_client_worker_timeout(struct proxy_client_dsync_worker *worker)
-{
-	const char *reason;
-
-	if (worker->save_io != NULL)
-		reason = " (waiting for more input from mail being saved)";
-	else if (worker->save_input != NULL) {
-		size_t bytes = o_stream_get_buffer_used_size(worker->output);
-
-		reason = t_strdup_printf(" (waiting for output stream to flush, "
-					 "%"PRIuSIZE_T" bytes left)", bytes);
-	} else if (worker->msg_get_data.input != NULL) {
-		reason = " (waiting for MSG-GET message from remote)";
-	} else {
-		reason = "";
-	}
-	i_error("proxy client timed out%s", reason);
-	proxy_client_fail(worker);
-}
-
-struct dsync_worker *dsync_worker_init_proxy_client(int fd_in, int fd_out)
-{
-	struct proxy_client_dsync_worker *worker;
-
-	worker = i_new(struct proxy_client_dsync_worker, 1);
-	worker->worker.v = proxy_client_dsync_worker;
-	worker->fd_in = fd_in;
-	worker->fd_out = fd_out;
-	worker->to = timeout_add(DSYNC_PROXY_CLIENT_TIMEOUT_MSECS,
-				 proxy_client_worker_timeout, worker);
-	worker->io = io_add(fd_in, IO_READ, proxy_client_worker_input, worker);
-	worker->input = i_stream_create_fd(fd_in, (size_t)-1, FALSE);
-	worker->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
-	o_stream_send_str(worker->output, DSYNC_PROXY_CLIENT_GREETING_LINE"\n");
-	/* we'll keep the output corked until flush is needed */
-	o_stream_cork(worker->output);
-	o_stream_set_flush_callback(worker->output, proxy_client_worker_output,
-				    worker);
-	fd_set_nonblock(fd_in, TRUE);
-	fd_set_nonblock(fd_out, TRUE);
-
-	worker->pending_commands = str_new(default_pool, 1024);
-	worker->msg_get_pool = pool_alloconly_create("dsync proxy msg", 128);
-	i_array_init(&worker->request_array, 64);
-	worker->request_queue = aqueue_init(&worker->request_array.arr);
-
-	return &worker->worker;
-}
-
-static void proxy_client_worker_deinit(struct dsync_worker *_worker)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	timeout_remove(&worker->to);
-	if (worker->to_input != NULL)
-		timeout_remove(&worker->to_input);
-	if (worker->io != NULL)
-		io_remove(&worker->io);
-	i_stream_destroy(&worker->input);
-	o_stream_destroy(&worker->output);
-	if (close(worker->fd_in) < 0)
-		i_error("close(worker input) failed: %m");
-	if (worker->fd_in != worker->fd_out) {
-		if (close(worker->fd_out) < 0)
-			i_error("close(worker output) failed: %m");
-	}
-	aqueue_deinit(&worker->request_queue);
-	array_free(&worker->request_array);
-	pool_unref(&worker->msg_get_pool);
-	str_free(&worker->pending_commands);
-	i_free(worker);
-}
-
-static bool
-worker_is_output_stream_full(struct proxy_client_dsync_worker *worker)
-{
-	return o_stream_get_buffer_used_size(worker->output) >=
-		OUTBUF_THROTTLE_SIZE;
-}
-
-static bool proxy_client_worker_is_output_full(struct dsync_worker *_worker)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	if (worker->save_input != NULL) {
-		/* we haven't finished sending a message save, so we're full. */
-		return TRUE;
-	}
-	return worker_is_output_stream_full(worker);
-}
-
-static int proxy_client_worker_output_flush(struct dsync_worker *_worker)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	int ret = 1;
-
-	if (o_stream_flush(worker->output) < 0)
-		return -1;
-
-	o_stream_uncork(worker->output);
-	if (o_stream_get_buffer_used_size(worker->output) > 0)
-		return 0;
-
-	if (o_stream_send(worker->output, str_data(worker->pending_commands),
-			  str_len(worker->pending_commands)) < 0)
-		ret = -1;
-	str_truncate(worker->pending_commands, 0);
-	o_stream_cork(worker->output);
-	return ret;
-}
-
-static struct dsync_worker_mailbox_iter *
-proxy_client_worker_mailbox_iter_init(struct dsync_worker *_worker)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	struct proxy_client_dsync_worker_mailbox_iter *iter;
-
-	iter = i_new(struct proxy_client_dsync_worker_mailbox_iter, 1);
-	iter->iter.worker = _worker;
-	iter->pool = pool_alloconly_create("proxy mailbox iter", 1024);
-	o_stream_send_str(worker->output, "BOX-LIST\n");
-	(void)proxy_client_worker_output_flush(_worker);
-	return &iter->iter;
-}
-
-static int
-proxy_client_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *_iter,
-				      struct dsync_mailbox *dsync_box_r)
-{
-	struct proxy_client_dsync_worker_mailbox_iter *iter =
-		(struct proxy_client_dsync_worker_mailbox_iter *)_iter;
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_iter->worker;
-	const char *line, *error;
-	int ret;
-
-	if ((ret = proxy_client_worker_read_line(worker, &line)) <= 0) {
-		if (ret < 0)
-			_iter->failed = TRUE;
-		return ret;
-	}
-
-	if ((line[0] == '+' || line[0] == '-') && line[1] == '\0') {
-		/* end of mailboxes */
-		if (line[0] == '-') {
-			i_error("Worker server's mailbox iteration failed");
-			_iter->failed = TRUE;
-		}
-		return -1;
-	}
-
-	p_clear(iter->pool);
-	if (dsync_proxy_mailbox_import(iter->pool, line,
-				       dsync_box_r, &error) < 0) {
-		i_error("Invalid mailbox input from worker server: %s", error);
-		_iter->failed = TRUE;
-		return -1;
-	}
-	return 1;
-}
-
-static int
-proxy_client_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter *_iter)
-{
-	struct proxy_client_dsync_worker_mailbox_iter *iter =
-		(struct proxy_client_dsync_worker_mailbox_iter *)_iter;
-	int ret = _iter->failed ? -1 : 0;
-
-	pool_unref(&iter->pool);
-	i_free(iter);
-	return ret;
-}
-
-static struct dsync_worker_subs_iter *
-proxy_client_worker_subs_iter_init(struct dsync_worker *_worker)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	struct proxy_client_dsync_worker_subs_iter *iter;
-
-	iter = i_new(struct proxy_client_dsync_worker_subs_iter, 1);
-	iter->iter.worker = _worker;
-	iter->pool = pool_alloconly_create("proxy subscription iter", 1024);
-	o_stream_send_str(worker->output, "SUBS-LIST\n");
-	(void)proxy_client_worker_output_flush(_worker);
-	return &iter->iter;
-}
-
-static int
-proxy_client_worker_subs_iter_next_line(struct proxy_client_dsync_worker_subs_iter *iter,
-					unsigned int wanted_arg_count,
-					char ***args_r)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)iter->iter.worker;
-	const char *line;
-	char **args;
-	int ret;
-
-	if ((ret = proxy_client_worker_read_line(worker, &line)) <= 0) {
-		if (ret < 0)
-			iter->iter.failed = TRUE;
-		return ret;
-	}
-
-	if ((line[0] == '+' || line[0] == '-') && line[1] == '\0') {
-		/* end of subscribed subscriptions */
-		if (line[0] == '-') {
-			i_error("Worker server's subscription iteration failed");
-			iter->iter.failed = TRUE;
-		}
-		return -1;
-	}
-
-	p_clear(iter->pool);
-	args = p_strsplit(iter->pool, line, "\t");
-	if (str_array_length((const char *const *)args) < wanted_arg_count) {
-		i_error("Invalid subscription input from worker server");
-		iter->iter.failed = TRUE;
-		return -1;
-	}
-	*args_r = args;
-	return 1;
-}
-
-static int
-proxy_client_worker_subs_iter_next(struct dsync_worker_subs_iter *_iter,
-				   struct dsync_worker_subscription *rec_r)
-{
-	struct proxy_client_dsync_worker_subs_iter *iter =
-		(struct proxy_client_dsync_worker_subs_iter *)_iter;
-	char **args;
-	int ret;
-
-	ret = proxy_client_worker_subs_iter_next_line(iter, 4, &args);
-	if (ret <= 0)
-		return ret;
-
-	rec_r->vname = str_tabunescape(args[0]);
-	rec_r->storage_name = str_tabunescape(args[1]);
-	rec_r->ns_prefix = str_tabunescape(args[2]);
-	rec_r->last_change = strtoul(args[3], NULL, 10);
-	return 1;
-}
-
-static int
-proxy_client_worker_subs_iter_next_un(struct dsync_worker_subs_iter *_iter,
-				      struct dsync_worker_unsubscription *rec_r)
-{
-	struct proxy_client_dsync_worker_subs_iter *iter =
-		(struct proxy_client_dsync_worker_subs_iter *)_iter;
-	char **args;
-	int ret;
-
-	ret = proxy_client_worker_subs_iter_next_line(iter, 3, &args);
-	if (ret <= 0)
-		return ret;
-
-	memset(rec_r, 0, sizeof(*rec_r));
-	if (dsync_proxy_mailbox_guid_import(args[0], &rec_r->name_sha1) < 0) {
-		i_error("Invalid subscription input from worker server: "
-			"Invalid unsubscription mailbox GUID");
-		iter->iter.failed = TRUE;
-		return -1;
-	}
-	rec_r->ns_prefix = str_tabunescape(args[1]);
-	rec_r->last_change = strtoul(args[2], NULL, 10);
-	return 1;
-}
-
-static int
-proxy_client_worker_subs_iter_deinit(struct dsync_worker_subs_iter *_iter)
-{
-	struct proxy_client_dsync_worker_subs_iter *iter =
-		(struct proxy_client_dsync_worker_subs_iter *)_iter;
-	int ret = _iter->failed ? -1 : 0;
-
-	pool_unref(&iter->pool);
-	i_free(iter);
-	return ret;
-}
-
-static void
-proxy_client_worker_set_subscribed(struct dsync_worker *_worker,
-				   const char *name, time_t last_change,
-				   bool set)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "SUBS-SET\t");
-		str_tabescape_write(str, name);
-		str_printfa(str, "\t%s\t%d\n", dec2str(last_change),
-			    set ? 1 : 0);
-		o_stream_send(worker->output, str_data(str), str_len(str));
-	} T_END;
-}
-
-struct proxy_client_dsync_worker_msg_iter {
-	struct dsync_worker_msg_iter iter;
-	pool_t pool;
-	bool done;
-};
-
-static struct dsync_worker_msg_iter *
-proxy_client_worker_msg_iter_init(struct dsync_worker *_worker,
-				  const mailbox_guid_t mailboxes[],
-				  unsigned int mailbox_count)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	struct proxy_client_dsync_worker_msg_iter *iter;
-	string_t *str;
-	unsigned int i;
-
-	iter = i_new(struct proxy_client_dsync_worker_msg_iter, 1);
-	iter->iter.worker = _worker;
-	iter->pool = pool_alloconly_create("proxy message iter", 10240);
-
-	str = str_new(iter->pool, 512);
-	str_append(str, "MSG-LIST");
-	for (i = 0; i < mailbox_count; i++) T_BEGIN {
-		str_append_c(str, '\t');
-		dsync_proxy_mailbox_guid_export(str, &mailboxes[i]);
-	} T_END;
-	str_append_c(str, '\n');
-	o_stream_send(worker->output, str_data(str), str_len(str));
-	p_clear(iter->pool);
-
-	(void)proxy_client_worker_output_flush(_worker);
-	return &iter->iter;
-}
-
-static int
-proxy_client_worker_msg_iter_next(struct dsync_worker_msg_iter *_iter,
-				  unsigned int *mailbox_idx_r,
-				  struct dsync_message *msg_r)
-{
-	struct proxy_client_dsync_worker_msg_iter *iter =
-		(struct proxy_client_dsync_worker_msg_iter *)_iter;
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_iter->worker;
-	const char *line, *error;
-	int ret;
-
-	if (iter->done)
-		return -1;
-
-	if ((ret = proxy_client_worker_read_line(worker, &line)) <= 0) {
-		if (ret < 0)
-			_iter->failed = TRUE;
-		return ret;
-	}
-
-	if ((line[0] == '+' || line[0] == '-') && line[1] == '\0') {
-		/* end of messages */
-		if (line[0] == '-') {
-			i_error("Worker server's message iteration failed");
-			_iter->failed = TRUE;
-		}
-		iter->done = TRUE;
-		return -1;
-	}
-
-	*mailbox_idx_r = 0;
-	while (*line >= '0' && *line <= '9') {
-		*mailbox_idx_r = *mailbox_idx_r * 10 + (*line - '0');
-		line++;
-	}
-	if (*line != '\t') {
-		i_error("Invalid mailbox idx from worker server");
-		_iter->failed = TRUE;
-		return -1;
-	}
-	line++;
-
-	p_clear(iter->pool);
-	if (dsync_proxy_msg_import(iter->pool, line, msg_r, &error) < 0) {
-		i_error("Invalid message input from worker server: %s", error);
-		_iter->failed = TRUE;
-		return -1;
-	}
-	return 1;
-}
-
-static int
-proxy_client_worker_msg_iter_deinit(struct dsync_worker_msg_iter *_iter)
-{
-	struct proxy_client_dsync_worker_msg_iter *iter =
-		(struct proxy_client_dsync_worker_msg_iter *)_iter;
-	int ret = _iter->failed ? -1 : 0;
-
-	pool_unref(&iter->pool);
-	i_free(iter);
-	return ret;
-}
-
-static void
-proxy_client_worker_cmd(struct proxy_client_dsync_worker *worker, string_t *str)
-{
-	if (worker->save_input == NULL)
-		o_stream_send(worker->output, str_data(str), str_len(str));
-	else
-		str_append_str(worker->pending_commands, str);
-}
-
-static void
-proxy_client_worker_create_mailbox(struct dsync_worker *_worker,
-				   const struct dsync_mailbox *dsync_box)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "BOX-CREATE\t");
-		dsync_proxy_mailbox_export(str, dsync_box);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_delete_mailbox(struct dsync_worker *_worker,
-				   const struct dsync_mailbox *dsync_box)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "BOX-DELETE\t");
-		dsync_proxy_mailbox_guid_export(str, &dsync_box->mailbox_guid);
-		str_printfa(str, "\t%s\n", dec2str(dsync_box->last_change));
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_delete_dir(struct dsync_worker *_worker,
-			       const struct dsync_mailbox *dsync_box)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "DIR-DELETE\t");
-		str_tabescape_write(str, dsync_box->name);
-		str_printfa(str, "\t%s\n", dec2str(dsync_box->last_change));
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_rename_mailbox(struct dsync_worker *_worker,
-				   const mailbox_guid_t *mailbox,
-				   const struct dsync_mailbox *dsync_box)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	char sep[2];
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "BOX-RENAME\t");
-		dsync_proxy_mailbox_guid_export(str, mailbox);
-		str_append_c(str, '\t');
-		str_tabescape_write(str, dsync_box->name);
-		str_append_c(str, '\t');
-		sep[0] = dsync_box->name_sep; sep[1] = '\0';
-		str_tabescape_write(str, sep);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_update_mailbox(struct dsync_worker *_worker,
-				   const struct dsync_mailbox *dsync_box)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "BOX-UPDATE\t");
-		dsync_proxy_mailbox_export(str, dsync_box);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_select_mailbox(struct dsync_worker *_worker,
-				   const mailbox_guid_t *mailbox,
-				   const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	if (dsync_guid_equals(&worker->selected_box_guid, mailbox))
-		return;
-	worker->selected_box_guid = *mailbox;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "BOX-SELECT\t");
-		dsync_proxy_mailbox_guid_export(str, mailbox);
-		if (cache_fields != NULL)
-			dsync_proxy_cache_fields_export(str, cache_fields);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_msg_update_metadata(struct dsync_worker *_worker,
-					const struct dsync_message *msg)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_printfa(str, "MSG-UPDATE\t%u\t%llu\t", msg->uid,
-			    (unsigned long long)msg->modseq);
-		imap_write_flags(str, msg->flags, msg->keywords);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_msg_update_uid(struct dsync_worker *_worker,
-				   uint32_t old_uid, uint32_t new_uid)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(64);
-		str_printfa(str, "MSG-UID-CHANGE\t%u\t%u\n", old_uid, new_uid);
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_msg_expunge(struct dsync_worker *_worker, uint32_t uid)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(64);
-		str_printfa(str, "MSG-EXPUNGE\t%u\n", uid);
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-}
-
-static void
-proxy_client_worker_msg_copy(struct dsync_worker *_worker,
-			     const mailbox_guid_t *src_mailbox,
-			     uint32_t src_uid,
-			     const struct dsync_message *dest_msg,
-			     dsync_worker_copy_callback_t *callback,
-			     void *context)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	struct proxy_client_request request;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "MSG-COPY\t");
-		dsync_proxy_mailbox_guid_export(str, src_mailbox);
-		str_printfa(str, "\t%u\t", src_uid);
-		dsync_proxy_msg_export(str, dest_msg);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-
-	memset(&request, 0, sizeof(request));
-	request.type = PROXY_CLIENT_REQUEST_TYPE_COPY;
-	request.callback.copy = callback;
-	request.context = context;
-	request.uid = src_uid;
-	aqueue_append(worker->request_queue, &request);
-}
-
-static void
-proxy_client_send_stream_real(struct proxy_client_dsync_worker *worker)
-{
-	dsync_worker_save_callback_t *callback;
-	void *context;
-	struct istream *input;
-	const unsigned char *data;
-	size_t size;
-	int ret;
-
-	while ((ret = i_stream_read_data(worker->save_input,
-					 &data, &size, 0)) > 0) {
-		dsync_proxy_send_dot_output(worker->output,
-					    &worker->save_input_last_lf,
-					    data, size);
-		i_stream_skip(worker->save_input, size);
-
-		if (worker_is_output_stream_full(worker)) {
-			o_stream_uncork(worker->output);
-			if (worker_is_output_stream_full(worker))
-				return;
-			o_stream_cork(worker->output);
-		}
-	}
-	if (ret == 0) {
-		/* waiting for more input */
-		o_stream_uncork(worker->output);
-		if (worker->save_io == NULL) {
-			int fd = i_stream_get_fd(worker->save_input);
-
-			worker->save_io =
-				io_add(fd, IO_READ,
-				       proxy_client_send_stream, worker);
-		}
-		return;
-	}
-	if (worker->save_io != NULL)
-		io_remove(&worker->save_io);
-	if (worker->save_input->stream_errno != 0) {
-		errno = worker->save_input->stream_errno;
-		i_error("proxy: reading message input failed: %m");
-		o_stream_close(worker->output);
-	} else {
-		i_assert(!i_stream_have_bytes_left(worker->save_input));
-		o_stream_send(worker->output, "\n.\n", 3);
-	}
-
-	callback = worker->save_callback;
-	context = worker->save_context;
-	worker->save_callback = NULL;
-	worker->save_context = NULL;
-
-	/* a bit ugly way to free the stream. the problem is that local worker
-	   has set a destroy callback, which in turn can call our msg_save()
-	   again before the i_stream_unref() is finished. */
-	input = worker->save_input;
-	worker->save_input = NULL;
-	i_stream_unref(&input);
-
-	(void)proxy_client_worker_output_flush(&worker->worker);
-
-	callback(context);
-}
-
-static void proxy_client_send_stream(struct proxy_client_dsync_worker *worker)
-{
-	proxy_client_send_stream_real(worker);
-	timeout_reset(worker->to);
-}
-
-static void
-proxy_client_worker_msg_save(struct dsync_worker *_worker,
-			     const struct dsync_message *msg,
-			     const struct dsync_msg_static_data *data,
-			     dsync_worker_save_callback_t *callback,
-			     void *context)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "MSG-SAVE\t");
-		dsync_proxy_msg_static_export(str, data);
-		str_append_c(str, '\t');
-		dsync_proxy_msg_export(str, msg);
-		str_append_c(str, '\n');
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-
-	i_assert(worker->save_input == NULL);
-	worker->save_callback = callback;
-	worker->save_context = context;
-	worker->save_input = data->input;
-	worker->save_input_last_lf = TRUE;
-	i_stream_ref(worker->save_input);
-	proxy_client_send_stream(worker);
-}
-
-static void
-proxy_client_worker_msg_save_cancel(struct dsync_worker *_worker)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-
-	if (worker->save_io != NULL)
-		io_remove(&worker->save_io);
-	if (worker->save_input != NULL)
-		i_stream_unref(&worker->save_input);
-}
-
-static void
-proxy_client_worker_msg_get(struct dsync_worker *_worker,
-			    const mailbox_guid_t *mailbox, uint32_t uid,
-			    dsync_worker_msg_callback_t *callback,
-			    void *context)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	struct proxy_client_request request;
-
-	T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		str_append(str, "MSG-GET\t");
-		dsync_proxy_mailbox_guid_export(str, mailbox);
-		str_printfa(str, "\t%u\n", uid);
-		proxy_client_worker_cmd(worker, str);
-	} T_END;
-
-	memset(&request, 0, sizeof(request));
-	request.type = PROXY_CLIENT_REQUEST_TYPE_GET;
-	request.callback.get = callback;
-	request.context = context;
-	request.uid = uid;
-	aqueue_append(worker->request_queue, &request);
-}
-
-static void
-proxy_client_worker_finish(struct dsync_worker *_worker,
-			   dsync_worker_finish_callback_t *callback,
-			   void *context)
-{
-	struct proxy_client_dsync_worker *worker =
-		(struct proxy_client_dsync_worker *)_worker;
-	struct proxy_client_request request;
-
-	i_assert(worker->save_input == NULL);
-	i_assert(!worker->finishing);
-
-	worker->finishing = TRUE;
-	worker->finished = FALSE;
-
-	o_stream_send_str(worker->output, "FINISH\n");
-	o_stream_uncork(worker->output);
-
-	memset(&request, 0, sizeof(request));
-	request.type = PROXY_CLIENT_REQUEST_TYPE_FINISH;
-	request.callback.finish = callback;
-	request.context = context;
-	aqueue_append(worker->request_queue, &request);
-}
-
-struct dsync_worker_vfuncs proxy_client_dsync_worker = {
-	proxy_client_worker_deinit,
-
-	proxy_client_worker_is_output_full,
-	proxy_client_worker_output_flush,
-
-	proxy_client_worker_mailbox_iter_init,
-	proxy_client_worker_mailbox_iter_next,
-	proxy_client_worker_mailbox_iter_deinit,
-
-	proxy_client_worker_subs_iter_init,
-	proxy_client_worker_subs_iter_next,
-	proxy_client_worker_subs_iter_next_un,
-	proxy_client_worker_subs_iter_deinit,
-	proxy_client_worker_set_subscribed,
-
-	proxy_client_worker_msg_iter_init,
-	proxy_client_worker_msg_iter_next,
-	proxy_client_worker_msg_iter_deinit,
-
-	proxy_client_worker_create_mailbox,
-	proxy_client_worker_delete_mailbox,
-	proxy_client_worker_delete_dir,
-	proxy_client_worker_rename_mailbox,
-	proxy_client_worker_update_mailbox,
-
-	proxy_client_worker_select_mailbox,
-	proxy_client_worker_msg_update_metadata,
-	proxy_client_worker_msg_update_uid,
-	proxy_client_worker_msg_expunge,
-	proxy_client_worker_msg_copy,
-	proxy_client_worker_msg_save,
-	proxy_client_worker_msg_save_cancel,
-	proxy_client_worker_msg_get,
-	proxy_client_worker_finish
-};
--- a/src/dsync/dsync-proxy-server-cmd.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,609 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "strescape.h"
-#include "network.h"
-#include "istream.h"
-#include "istream-dot.h"
-#include "ostream.h"
-#include "imap-util.h"
-#include "master-service.h"
-#include "dsync-worker.h"
-#include "dsync-proxy.h"
-#include "dsync-proxy-server.h"
-
-#include <stdlib.h>
-
-#define OUTBUF_THROTTLE_SIZE (1024*64)
-
-static bool
-proxy_server_is_output_full(struct dsync_proxy_server *server)
-{
-	return o_stream_get_buffer_used_size(server->output) >=
-		OUTBUF_THROTTLE_SIZE;
-}
-
-static int
-cmd_box_list(struct dsync_proxy_server *server,
-	     const char *const *args ATTR_UNUSED)
-{
-	struct dsync_mailbox dsync_box;
-	string_t *str;
-	int ret;
-
-	if (server->mailbox_iter == NULL) {
-		server->mailbox_iter =
-			dsync_worker_mailbox_iter_init(server->worker);
-	}
-
-	str = t_str_new(256);
-	while ((ret = dsync_worker_mailbox_iter_next(server->mailbox_iter,
-						     &dsync_box)) > 0) {
-		str_truncate(str, 0);
-		dsync_proxy_mailbox_export(str, &dsync_box);
-		str_append_c(str, '\n');
-		o_stream_send(server->output, str_data(str), str_len(str));
-		if (proxy_server_is_output_full(server))
-			break;
-	}
-	if (ret >= 0) {
-		/* continue later */
-		o_stream_set_flush_pending(server->output, TRUE);
-		return 0;
-	}
-	if (dsync_worker_mailbox_iter_deinit(&server->mailbox_iter) < 0) {
-		o_stream_send(server->output, "-\n", 2);
-		return -1;
-	} else {
-		o_stream_send(server->output, "+\n", 2);
-		return 1;
-	}
-}
-
-static bool cmd_subs_list_subscriptions(struct dsync_proxy_server *server)
-{
-	struct dsync_worker_subscription rec;
-	string_t *str;
-	int ret;
-
-	str = t_str_new(256);
-	while ((ret = dsync_worker_subs_iter_next(server->subs_iter,
-						  &rec)) > 0) {
-		str_truncate(str, 0);
-		str_tabescape_write(str, rec.vname);
-		str_append_c(str, '\t');
-		str_tabescape_write(str, rec.storage_name);
-		str_append_c(str, '\t');
-		str_tabescape_write(str, rec.ns_prefix);
-		str_printfa(str, "\t%ld\n", (long)rec.last_change);
-		o_stream_send(server->output, str_data(str), str_len(str));
-		if (proxy_server_is_output_full(server))
-			break;
-	}
-	if (ret >= 0) {
-		/* continue later */
-		o_stream_set_flush_pending(server->output, TRUE);
-		return FALSE;
-	}
-	return TRUE;
-}
-
-static bool cmd_subs_list_unsubscriptions(struct dsync_proxy_server *server)
-{
-	struct dsync_worker_unsubscription rec;
-	string_t *str;
-	int ret;
-
-	str = t_str_new(256);
-	while ((ret = dsync_worker_subs_iter_next_un(server->subs_iter,
-						     &rec)) > 0) {
-		str_truncate(str, 0);
-		dsync_proxy_mailbox_guid_export(str, &rec.name_sha1);
-		str_append_c(str, '\t');
-		str_tabescape_write(str, rec.ns_prefix);
-		str_printfa(str, "\t%ld\n", (long)rec.last_change);
-		o_stream_send(server->output, str_data(str), str_len(str));
-		if (proxy_server_is_output_full(server))
-			break;
-	}
-	if (ret >= 0) {
-		/* continue later */
-		o_stream_set_flush_pending(server->output, TRUE);
-		return FALSE;
-	}
-	return TRUE;
-}
-
-static int
-cmd_subs_list(struct dsync_proxy_server *server,
-	      const char *const *args ATTR_UNUSED)
-{
-	if (server->subs_iter == NULL) {
-		server->subs_iter =
-			dsync_worker_subs_iter_init(server->worker);
-	}
-
-	if (!server->subs_sending_unsubscriptions) {
-		if (!cmd_subs_list_subscriptions(server))
-			return 0;
-		/* a bit hacky way to handle this. this assumes that caller
-		   goes through all subscriptions first, and next starts
-		   going through unsubscriptions */
-		o_stream_send(server->output, "+\n", 2);
-		server->subs_sending_unsubscriptions = TRUE;
-	}
-	if (!cmd_subs_list_unsubscriptions(server))
-		return 0;
-
-	server->subs_sending_unsubscriptions = FALSE;
-	if (dsync_worker_subs_iter_deinit(&server->subs_iter) < 0) {
-		o_stream_send(server->output, "-\n", 2);
-		return -1;
-	} else {
-		o_stream_send(server->output, "+\n", 2);
-		return 1;
-	}
-}
-
-static int
-cmd_subs_set(struct dsync_proxy_server *server, const char *const *args)
-{
-	if (str_array_length(args) < 3) {
-		i_error("subs-set: Missing parameters");
-		return -1;
-	}
-
-	dsync_worker_set_subscribed(server->worker, args[0],
-				    strtoul(args[1], NULL, 10),
-				    strcmp(args[2], "1") == 0);
-	return 1;
-}
-
-static int
-cmd_msg_list_init(struct dsync_proxy_server *server, const char *const *args)
-{
-	mailbox_guid_t *mailboxes;
-	unsigned int i, count;
-	int ret;
-
-	count = str_array_length(args);
-	mailboxes = count == 0 ? NULL : t_new(mailbox_guid_t, count);
-	for (i = 0; i < count; i++) {
-		T_BEGIN {
-			ret = dsync_proxy_mailbox_guid_import(args[i],
-							      &mailboxes[i]);
-		} T_END;
-
-		if (ret < 0) {
-			i_error("msg-list: Invalid mailbox GUID '%s'", args[i]);
-			return -1;
-		}
-	}
-	server->msg_iter = dsync_worker_msg_iter_init(server->worker,
-						      mailboxes, count);
-	return 0;
-}
-
-static int
-cmd_msg_list(struct dsync_proxy_server *server, const char *const *args)
-{
-	unsigned int mailbox_idx;
-	struct dsync_message msg;
-	string_t *str;
-	int ret;
-
-	if (server->msg_iter == NULL) {
-		if (cmd_msg_list_init(server, args) < 0)
-			return -1;
-	}
-
-	str = t_str_new(256);
-	while ((ret = dsync_worker_msg_iter_next(server->msg_iter,
-						 &mailbox_idx, &msg)) > 0) {
-		str_truncate(str, 0);
-		str_printfa(str, "%u\t", mailbox_idx);
-		dsync_proxy_msg_export(str, &msg);
-		str_append_c(str, '\n');
-		o_stream_send(server->output, str_data(str), str_len(str));
-		if (proxy_server_is_output_full(server))
-			break;
-	}
-	if (ret >= 0) {
-		/* continue later */
-		o_stream_set_flush_pending(server->output, TRUE);
-		return 0;
-	}
-	if (dsync_worker_msg_iter_deinit(&server->msg_iter) < 0) {
-		o_stream_send(server->output, "-\n", 2);
-		return -1;
-	} else {
-		o_stream_send(server->output, "+\n", 2);
-		return 1;
-	}
-}
-
-static int
-cmd_box_create(struct dsync_proxy_server *server, const char *const *args)
-{
-	struct dsync_mailbox dsync_box;
-	const char *error;
-
-	if (dsync_proxy_mailbox_import_unescaped(pool_datastack_create(),
-						 args, &dsync_box,
-						 &error) < 0) {
-		i_error("Invalid mailbox input: %s", error);
-		return -1;
-	}
-	dsync_worker_create_mailbox(server->worker, &dsync_box);
-	return 1;
-}
-
-static int
-cmd_box_delete(struct dsync_proxy_server *server, const char *const *args)
-{
-	mailbox_guid_t guid;
-	struct dsync_mailbox dsync_box;
-
-	if (str_array_length(args) < 2)
-		return -1;
-	if (dsync_proxy_mailbox_guid_import(args[0], &guid) < 0) {
-		i_error("box-delete: Invalid mailbox GUID '%s'", args[0]);
-		return -1;
-	}
-
-	memset(&dsync_box, 0, sizeof(dsync_box));
-	dsync_box.mailbox_guid = guid;
-	dsync_box.last_change = strtoul(args[1], NULL, 10);
-	dsync_worker_delete_mailbox(server->worker, &dsync_box);
-	return 1;
-}
-
-static int
-cmd_dir_delete(struct dsync_proxy_server *server, const char *const *args)
-{
-	struct dsync_mailbox dsync_box;
-
-	if (str_array_length(args) < 2)
-		return -1;
-
-	memset(&dsync_box, 0, sizeof(dsync_box));
-	dsync_box.name = str_tabunescape(t_strdup_noconst(args[0]));
-	dsync_box.last_change = strtoul(args[1], NULL, 10);
-	dsync_worker_delete_dir(server->worker, &dsync_box);
-	return 1;
-}
-
-static int
-cmd_box_rename(struct dsync_proxy_server *server, const char *const *args)
-{
-	mailbox_guid_t guid;
-	struct dsync_mailbox dsync_box;
-
-	if (str_array_length(args) < 3)
-		return -1;
-	if (dsync_proxy_mailbox_guid_import(args[0], &guid) < 0) {
-		i_error("box-delete: Invalid mailbox GUID '%s'", args[0]);
-		return -1;
-	}
-
-	memset(&dsync_box, 0, sizeof(dsync_box));
-	dsync_box.name = args[1];
-	dsync_box.name_sep = args[2][0];
-	dsync_worker_rename_mailbox(server->worker, &guid, &dsync_box);
-	return 1;
-}
-
-static int
-cmd_box_update(struct dsync_proxy_server *server, const char *const *args)
-{
-	struct dsync_mailbox dsync_box;
-	const char *error;
-
-	if (dsync_proxy_mailbox_import_unescaped(pool_datastack_create(),
-						 args, &dsync_box,
-						 &error) < 0) {
-		i_error("Invalid mailbox input: %s", error);
-		return -1;
-	}
-	dsync_worker_update_mailbox(server->worker, &dsync_box);
-	return 1;
-}
-
-static int
-cmd_box_select(struct dsync_proxy_server *server, const char *const *args)
-{
-	struct dsync_mailbox box;
-	const char *error;
-
-	memset(&box, 0, sizeof(box));
-	if (args[0] == NULL ||
-	    dsync_proxy_mailbox_guid_import(args[0], &box.mailbox_guid) < 0) {
-		i_error("box-select: Invalid mailbox GUID '%s'", args[0]);
-		return -1;
-	}
-	args++;
-
-	if (dsync_proxy_cache_fields_import(args, pool_datastack_create(),
-					    &box.cache_fields, &error) < 0) {
-		i_error("box-select: %s", error);
-		return -1;
-	}
-	dsync_worker_select_mailbox(server->worker, &box);
-	return 1;
-}
-
-static int
-cmd_msg_update(struct dsync_proxy_server *server, const char *const *args)
-{
-	struct dsync_message msg;
-
-	/* uid modseq flags */
-	if (str_array_length(args) < 3)
-		return -1;
-
-	memset(&msg, 0, sizeof(msg));
-	msg.uid = strtoul(args[0], NULL, 10);
-	msg.modseq = strtoull(args[1], NULL, 10);
-	if (dsync_proxy_msg_parse_flags(pool_datastack_create(),
-					args[2], &msg) < 0)
-		return -1;
-
-	dsync_worker_msg_update_metadata(server->worker, &msg);
-	return 1;
-}
-
-static int
-cmd_msg_uid_change(struct dsync_proxy_server *server, const char *const *args)
-{
-	if (args[0] == NULL || args[1] == NULL)
-		return -1;
-
-	dsync_worker_msg_update_uid(server->worker,
-				    strtoul(args[0], NULL, 10),
-				    strtoul(args[1], NULL, 10));
-	return 1;
-}
-
-static int
-cmd_msg_expunge(struct dsync_proxy_server *server, const char *const *args)
-{
-	if (args[0] == NULL)
-		return -1;
-
-	dsync_worker_msg_expunge(server->worker, strtoul(args[0], NULL, 10));
-	return 1;
-}
-
-static void copy_callback(bool success, void *context)
-{
-	struct dsync_proxy_server *server = context;
-	const char *reply;
-
-	i_assert(server->copy_uid != 0);
-
-	reply = t_strdup_printf("%d\t%u\n", success ? 1 : 0, server->copy_uid);
-	o_stream_send_str(server->output, reply);
-}
-
-static int
-cmd_msg_copy(struct dsync_proxy_server *server, const char *const *args)
-{
-	mailbox_guid_t src_mailbox_guid;
-	uint32_t src_uid;
-	struct dsync_message msg;
-	const char *error;
-
-	/* src_mailbox_guid src_uid <message> */
-	if (str_array_length(args) < 3)
-		return -1;
-
-	if (dsync_proxy_mailbox_guid_import(args[0], &src_mailbox_guid) < 0) {
-		i_error("msg-copy: Invalid mailbox GUID '%s'", args[0]);
-		return -1;
-	}
-	src_uid = strtoul(args[1], NULL, 10);
-
-	if (dsync_proxy_msg_import_unescaped(pool_datastack_create(),
-					     args + 2, &msg, &error) < 0)
-		i_error("Invalid message input: %s", error);
-
-	server->copy_uid = src_uid;
-	dsync_worker_msg_copy(server->worker, &src_mailbox_guid, src_uid, &msg,
-			      copy_callback, server);
-	server->copy_uid = 0;
-	return 1;
-}
-
-static void cmd_msg_save_callback(void *context)
-{
-	struct dsync_proxy_server *server = context;
-
-	server->save_finished = TRUE;
-}
-
-static int
-cmd_msg_save(struct dsync_proxy_server *server, const char *const *args)
-{
-	struct dsync_message msg;
-	struct dsync_msg_static_data data;
-	const char *error;
-	int ret;
-
-	if (dsync_proxy_msg_static_import_unescaped(pool_datastack_create(),
-						    args, &data, &error) < 0) {
-		i_error("Invalid message input: %s", error);
-		return -1;
-	}
-	data.input = i_stream_create_dot(server->input, FALSE);
-
-	if (dsync_proxy_msg_import_unescaped(pool_datastack_create(),
-					     args + 2, &msg, &error) < 0) {
-		i_error("Invalid message input: %s", error);
-		return -1;
-	}
-
-	/* we rely on save reading the entire input */
-	server->save_finished = FALSE;
-	net_set_nonblock(server->fd_in, FALSE);
-	dsync_worker_msg_save(server->worker, &msg, &data,
-			      cmd_msg_save_callback, server);
-	net_set_nonblock(server->fd_in, TRUE);
-	ret = dsync_worker_has_failed(server->worker) ? -1 : 1;
-	i_assert(server->save_finished);
-	i_assert(data.input->eof || ret < 0);
-	i_stream_destroy(&data.input);
-	return ret;
-}
-
-static void cmd_msg_get_send_more(struct dsync_proxy_server *server)
-{
-	const unsigned char *data;
-	size_t size;
-	int ret;
-
-	while (!proxy_server_is_output_full(server)) {
-		ret = i_stream_read_data(server->get_input, &data, &size, 0);
-		if (ret == -1) {
-			/* done */
-			o_stream_send(server->output, "\n.\n", 3);
-			i_stream_unref(&server->get_input);
-			return;
-		} else {
-			/* for now we assume input is blocking */
-			i_assert(ret != 0);
-		}
-
-		dsync_proxy_send_dot_output(server->output,
-					    &server->get_input_last_lf,
-					    data, size);
-		i_stream_skip(server->get_input, size);
-	}
-	o_stream_set_flush_pending(server->output, TRUE);
-}
-
-static void
-cmd_msg_get_callback(enum dsync_msg_get_result result,
-		     const struct dsync_msg_static_data *data, void *context)
-{
-	struct dsync_proxy_server *server = context;
-	string_t *str;
-
-	i_assert(server->get_uid != 0);
-
-	switch (result) {
-	case DSYNC_MSG_GET_RESULT_SUCCESS:
-		break;
-	case DSYNC_MSG_GET_RESULT_EXPUNGED:
-		o_stream_send(server->output, "0\n", 3);
-		return;
-	case DSYNC_MSG_GET_RESULT_FAILED:
-		o_stream_send(server->output, "-\n", 3);
-		return;
-	}
-
-	str = t_str_new(128);
-	str_printfa(str, "1\t%u\t", server->get_uid);
-	dsync_proxy_msg_static_export(str, data);
-	str_append_c(str, '\n');
-	o_stream_send(server->output, str_data(str), str_len(str));
-
-	/* then we'll still have to send the message body. */
-	server->get_input = data->input;
-	cmd_msg_get_send_more(server);
-	if (server->get_input == NULL) {
-		/* if we came here from ioloop, make sure the command gets
-		   freed in the output flush callback */
-		o_stream_set_flush_pending(server->output, TRUE);
-	}
-}
-
-static int
-cmd_msg_get(struct dsync_proxy_server *server, const char *const *args)
-{
-	mailbox_guid_t mailbox_guid;
-	uint32_t uid;
-
-	if (str_array_length(args) < 2)
-		return -1;
-
-	if (dsync_proxy_mailbox_guid_import(args[0], &mailbox_guid) < 0) {
-		i_error("msg-get: Invalid mailbox GUID '%s'", args[0]);
-		return -1;
-	}
-
-	uid = strtoul(args[1], NULL, 10);
-	if (uid == 0)
-		return -1;
-
-	if (server->get_input != NULL) {
-		i_assert(server->get_uid == uid);
-		cmd_msg_get_send_more(server);
-	} else {
-		server->get_uid = uid;
-		dsync_worker_msg_get(server->worker, &mailbox_guid, uid,
-				     cmd_msg_get_callback, server);
-	}
-	if (server->get_input != NULL)
-		return 0;
-	server->get_uid = 0;
-	return 1;
-}
-
-static void cmd_finish_callback(bool success, void *context)
-{
-	struct dsync_proxy_server *server = context;
-	const char *reply;
-
-	if (!success)
-		reply = "fail\n";
-	else if (dsync_worker_has_unexpected_changes(server->worker))
-		reply = "changes\n";
-	else
-		reply = "ok\n";
-
-	server->finished = TRUE;
-	o_stream_send_str(server->output, reply);
-}
-
-static int
-cmd_finish(struct dsync_proxy_server *server,
-	   const char *const *args ATTR_UNUSED)
-{
-	dsync_worker_finish(server->worker, cmd_finish_callback, server);
-	return 1;
-}
-
-static struct dsync_proxy_server_command commands[] = {
-	{ "BOX-LIST", cmd_box_list },
-	{ "SUBS-LIST", cmd_subs_list },
-	{ "SUBS-SET", cmd_subs_set },
-	{ "MSG-LIST", cmd_msg_list },
-	{ "BOX-CREATE", cmd_box_create },
-	{ "BOX-DELETE", cmd_box_delete },
-	{ "DIR-DELETE", cmd_dir_delete },
-	{ "BOX-RENAME", cmd_box_rename },
-	{ "BOX-UPDATE", cmd_box_update },
-	{ "BOX-SELECT", cmd_box_select },
-	{ "MSG-UPDATE", cmd_msg_update },
-	{ "MSG-UID-CHANGE", cmd_msg_uid_change },
-	{ "MSG-EXPUNGE", cmd_msg_expunge },
-	{ "MSG-COPY", cmd_msg_copy },
-	{ "MSG-SAVE", cmd_msg_save },
-	{ "MSG-GET", cmd_msg_get },
-	{ "FINISH", cmd_finish },
-	{ NULL, NULL }
-};
-
-struct dsync_proxy_server_command *
-dsync_proxy_server_command_find(const char *name)
-{
-	unsigned int i;
-
-	for (i = 0; commands[i].name != NULL; i++) {
-		if (strcasecmp(commands[i].name, name) == 0)
-			return &commands[i];
-	}
-	return NULL;
-}
--- a/src/dsync/dsync-proxy-server.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "strescape.h"
-#include "fd-set-nonblock.h"
-#include "istream.h"
-#include "ostream.h"
-#include "master-service.h"
-#include "dsync-worker.h"
-#include "dsync-proxy.h"
-#include "dsync-proxy-server.h"
-
-#include <stdlib.h>
-
-static int
-proxy_server_read_line(struct dsync_proxy_server *server,
-		       const char **line_r)
-{
-	*line_r = i_stream_read_next_line(server->input);
-	if (*line_r == NULL) {
-		if (server->input->stream_errno != 0) {
-			errno = server->input->stream_errno;
-			i_error("read() from proxy client failed: %m");
-			master_service_stop(master_service);
-			return -1;
-		}
-		if (server->input->eof) {
-			if (!server->finished)
-				i_error("read() from proxy client failed: EOF");
-			master_service_stop(master_service);
-			return -1;
-		}
-	}
-	if (*line_r == NULL)
-		return 0;
-
-	if (!server->handshake_received) {
-		if (strcmp(*line_r, DSYNC_PROXY_CLIENT_GREETING_LINE) != 0) {
-			i_error("Invalid client handshake: %s", *line_r);
-			master_service_stop(master_service);
-			return -1;
-		}
-		server->handshake_received = TRUE;
-		return proxy_server_read_line(server, line_r);
-	}
-	return 1;
-}
-
-static int proxy_server_run_cmd(struct dsync_proxy_server *server)
-{
-	int ret;
-
-	if ((ret = server->cur_cmd->func(server, server->cur_args)) == 0)
-		return 0;
-	if (ret < 0) {
-		i_error("command %s failed", server->cur_cmd->name);
-		return -1;
-	}
-
-	server->cur_cmd = NULL;
-	server->cur_args = NULL;
-	return 1;
-}
-
-static int
-proxy_server_input_line(struct dsync_proxy_server *server, const char *line)
-{
-	const char *const *args;
-	const char **cmd_args;
-	unsigned int i, count;
-
-	i_assert(server->cur_cmd == NULL);
-
-	p_clear(server->cmd_pool);
-	args = (const char *const *)p_strsplit(server->cmd_pool, line, "\t");
-	if (args[0] == NULL) {
-		i_error("proxy client sent invalid input: %s", line);
-		return -1;
-	}
-
-	server->cur_cmd = dsync_proxy_server_command_find(args[0]);
-	if (server->cur_cmd == NULL) {
-		i_error("proxy client sent unknown command: %s", args[0]);
-		return -1;
-	} else {
-		args++;
-		count = str_array_length(args);
-
-		cmd_args = p_new(server->cmd_pool, const char *, count + 1);
-		for (i = 0; i < count; i++) {
-			cmd_args[i] = str_tabunescape(p_strdup(server->cmd_pool,
-							       args[i]));
-		}
-
-		server->cur_args = cmd_args;
-		return proxy_server_run_cmd(server);
-	}
-}
-
-static void proxy_server_input(struct dsync_proxy_server *server)
-{
-	const char *line;
-	int ret = 0;
-
-	if (server->cur_cmd != NULL) {
-		/* wait until command handling is finished */
-		io_remove(&server->io);
-		return;
-	}
-
-	o_stream_cork(server->output);
-	while (proxy_server_read_line(server, &line) > 0) {
-		T_BEGIN {
-			ret = proxy_server_input_line(server, line);
-		} T_END;
-		if (ret <= 0)
-			break;
-	}
-	o_stream_uncork(server->output);
-	if (server->output->closed)
-		ret = -1;
-
-	if (ret < 0)
-		master_service_stop(master_service);
-	timeout_reset(server->to);
-}
-
-static int proxy_server_output(struct dsync_proxy_server *server)
-{
-	struct ostream *output = server->output;
-	int ret;
-
-	if ((ret = o_stream_flush(output)) < 0)
-		ret = 1;
-	else if (server->cur_cmd != NULL) {
-		o_stream_cork(output);
-		(void)proxy_server_run_cmd(server);
-		o_stream_uncork(output);
-
-		if (server->cur_cmd == NULL) {
-			if (server->io == NULL) {
-				server->io = io_add(server->fd_in, IO_READ,
-						    proxy_server_input, server);
-			}
-			/* handle pending input */
-			proxy_server_input(server);
-		}
-	}
-	if (output->closed)
-		master_service_stop(master_service);
-	timeout_reset(server->to);
-	return ret;
-}
-
-static void dsync_proxy_server_timeout(void *context ATTR_UNUSED)
-{
-	i_error("proxy server timed out");
-	master_service_stop(master_service);
-}
-
-struct dsync_proxy_server *
-dsync_proxy_server_init(int fd_in, int fd_out, struct dsync_worker *worker)
-{
-	struct dsync_proxy_server *server;
-
-	server = i_new(struct dsync_proxy_server, 1);
-	server->worker = worker;
-	server->fd_in = fd_in;
-	server->fd_out = fd_out;
-
-	server->cmd_pool = pool_alloconly_create("worker server cmd", 1024);
-	server->io = io_add(fd_in, IO_READ, proxy_server_input, server);
-	server->input = i_stream_create_fd(fd_in, (size_t)-1, FALSE);
-	server->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
-	server->to = timeout_add(DSYNC_PROXY_SERVER_TIMEOUT_MSECS,
-				 dsync_proxy_server_timeout, NULL);
-	o_stream_set_flush_callback(server->output, proxy_server_output,
-				    server);
-	o_stream_send_str(server->output, DSYNC_PROXY_SERVER_GREETING_LINE"\n");
-	fd_set_nonblock(fd_in, TRUE);
-	fd_set_nonblock(fd_out, TRUE);
-	return server;
-}
-
-void dsync_proxy_server_deinit(struct dsync_proxy_server **_server)
-{
-	struct dsync_proxy_server *server = *_server;
-
-	*_server = NULL;
-
-	if (server->get_input != NULL)
-		i_stream_unref(&server->get_input);
-	pool_unref(&server->cmd_pool);
-	timeout_remove(&server->to);
-	if (server->io != NULL)
-		io_remove(&server->io);
-	i_stream_destroy(&server->input);
-	o_stream_destroy(&server->output);
-	if (close(server->fd_in) < 0)
-		i_error("close(proxy input) failed: %m");
-	if (server->fd_in != server->fd_out) {
-		if (close(server->fd_out) < 0)
-			i_error("close(proxy output) failed: %m");
-	}
-	i_free(server);
-}
--- a/src/dsync/dsync-proxy-server.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-#ifndef DSYNC_PROXY_SERVER_H
-#define DSYNC_PROXY_SERVER_H
-
-struct dsync_proxy_server;
-
-struct dsync_proxy_server_command {
-	const char *name;
-	int (*func)(struct dsync_proxy_server *server,
-		    const char *const *args);
-};
-
-struct dsync_proxy_server {
-	int fd_in, fd_out;
-	struct io *io;
-	struct istream *input;
-	struct ostream *output;
-	struct timeout *to;
-
-	struct dsync_worker *worker;
-
-	pool_t cmd_pool;
-	struct dsync_proxy_server_command *cur_cmd;
-	const char *const *cur_args;
-
-	struct dsync_worker_mailbox_iter *mailbox_iter;
-	struct dsync_worker_subs_iter *subs_iter;
-	struct dsync_worker_msg_iter *msg_iter;
-
-	struct istream *get_input;
-	bool get_input_last_lf;
-	uint32_t get_uid, copy_uid;
-
-	unsigned int handshake_received:1;
-	unsigned int subs_sending_unsubscriptions:1;
-	unsigned int save_finished:1;
-	unsigned int finished:1;
-};
-
-struct dsync_proxy_server *
-dsync_proxy_server_init(int fd_in, int fd_out, struct dsync_worker *worker);
-void dsync_proxy_server_deinit(struct dsync_proxy_server **server);
-
-struct dsync_proxy_server_command *
-dsync_proxy_server_command_find(const char *name);
-
-#endif
--- a/src/dsync/dsync-proxy.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,412 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "strescape.h"
-#include "ostream.h"
-#include "hex-binary.h"
-#include "mail-types.h"
-#include "imap-util.h"
-#include "mail-cache.h"
-#include "dsync-data.h"
-#include "dsync-proxy.h"
-
-#include <stdlib.h>
-
-#define DSYNC_CACHE_DECISION_NO 'n'
-#define DSYNC_CACHE_DECISION_YES 'y'
-#define DSYNC_CACHE_DECISION_TEMP 't'
-#define DSYNC_CACHE_DECISION_FORCED 'f'
-
-void dsync_proxy_cache_fields_export(string_t *str,
-				     const ARRAY_TYPE(mailbox_cache_field) *_fields)
-{
-	const struct mailbox_cache_field *fields;
-	unsigned int i, count;
-
-	if (!array_is_created(_fields))
-		return;
-
-	fields = array_get(_fields, &count);
-	for (i = 0; i < count; i++) {
-		str_append_c(str, '\t');
-		str_tabescape_write(str, fields[i].name);
-		str_append_c(str, '\t');
-		switch (fields[i].decision & ~MAIL_CACHE_DECISION_FORCED) {
-		case MAIL_CACHE_DECISION_NO:
-			str_append_c(str, DSYNC_CACHE_DECISION_NO);
-			break;
-		case MAIL_CACHE_DECISION_YES:
-			str_append_c(str, DSYNC_CACHE_DECISION_YES);
-			break;
-		case MAIL_CACHE_DECISION_TEMP:
-			str_append_c(str, DSYNC_CACHE_DECISION_TEMP);
-			break;
-		}
-		if ((fields[i].decision & MAIL_CACHE_DECISION_FORCED) != 0)
-			str_append_c(str, DSYNC_CACHE_DECISION_FORCED);
-		str_printfa(str, "\t%ld", fields[i].last_used);
-	}
-}
-
-static int dsync_proxy_cache_dec_parse(const char *str,
-				       enum mail_cache_decision_type *dec_r)
-{
-	switch (*str++) {
-	case DSYNC_CACHE_DECISION_NO:
-		*dec_r = MAIL_CACHE_DECISION_NO;
-		break;
-	case DSYNC_CACHE_DECISION_YES:
-		*dec_r = MAIL_CACHE_DECISION_YES;
-		break;
-	case DSYNC_CACHE_DECISION_TEMP:
-		*dec_r = MAIL_CACHE_DECISION_TEMP;
-		break;
-	default:
-		return -1;
-	}
-	if (*str == DSYNC_CACHE_DECISION_FORCED) {
-		*dec_r |= MAIL_CACHE_DECISION_FORCED;
-		str++;
-	}
-	if (*str != '\0')
-		return -1;
-	return 0;
-}
-
-int dsync_proxy_cache_fields_import(const char *const *args, pool_t pool,
-				    ARRAY_TYPE(mailbox_cache_field) *fields,
-				    const char **error_r)
-{
-	struct mailbox_cache_field *cache_field;
-	enum mail_cache_decision_type dec;
-	unsigned int i, count = str_array_length(args);
-
-	if (count % 3 != 0) {
-		*error_r = "Invalid mailbox cache fields";
-		return -1;
-	}
-	if (!array_is_created(fields))
-		p_array_init(fields, pool, (count/3) + 1);
-	for (i = 0; i < count; i += 3) {
-		cache_field = array_append_space(fields);
-		cache_field->name = p_strdup(pool, args[i]);
-
-		if (dsync_proxy_cache_dec_parse(args[i+1], &dec) < 0) {
-			*error_r = "Invalid cache decision";
-			return -1;
-		}
-		cache_field->decision = dec;
-		if (str_to_time(args[i+2], &cache_field->last_used) < 0) {
-			*error_r = "Invalid cache last_used";
-			return -1;
-		}
-	}
-	return 0;
-}
-
-void dsync_proxy_msg_export(string_t *str,
-			    const struct dsync_message *msg)
-{
-	str_tabescape_write(str, msg->guid);
-	str_printfa(str, "\t%u\t%llu\t", msg->uid,
-		    (unsigned long long)msg->modseq);
-	if ((msg->flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0)
-		str_append(str, "\\dsync-expunged ");
-	imap_write_flags(str, msg->flags & MAIL_FLAGS_MASK, msg->keywords);
-	str_printfa(str, "\t%ld", (long)msg->save_date);
-}
-
-int dsync_proxy_msg_parse_flags(pool_t pool, const char *str,
-				struct dsync_message *msg_r)
-{
-	ARRAY_TYPE(const_string) keywords;
-	const char *const *args, *kw;
-	enum mail_flags flag;
-
-	msg_r->flags = 0;
-	p_array_init(&keywords, pool, 16);
-	for (args = t_strsplit_spaces(str, " "); *args != NULL; args++) {
-		if (**args != '\\') {
-			kw = p_strdup(pool, *args);
-			array_append(&keywords, &kw, 1);
-		} else if (strcasecmp(*args, "\\dsync-expunged") == 0) {
-			msg_r->flags |= DSYNC_MAIL_FLAG_EXPUNGED;
-		} else {
-			flag = imap_parse_system_flag(*args);
-			if (flag == 0)
-				return -1;
-			msg_r->flags |= flag;
-		}
-	}
-	(void)array_append_space(&keywords);
-
-	msg_r->keywords = array_idx(&keywords, 0);
-	return 0;
-}
-
-int dsync_proxy_msg_import_unescaped(pool_t pool, const char *const *args,
-				     struct dsync_message *msg_r,
-				     const char **error_r)
-{
-	/* guid uid modseq flags save_date */
-	if (str_array_length(args) < 5) {
-		*error_r = "Missing parameters";
-		return -1;
-	}
-
-	memset(msg_r, 0, sizeof(*msg_r));
-	msg_r->guid = p_strdup(pool, args[0]);
-	msg_r->uid = strtoul(args[1], NULL, 10);
-	msg_r->modseq = strtoull(args[2], NULL, 10);
-	if (dsync_proxy_msg_parse_flags(pool, args[3], msg_r) < 0) {
-		*error_r = "Invalid system flags";
-		return -1;
-	}
-	msg_r->save_date = strtoul(args[4], NULL, 10);
-	return 0;
-}
-
-int dsync_proxy_msg_import(pool_t pool, const char *str,
-			   struct dsync_message *msg_r, const char **error_r)
-{
-	char **args;
-	unsigned int i;
-	int ret;
-
-	T_BEGIN {
-		args = p_strsplit(pool_datastack_create(), str, "\t");
-		for (i = 0; args[i] != NULL; i++)
-			args[i] = str_tabunescape(args[i]);
-		ret = dsync_proxy_msg_import_unescaped(pool,
-						(const char *const *)args,
-						msg_r, error_r);
-	} T_END;
-	return ret;
-}
-
-void dsync_proxy_msg_static_export(string_t *str,
-				   const struct dsync_msg_static_data *msg)
-{
-	str_printfa(str, "%ld\t", (long)msg->received_date);
-	str_tabescape_write(str, msg->pop3_uidl);
-}
-
-int dsync_proxy_msg_static_import_unescaped(pool_t pool,
-					    const char *const *args,
-					    struct dsync_msg_static_data *msg_r,
-					    const char **error_r)
-{
-	/* received_date pop3_uidl */
-	if (str_array_length(args) < 2) {
-		*error_r = "Missing parameters";
-		return -1;
-	}
-
-	memset(msg_r, 0, sizeof(*msg_r));
-	msg_r->received_date = strtoul(args[0], NULL, 10);
-	msg_r->pop3_uidl = p_strdup(pool, args[1]);
-	return 0;
-}
-
-int dsync_proxy_msg_static_import(pool_t pool, const char *str,
-				  struct dsync_msg_static_data *msg_r,
-				  const char **error_r)
-{
-	char **args;
-	unsigned int i;
-	int ret;
-
-	T_BEGIN {
-		args = p_strsplit(pool_datastack_create(), str, "\t");
-		for (i = 0; args[i] != NULL; i++)
-			args[i] = str_tabunescape(args[i]);
-		ret = dsync_proxy_msg_static_import_unescaped(pool, 
-						(const char *const *)args,
-						msg_r, error_r);
-	} T_END;
-	return ret;
-}
-
-void dsync_proxy_mailbox_export(string_t *str,
-				const struct dsync_mailbox *box)
-{
-	char s[2];
-
-	str_tabescape_write(str, box->name);
-	str_append_c(str, '\t');
-	s[0] = box->name_sep; s[1] = '\0';
-	str_tabescape_write(str, s);
-	str_printfa(str, "\t%lu\t%u", (unsigned long)box->last_change,
-		    box->flags);
-
-	if (dsync_mailbox_is_noselect(box)) {
-		i_assert(box->uid_validity == 0);
-		return;
-	}
-	i_assert(box->uid_validity != 0 ||
-		 (box->flags & DSYNC_MAILBOX_FLAG_DELETED_MAILBOX) != 0);
-	i_assert(box->uid_validity == 0 || box->uid_next != 0);
-
-	str_append_c(str, '\t');
-	dsync_proxy_mailbox_guid_export(str, &box->mailbox_guid);
-	str_printfa(str, "\t%u\t%u\t%u\t%llu\t%u",
-		    box->uid_validity, box->uid_next, box->message_count,
-		    (unsigned long long)box->highest_modseq,
-		    box->first_recent_uid);
-	dsync_proxy_cache_fields_export(str, &box->cache_fields);
-}
-
-int dsync_proxy_mailbox_import_unescaped(pool_t pool, const char *const *args,
-					 struct dsync_mailbox *box_r,
-					 const char **error_r)
-{
-	unsigned int i = 0, count;
-	bool box_deleted;
-	char *p;
-
-	memset(box_r, 0, sizeof(*box_r));
-
-	count = str_array_length(args);
-	if (count != 4 && count < 8) {
-		*error_r = "Mailbox missing parameters";
-		return -1;
-	}
-
-	/* name dir_guid mailbox_guid uid_validity uid_next
-	   message_count highest_modseq */
-	box_r->name = p_strdup(pool, args[i++]);
-	dsync_str_sha_to_guid(box_r->name, &box_r->name_sha1);
-
-	if (strlen(args[i]) > 1) {
-		*error_r = "Invalid mailbox name hierarchy separator";
-		return -1;
-	}
-	box_r->name_sep = args[i++][0];
-
-	box_r->last_change = strtoul(args[i++], &p, 10);
-	if (*p != '\0') {
-		*error_r = "Invalid mailbox last_change";
-		return -1;
-	}
-	box_r->flags = strtoul(args[i++], &p, 10);
-	if (*p != '\0' ||
-	    (dsync_mailbox_is_noselect(box_r) != (args[i] == NULL))) {
-		*error_r = "Invalid mailbox flags";
-		return -1;
-	}
-	box_deleted = (box_r->flags & (DSYNC_MAILBOX_FLAG_DELETED_MAILBOX |
-				       DSYNC_MAILBOX_FLAG_DELETED_DIR)) != 0;
-	if (box_r->name_sep == '\0' && !box_deleted) {
-		*error_r = "Missing mailbox name hierarchy separator";
-		return -1;
-	}
-
-	if (args[i] == NULL) {
-		/* \noselect mailbox */
-		return 0;
-	}
-
-	if (dsync_proxy_mailbox_guid_import(args[i++],
-					    &box_r->mailbox_guid) < 0) {
-		*error_r = "Invalid mailbox GUID";
-		return -1;
-	}
-
-	box_r->uid_validity = strtoul(args[i++], &p, 10);
-	if (*p != '\0' || (box_r->uid_validity == 0 && !box_deleted)) {
-		abort();
-		*error_r = "Invalid mailbox uid_validity";
-		return -1;
-	}
-
-	box_r->uid_next = strtoul(args[i++], &p, 10);
-	if (*p != '\0' || (box_r->uid_next == 0 && !box_deleted)) {
-		*error_r = "Invalid mailbox uid_next";
-		return -1;
-	}
-
-	box_r->message_count = strtoul(args[i++], &p, 10);
-	if (*p != '\0') {
-		*error_r = "Invalid mailbox message_count";
-		return -1;
-	}
-
-	box_r->highest_modseq = strtoull(args[i++], &p, 10);
-	if (*p != '\0') {
-		*error_r = "Invalid mailbox highest_modseq";
-		return -1;
-	}
-
-	box_r->first_recent_uid = strtoul(args[i++], &p, 10);
-	if (*p != '\0') {
-		*error_r = "Invalid mailbox first_recent_uid";
-		return -1;
-	}
-
-	args += i;
-	count -= i;
-	if (dsync_proxy_cache_fields_import(args, pool, &box_r->cache_fields,
-					    error_r) < 0)
-		return -1;
-	return 0;
-}
-
-int dsync_proxy_mailbox_import(pool_t pool, const char *str,
-			       struct dsync_mailbox *box_r,
-			       const char **error_r)
-{
-	char **args;
-	int ret;
-
-	T_BEGIN {
-		args = p_strsplit(pool_datastack_create(), str, "\t");
-		if (args[0] != NULL)
-			args[0] = str_tabunescape(args[0]);
-		ret = dsync_proxy_mailbox_import_unescaped(pool,
-						(const char *const *)args,
-						box_r, error_r);
-	} T_END;
-	return ret;
-}
-
-void dsync_proxy_mailbox_guid_export(string_t *str,
-				     const mailbox_guid_t *mailbox)
-{
-	str_append(str, dsync_guid_to_str(mailbox));
-}
-
-int dsync_proxy_mailbox_guid_import(const char *str, mailbox_guid_t *guid_r)
-{
-	buffer_t *buf;
-
-	buf = buffer_create_dynamic(pool_datastack_create(),
-				    sizeof(guid_r->guid));
-	if (hex_to_binary(str, buf) < 0 || buf->used != sizeof(guid_r->guid))
-		return -1;
-	memcpy(guid_r->guid, buf->data, sizeof(guid_r->guid));
-	return 0;
-}
-
-void dsync_proxy_send_dot_output(struct ostream *output, bool *last_lf,
-				 const unsigned char *data, size_t size)
-{
-	size_t i, start;
-
-	i_assert(size > 0);
-
-	if (*last_lf && data[0] == '.')
-		o_stream_send(output, ".", 1);
-
-	for (i = 1, start = 0; i < size; i++) {
-		if (data[i-1] == '\n' && data[i] == '.') {
-			o_stream_send(output, data + start, i - start);
-			o_stream_send(output, ".", 1);
-			start = i;
-		}
-	}
-	o_stream_send(output, data + start, i - start);
-	*last_lf = data[i-1] == '\n';
-	i_assert(i == size);
-}
--- a/src/dsync/dsync-proxy.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-#ifndef DSYNC_PROXY_H
-#define DSYNC_PROXY_H
-
-#include "dsync-data.h"
-
-#define DSYNC_PROXY_CLIENT_TIMEOUT_MSECS (14*60*1000)
-#define DSYNC_PROXY_SERVER_TIMEOUT_MSECS (15*60*1000)
-
-#define DSYNC_PROXY_CLIENT_GREETING_LINE "dsync-client\t2"
-#define DSYNC_PROXY_SERVER_GREETING_LINE "dsync-server\t2"
-
-struct dsync_message;
-struct dsync_mailbox;
-
-void dsync_proxy_cache_fields_export(string_t *str,
-				     const ARRAY_TYPE(mailbox_cache_field) *fields);
-int dsync_proxy_cache_fields_import(const char *const *args, pool_t pool,
-				    ARRAY_TYPE(mailbox_cache_field) *fields,
-				    const char **error_r);
-
-void dsync_proxy_msg_export(string_t *str, const struct dsync_message *msg);
-int dsync_proxy_msg_parse_flags(pool_t pool, const char *str,
-				struct dsync_message *msg_r);
-int dsync_proxy_msg_import_unescaped(pool_t pool, const char *const *args,
-				     struct dsync_message *msg_r,
-				     const char **error_r);
-int dsync_proxy_msg_import(pool_t pool, const char *str,
-			   struct dsync_message *msg_r, const char **error_r);
-
-void dsync_proxy_msg_static_export(string_t *str,
-				   const struct dsync_msg_static_data *msg);
-int dsync_proxy_msg_static_import(pool_t pool, const char *str,
-				  struct dsync_msg_static_data *msg_r,
-				  const char **error_r);
-int dsync_proxy_msg_static_import_unescaped(pool_t pool,
-					    const char *const *args,
-					    struct dsync_msg_static_data *msg_r,
-					    const char **error_r);
-
-void dsync_proxy_mailbox_export(string_t *str, const struct dsync_mailbox *box);
-int dsync_proxy_mailbox_import(pool_t pool, const char *str,
-			       struct dsync_mailbox *box_r,
-			       const char **error_r);
-int dsync_proxy_mailbox_import_unescaped(pool_t pool, const char *const *args,
-					 struct dsync_mailbox *box_r,
-					 const char **error_r);
-
-void dsync_proxy_mailbox_guid_export(string_t *str,
-				     const mailbox_guid_t *mailbox);
-int dsync_proxy_mailbox_guid_import(const char *str, mailbox_guid_t *guid_r);
-
-void dsync_proxy_send_dot_output(struct ostream *output, bool *last_lf,
-				 const unsigned char *data, size_t size);
-
-#endif
--- a/src/dsync/dsync-worker-local.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1895 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "hash.h"
-#include "str.h"
-#include "hex-binary.h"
-#include "network.h"
-#include "istream.h"
-#include "settings-parser.h"
-#include "mailbox-log.h"
-#include "mail-user.h"
-#include "mail-namespace.h"
-#include "mail-storage.h"
-#include "mail-search-build.h"
-#include "mailbox-list-private.h"
-#include "dsync-worker-private.h"
-
-#include <ctype.h>
-
-struct local_dsync_worker_mailbox_iter {
-	struct dsync_worker_mailbox_iter iter;
-	pool_t ret_pool;
-	struct mailbox_list_iterate_context *list_iter;
-	struct hash_iterate_context *deleted_iter;
-	struct hash_iterate_context *deleted_dir_iter;
-};
-
-struct local_dsync_worker_subs_iter {
-	struct dsync_worker_subs_iter iter;
-	struct mailbox_list_iterate_context *list_iter;
-	struct hash_iterate_context *deleted_iter;
-};
-
-struct local_dsync_worker_msg_iter {
-	struct dsync_worker_msg_iter iter;
-	mailbox_guid_t *mailboxes;
-	unsigned int mailbox_idx, mailbox_count;
-
-	struct mail_search_context *search_ctx;
-	struct mailbox *box;
-	struct mailbox_transaction_context *trans;
-	uint32_t prev_uid;
-
-	string_t *tmp_guid_str;
-	ARRAY_TYPE(mailbox_expunge_rec) expunges;
-	unsigned int expunge_idx;
-	unsigned int expunges_set:1;
-};
-
-struct local_dsync_mailbox {
-	struct mail_namespace *ns;
-	mailbox_guid_t guid;
-	const char *name;
-	bool deleted;
-};
-
-struct local_dsync_mailbox_change {
-	mailbox_guid_t guid;
-	time_t last_delete;
-
-	unsigned int deleted_mailbox:1;
-};
-
-struct local_dsync_dir_change {
-	mailbox_guid_t name_sha1;
-	struct mailbox_list *list;
-
-	time_t last_rename;
-	time_t last_delete;
-	time_t last_subs_change;
-
-	unsigned int unsubscribed:1;
-	unsigned int deleted_dir:1;
-};
-
-struct local_dsync_worker_msg_get {
-	mailbox_guid_t mailbox;
-	uint32_t uid;
-	dsync_worker_msg_callback_t *callback;
-	void *context;
-};
-
-struct local_dsync_worker {
-	struct dsync_worker worker;
-	struct mail_user *user;
-
-	pool_t pool;
-	/* mailbox_guid_t -> struct local_dsync_mailbox* */
-	struct hash_table *mailbox_hash;
-	/* mailbox_guid_t -> struct local_dsync_mailbox_change* */
-	struct hash_table *mailbox_changes_hash;
-	/* <-> struct local_dsync_dir_change */
-	struct hash_table *dir_changes_hash;
-
-	char alt_char;
-	ARRAY_DEFINE(subs_namespaces, struct mail_namespace *);
-
-	mailbox_guid_t selected_box_guid;
-	struct mailbox *selected_box;
-	struct mail *mail, *ext_mail;
-
-	ARRAY_TYPE(uint32_t) saved_uids;
-
-	mailbox_guid_t get_mailbox;
-	struct mail *get_mail;
-	ARRAY_DEFINE(msg_get_queue, struct local_dsync_worker_msg_get);
-
-	struct io *save_io;
-	struct mail_save_context *save_ctx;
-	struct istream *save_input;
-	dsync_worker_save_callback_t *save_callback;
-	void *save_context;
-
-	dsync_worker_finish_callback_t *finish_callback;
-	void *finish_context;
-
-	unsigned int reading_mail:1;
-	unsigned int finishing:1;
-	unsigned int finished:1;
-};
-
-extern struct dsync_worker_vfuncs local_dsync_worker;
-
-static void local_worker_mailbox_close(struct local_dsync_worker *worker);
-static void local_worker_msg_box_close(struct local_dsync_worker *worker);
-static void
-local_worker_msg_get_next(struct local_dsync_worker *worker,
-			  const struct local_dsync_worker_msg_get *get);
-
-static int mailbox_guid_cmp(const void *p1, const void *p2)
-{
-	const mailbox_guid_t *g1 = p1, *g2 = p2;
-
-	return memcmp(g1->guid, g2->guid, sizeof(g1->guid));
-}
-
-static unsigned int mailbox_guid_hash(const void *p)
-{
-	const mailbox_guid_t *guid = p;
-        const uint8_t *s = guid->guid;
-	unsigned int i, g, h = 0;
-
-	for (i = 0; i < sizeof(guid->guid); i++) {
-		h = (h << 4) + s[i];
-		if ((g = h & 0xf0000000UL)) {
-			h = h ^ (g >> 24);
-			h = h ^ g;
-		}
-	}
-	return h;
-}
-
-static struct mail_namespace *
-namespace_find_set(struct mail_user *user,
-		   const struct mail_namespace_settings *set)
-{
-	struct mail_namespace *ns;
-
-	for (ns = user->namespaces; ns != NULL; ns = ns->next) {
-		/* compare settings pointers so that it'll work
-		   for shared namespaces */
-		if (ns->set == set)
-			return ns;
-	}
-	return NULL;
-}
-
-static void dsync_drop_extra_namespaces(struct local_dsync_worker *worker)
-{
-	struct mail_user *user = worker->user;
-	struct mail_namespace_settings *const *ns_unset, *const *ns_set;
-	struct mail_namespace *ns;
-	unsigned int i, count, count2;
-
-	if (!array_is_created(&user->unexpanded_set->namespaces))
-		return;
-
-	/* drop all namespaces that have a location defined internally */
-	ns_unset = array_get(&user->unexpanded_set->namespaces, &count);
-	ns_set = array_get(&user->set->namespaces, &count2);
-	i_assert(count == count2);
-	for (i = 0; i < count; i++) {
-		if (strcmp(ns_unset[i]->location,
-			   SETTING_STRVAR_UNEXPANDED) == 0)
-			continue;
-
-		ns = namespace_find_set(user, ns_set[i]);
-		i_assert(ns != NULL);
-		if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0) {
-			/* remember the subscriptions=no namespaces so we can
-			   handle their subscriptions in parent namespaces
-			   properly */
-			mail_namespace_ref(ns);
-			array_append(&worker->subs_namespaces, &ns, 1);
-		}
-		mail_namespace_destroy(ns);
-	}
-	if (user->namespaces == NULL) {
-		i_fatal("All your namespaces have a location setting. "
-			"It should be empty (default mail_location) in the "
-			"namespace to be converted.");
-	}
-}
-
-struct dsync_worker *
-dsync_worker_init_local(struct mail_user *user, char alt_char)
-{
-	struct local_dsync_worker *worker;
-	pool_t pool;
-
-	pool = pool_alloconly_create("local dsync worker", 10240);
-	worker = p_new(pool, struct local_dsync_worker, 1);
-	worker->worker.v = local_dsync_worker;
-	worker->user = user;
-	worker->pool = pool;
-	worker->alt_char = alt_char;
-	worker->mailbox_hash =
-		hash_table_create(default_pool, pool, 0,
-				  mailbox_guid_hash, mailbox_guid_cmp);
-	i_array_init(&worker->saved_uids, 128);
-	i_array_init(&worker->msg_get_queue, 32);
-	p_array_init(&worker->subs_namespaces, pool, 8);
-	dsync_drop_extra_namespaces(worker);
-	return &worker->worker;
-}
-
-static void local_worker_deinit(struct dsync_worker *_worker)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mail_namespace **nsp;
-
-	i_assert(worker->save_input == NULL);
-
-	array_foreach_modifiable(&worker->subs_namespaces, nsp)
-		mail_namespace_unref(nsp);
-
-	local_worker_msg_box_close(worker);
-	local_worker_mailbox_close(worker);
-	hash_table_destroy(&worker->mailbox_hash);
-	if (worker->mailbox_changes_hash != NULL)
-		hash_table_destroy(&worker->mailbox_changes_hash);
-	if (worker->dir_changes_hash != NULL)
-		hash_table_destroy(&worker->dir_changes_hash);
-	array_free(&worker->msg_get_queue);
-	array_free(&worker->saved_uids);
-	pool_unref(&worker->pool);
-}
-
-static bool local_worker_is_output_full(struct dsync_worker *worker ATTR_UNUSED)
-{
-	return FALSE;
-}
-
-static int local_worker_output_flush(struct dsync_worker *worker ATTR_UNUSED)
-{
-	return 1;
-}
-
-static void
-dsync_worker_save_mailbox_change(struct local_dsync_worker *worker,
-				 const struct mailbox_log_record *rec)
-{
-	struct local_dsync_mailbox_change *change;
-	time_t stamp;
-
-	change = hash_table_lookup(worker->mailbox_changes_hash,
-				   rec->mailbox_guid);
-	if (change == NULL) {
-		change = i_new(struct local_dsync_mailbox_change, 1);
-		memcpy(change->guid.guid, rec->mailbox_guid,
-		       sizeof(change->guid.guid));
-		hash_table_insert(worker->mailbox_changes_hash,
-				  change->guid.guid, change);
-	}
-
-	stamp = mailbox_log_record_get_timestamp(rec);
-	switch (rec->type) {
-	case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
-		change->deleted_mailbox = TRUE;
-		if (change->last_delete < stamp)
-			change->last_delete = stamp;
-		break;
-	case MAILBOX_LOG_RECORD_DELETE_DIR:
-	case MAILBOX_LOG_RECORD_RENAME:
-	case MAILBOX_LOG_RECORD_SUBSCRIBE:
-	case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
-		i_unreached();
-	}
-}
-
-static void
-dsync_worker_save_dir_change(struct local_dsync_worker *worker,
-			     struct mailbox_list *list,
-			     const struct mailbox_log_record *rec)
-{
-	struct local_dsync_dir_change *change, new_change;
-	time_t stamp;
-
-	memset(&new_change, 0, sizeof(new_change));
-	new_change.list = list;
-	memcpy(new_change.name_sha1.guid, rec->mailbox_guid,
-	       sizeof(new_change.name_sha1.guid));
-
-	stamp = mailbox_log_record_get_timestamp(rec);
-	change = hash_table_lookup(worker->dir_changes_hash, &new_change);
-	if (change == NULL) {
-		change = i_new(struct local_dsync_dir_change, 1);
-		*change = new_change;
-		hash_table_insert(worker->dir_changes_hash, change, change);
-	}
-
-	switch (rec->type) {
-	case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
-		i_unreached();
-	case MAILBOX_LOG_RECORD_DELETE_DIR:
-		change->deleted_dir = TRUE;
-		if (change->last_delete < stamp)
-			change->last_delete = stamp;
-		break;
-	case MAILBOX_LOG_RECORD_RENAME:
-		if (change->last_rename < stamp)
-			change->last_rename = stamp;
-		break;
-	case MAILBOX_LOG_RECORD_SUBSCRIBE:
-	case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
-		if (change->last_subs_change > stamp) {
-			/* we've already seen a newer subscriptions state. this
-			   is probably a stale record created by dsync */
-		} else {
-			change->last_subs_change = stamp;
-			change->unsubscribed =
-				rec->type == MAILBOX_LOG_RECORD_UNSUBSCRIBE;
-		}
-		break;
-	}
-}
-
-static int
-dsync_worker_get_list_mailbox_log(struct local_dsync_worker *worker,
-				  struct mailbox_list *list)
-{
-	struct mailbox_log *log;
-	struct mailbox_log_iter *iter;
-	const struct mailbox_log_record *rec;
-
-	log = mailbox_list_get_changelog(list);
-	iter = mailbox_log_iter_init(log);
-	while ((rec = mailbox_log_iter_next(iter)) != NULL) {
-		switch (rec->type) {
-		case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
-			dsync_worker_save_mailbox_change(worker, rec);
-			break;
-		case MAILBOX_LOG_RECORD_DELETE_DIR:
-		case MAILBOX_LOG_RECORD_RENAME:
-		case MAILBOX_LOG_RECORD_SUBSCRIBE:
-		case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
-			dsync_worker_save_dir_change(worker, list, rec);
-			break;
-		}
-	}
-	return mailbox_log_iter_deinit(&iter);
-}
-
-static unsigned int mailbox_log_record_hash(const void *p)
-{
-	const uint8_t *guid = p;
-
-	return ((unsigned int)guid[0] << 24) |
-		((unsigned int)guid[1] << 16) |
-		((unsigned int)guid[2] << 8) |
-		(unsigned int)guid[3];
-}
-
-static int mailbox_log_record_cmp(const void *p1, const void *p2)
-{
-	return memcmp(p1, p2, GUID_128_SIZE);
-}
-
-static unsigned int dir_change_hash(const void *p)
-{
-	const struct local_dsync_dir_change *change = p;
-
-	return mailbox_log_record_hash(change->name_sha1.guid) ^
-		POINTER_CAST_TO(change->list, unsigned int);
-}
-
-static int dir_change_cmp(const void *p1, const void *p2)
-{
-	const struct local_dsync_dir_change *c1 = p1, *c2 = p2;
-
-	if (c1->list != c2->list)
-		return 1;
-
-	return memcmp(c1->name_sha1.guid, c2->name_sha1.guid,
-		      GUID_128_SIZE);
-}
-
-static int dsync_worker_get_mailbox_log(struct local_dsync_worker *worker)
-{
-	struct mail_namespace *ns;
-	int ret = 0;
-
-	if (worker->mailbox_changes_hash != NULL)
-		return 0;
-
-	worker->mailbox_changes_hash =
-		hash_table_create(default_pool, worker->pool, 0,
-				  mailbox_log_record_hash,
-				  mailbox_log_record_cmp);
-	worker->dir_changes_hash =
-		hash_table_create(default_pool, worker->pool, 0,
-				  dir_change_hash, dir_change_cmp);
-	for (ns = worker->user->namespaces; ns != NULL; ns = ns->next) {
-		if (ns->alias_for != NULL)
-			continue;
-
-		if (dsync_worker_get_list_mailbox_log(worker, ns->list) < 0)
-			ret = -1;
-	}
-	return ret;
-}
-
-static struct dsync_worker_mailbox_iter *
-local_worker_mailbox_iter_init(struct dsync_worker *_worker)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct local_dsync_worker_mailbox_iter *iter;
-	enum mailbox_list_iter_flags list_flags =
-		MAILBOX_LIST_ITER_SKIP_ALIASES |
-		MAILBOX_LIST_ITER_NO_AUTO_BOXES;
-	static const char *patterns[] = { "*", NULL };
-
-	iter = i_new(struct local_dsync_worker_mailbox_iter, 1);
-	iter->iter.worker = _worker;
-	iter->ret_pool = pool_alloconly_create("local mailbox iter", 1024);
-	iter->list_iter =
-		mailbox_list_iter_init_namespaces(worker->user->namespaces,
-						  patterns, NAMESPACE_PRIVATE,
-						  list_flags);
-	(void)dsync_worker_get_mailbox_log(worker);
-	return &iter->iter;
-}
-
-static void
-local_dsync_worker_add_mailbox(struct local_dsync_worker *worker,
-			       struct mail_namespace *ns, const char *name,
-			       const mailbox_guid_t *guid)
-{
-	struct local_dsync_mailbox *lbox;
-
-	lbox = p_new(worker->pool, struct local_dsync_mailbox, 1);
-	lbox->ns = ns;
-	memcpy(lbox->guid.guid, guid->guid, sizeof(lbox->guid.guid));
-	lbox->name = p_strdup(worker->pool, name);
-
-	hash_table_insert(worker->mailbox_hash, &lbox->guid, lbox);
-}
-
-static int
-iter_next_deleted(struct local_dsync_worker_mailbox_iter *iter,
-		  struct local_dsync_worker *worker,
-		  struct dsync_mailbox *dsync_box_r)
-{
-	void *key, *value;
-
-	if (iter->deleted_iter == NULL) {
-		iter->deleted_iter =
-			hash_table_iterate_init(worker->mailbox_changes_hash);
-	}
-	while (hash_table_iterate(iter->deleted_iter, &key, &value)) {
-		const struct local_dsync_mailbox_change *change = value;
-
-		if (change->deleted_mailbox) {
-			/* the name doesn't matter */
-			dsync_box_r->name = "";
-			dsync_box_r->mailbox_guid = change->guid;
-			dsync_box_r->last_change = change->last_delete;
-			dsync_box_r->flags |=
-				DSYNC_MAILBOX_FLAG_DELETED_MAILBOX;
-			return 1;
-		}
-	}
-
-	if (iter->deleted_dir_iter == NULL) {
-		iter->deleted_dir_iter =
-			hash_table_iterate_init(worker->dir_changes_hash);
-	}
-	while (hash_table_iterate(iter->deleted_dir_iter, &key, &value)) {
-		const struct local_dsync_dir_change *change = value;
-
-		if (change->deleted_dir) {
-			/* the name doesn't matter */
-			dsync_box_r->name = "";
-			dsync_box_r->name_sha1 = change->name_sha1;
-			dsync_box_r->last_change = change->last_delete;
-			dsync_box_r->flags |= DSYNC_MAILBOX_FLAG_NOSELECT |
-				DSYNC_MAILBOX_FLAG_DELETED_DIR;
-			return 1;
-		}
-	}
-	hash_table_iterate_deinit(&iter->deleted_iter);
-	return -1;
-}
-
-static int
-local_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *_iter,
-			       struct dsync_mailbox *dsync_box_r)
-{
-	struct local_dsync_worker_mailbox_iter *iter =
-		(struct local_dsync_worker_mailbox_iter *)_iter;
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_iter->worker;
-	const enum mailbox_flags flags = MAILBOX_FLAG_READONLY;
-	const enum mailbox_status_items status_items =
-		STATUS_UIDNEXT | STATUS_UIDVALIDITY |
-		STATUS_HIGHESTMODSEQ | STATUS_FIRST_RECENT_UID;
-	const enum mailbox_metadata_items metadata_items =
-		MAILBOX_METADATA_CACHE_FIELDS | MAILBOX_METADATA_GUID;
-	const struct mailbox_info *info;
-	const char *storage_name;
-	struct mailbox *box;
-	struct mailbox_status status;
-	struct mailbox_metadata metadata;
-	struct local_dsync_mailbox_change *change;
-	struct local_dsync_dir_change *dir_change, change_lookup;
-	struct local_dsync_mailbox *old_lbox;
-	enum mail_error error;
-	struct mailbox_cache_field *cache_fields;
-	unsigned int i, cache_field_count;
-
-	memset(dsync_box_r, 0, sizeof(*dsync_box_r));
-
-	info = mailbox_list_iter_next(iter->list_iter);
-	if (info == NULL)
-		return iter_next_deleted(iter, worker, dsync_box_r);
-
-	dsync_box_r->name = info->name;
-	dsync_box_r->name_sep = mail_namespace_get_sep(info->ns);
-
-	storage_name = mailbox_list_get_storage_name(info->ns->list, info->name);
-	dsync_str_sha_to_guid(storage_name, &dsync_box_r->name_sha1);
-
-	/* get last change timestamp */
-	change_lookup.list = info->ns->list;
-	change_lookup.name_sha1 = dsync_box_r->name_sha1;
-	dir_change = hash_table_lookup(worker->dir_changes_hash,
-				       &change_lookup);
-	if (dir_change != NULL) {
-		/* it shouldn't be marked as deleted, but drop it to be sure */
-		dir_change->deleted_dir = FALSE;
-		dsync_box_r->last_change = dir_change->last_rename;
-	}
-
-	if ((info->flags & MAILBOX_NOSELECT) != 0) {
-		dsync_box_r->flags |= DSYNC_MAILBOX_FLAG_NOSELECT;
-		local_dsync_worker_add_mailbox(worker, info->ns, info->name,
-					       &dsync_box_r->name_sha1);
-		return 1;
-	}
-
-	box = mailbox_alloc(info->ns->list, info->name, flags);
-	if (mailbox_get_status(box, status_items, &status) < 0 ||
-	    mailbox_get_metadata(box, metadata_items, &metadata) < 0) {
-		i_error("Failed to sync mailbox %s: %s", info->name,
-			mailbox_get_last_error(box, &error));
-		mailbox_free(&box);
-		if (error == MAIL_ERROR_NOTFOUND ||
-		    error == MAIL_ERROR_NOTPOSSIBLE) {
-			/* Mailbox isn't selectable, try the next one. We
-			   should have already caught \Noselect mailboxes, but
-			   check them anyway here. The NOTPOSSIBLE check is
-			   mainly for invalid mbox files. */
-			return local_worker_mailbox_iter_next(_iter,
-							      dsync_box_r);
-		}
-		_iter->failed = TRUE;
-		return -1;
-	}
-
-	change = hash_table_lookup(worker->mailbox_changes_hash, metadata.guid);
-	if (change != NULL) {
-		/* it shouldn't be marked as deleted, but drop it to be sure */
-		change->deleted_mailbox = FALSE;
-	}
-
-	memcpy(dsync_box_r->mailbox_guid.guid, metadata.guid,
-	       sizeof(dsync_box_r->mailbox_guid.guid));
-	dsync_box_r->uid_validity = status.uidvalidity;
-	dsync_box_r->uid_next = status.uidnext;
-	dsync_box_r->message_count = status.messages;
-	dsync_box_r->first_recent_uid = status.first_recent_uid;
-	dsync_box_r->highest_modseq = status.highest_modseq;
-
-	p_clear(iter->ret_pool);
-	p_array_init(&dsync_box_r->cache_fields, iter->ret_pool,
-		     array_count(metadata.cache_fields));
-	array_append_array(&dsync_box_r->cache_fields, metadata.cache_fields);
-	cache_fields = array_get_modifiable(&dsync_box_r->cache_fields,
-					    &cache_field_count);
-	for (i = 0; i < cache_field_count; i++) {
-		cache_fields[i].name =
-			p_strdup(iter->ret_pool, cache_fields[i].name);
-	}
-
-	old_lbox = hash_table_lookup(worker->mailbox_hash,
-				     &dsync_box_r->mailbox_guid);
-	if (old_lbox != NULL) {
-		i_error("Mailboxes don't have unique GUIDs: "
-			"%s is shared by %s and %s",
-			dsync_guid_to_str(&dsync_box_r->mailbox_guid),
-			old_lbox->name, info->name);
-		mailbox_free(&box);
-		_iter->failed = TRUE;
-		return -1;
-	}
-	local_dsync_worker_add_mailbox(worker, info->ns, info->name,
-				       &dsync_box_r->mailbox_guid);
-	mailbox_free(&box);
-	return 1;
-}
-
-static int
-local_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter *_iter)
-{
-	struct local_dsync_worker_mailbox_iter *iter =
-		(struct local_dsync_worker_mailbox_iter *)_iter;
-	int ret = _iter->failed ? -1 : 0;
-
-	if (mailbox_list_iter_deinit(&iter->list_iter) < 0)
-		ret = -1;
-	pool_unref(&iter->ret_pool);
-	i_free(iter);
-	return ret;
-}
-
-static struct dsync_worker_subs_iter *
-local_worker_subs_iter_init(struct dsync_worker *_worker)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct local_dsync_worker_subs_iter *iter;
-	enum mailbox_list_iter_flags list_flags =
-		MAILBOX_LIST_ITER_SKIP_ALIASES |
-		MAILBOX_LIST_ITER_SELECT_SUBSCRIBED;
-	static const char *patterns[] = { "*", NULL };
-
-	iter = i_new(struct local_dsync_worker_subs_iter, 1);
-	iter->iter.worker = _worker;
-	iter->list_iter =
-		mailbox_list_iter_init_namespaces(worker->user->namespaces,
-						  patterns, NAMESPACE_PRIVATE,
-						  list_flags);
-	(void)dsync_worker_get_mailbox_log(worker);
-	return &iter->iter;
-}
-
-static int
-local_worker_subs_iter_next(struct dsync_worker_subs_iter *_iter,
-			    struct dsync_worker_subscription *rec_r)
-{
-	struct local_dsync_worker_subs_iter *iter =
-		(struct local_dsync_worker_subs_iter *)_iter;
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_iter->worker;
-	struct local_dsync_dir_change *change, change_lookup;
-	const struct mailbox_info *info;
-	const char *storage_name;
-
-	memset(rec_r, 0, sizeof(*rec_r));
-
-	info = mailbox_list_iter_next(iter->list_iter);
-	if (info == NULL)
-		return -1;
-
-	storage_name = mailbox_list_get_storage_name(info->ns->list, info->name);
-	dsync_str_sha_to_guid(storage_name, &change_lookup.name_sha1);
-	change_lookup.list = info->ns->list;
-
-	change = hash_table_lookup(worker->dir_changes_hash,
-				   &change_lookup);
-	if (change != NULL) {
-		/* it shouldn't be marked as unsubscribed, but drop it to
-		   be sure */
-		change->unsubscribed = FALSE;
-		rec_r->last_change = change->last_subs_change;
-	}
-	rec_r->ns_prefix = info->ns->prefix;
-	rec_r->vname = info->name;
-	rec_r->storage_name = storage_name;
-	return 1;
-}
-
-static int
-local_worker_subs_iter_next_un(struct dsync_worker_subs_iter *_iter,
-			       struct dsync_worker_unsubscription *rec_r)
-{
-	struct local_dsync_worker_subs_iter *iter =
-		(struct local_dsync_worker_subs_iter *)_iter;
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_iter->worker;
-	void *key, *value;
-
-	if (iter->deleted_iter == NULL) {
-		iter->deleted_iter =
-			hash_table_iterate_init(worker->dir_changes_hash);
-	}
-	while (hash_table_iterate(iter->deleted_iter, &key, &value)) {
-		const struct local_dsync_dir_change *change = value;
-
-		if (change->unsubscribed) {
-			/* the name doesn't matter */
-			struct mail_namespace *ns =
-				mailbox_list_get_namespace(change->list);
-			memset(rec_r, 0, sizeof(*rec_r));
-			rec_r->name_sha1 = change->name_sha1;
-			rec_r->ns_prefix = ns->prefix;
-			rec_r->last_change = change->last_subs_change;
-			return 1;
-		}
-	}
-	hash_table_iterate_deinit(&iter->deleted_iter);
-	return -1;
-}
-
-static int
-local_worker_subs_iter_deinit(struct dsync_worker_subs_iter *_iter)
-{
-	struct local_dsync_worker_subs_iter *iter =
-		(struct local_dsync_worker_subs_iter *)_iter;
-	int ret = _iter->failed ? -1 : 0;
-
-	if (mailbox_list_iter_deinit(&iter->list_iter) < 0)
-		ret = -1;
-	i_free(iter);
-	return ret;
-}
-
-static void
-local_worker_set_subscribed(struct dsync_worker *_worker,
-			    const char *name, time_t last_change, bool set)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mail_namespace *ns;
-	struct mailbox *box;
-
-	ns = mail_namespace_find(worker->user->namespaces, name);
-	if (ns == NULL) {
-		i_error("Can't find namespace for mailbox %s", name);
-		return;
-	}
-
-	box = mailbox_alloc(ns->list, name, 0);
-	ns = mailbox_get_namespace(box);
-
-	mailbox_list_set_changelog_timestamp(ns->list, last_change);
-	if (mailbox_set_subscribed(box, set) < 0) {
-		dsync_worker_set_failure(_worker);
-		i_error("Can't update subscription %s: %s", name,
-			mail_storage_get_last_error(mailbox_get_storage(box),
-						    NULL));
-	}
-	mailbox_list_set_changelog_timestamp(ns->list, (time_t)-1);
-	mailbox_free(&box);
-}
-
-static int local_mailbox_open(struct local_dsync_worker *worker,
-			      const mailbox_guid_t *guid,
-			      struct mailbox **box_r)
-{
-	struct local_dsync_mailbox *lbox;
-	struct mailbox *box;
-	struct mailbox_metadata metadata;
-
-	lbox = hash_table_lookup(worker->mailbox_hash, guid);
-	if (lbox == NULL) {
-		i_error("Trying to open a non-listed mailbox with guid=%s",
-			dsync_guid_to_str(guid));
-		return -1;
-	}
-	if (lbox->deleted) {
-		*box_r = NULL;
-		return 0;
-	}
-
-	box = mailbox_alloc(lbox->ns->list, lbox->name, 0);
-	if (mailbox_sync(box, 0) < 0 ||
-	    mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
-		i_error("Failed to sync mailbox %s: %s", lbox->name,
-			mailbox_get_last_error(box, NULL));
-		mailbox_free(&box);
-		return -1;
-	}
-
-	if (memcmp(metadata.guid, guid->guid, sizeof(guid->guid)) != 0) {
-		i_error("Mailbox %s changed its GUID (%s -> %s)",
-			lbox->name, dsync_guid_to_str(guid),
-			guid_128_to_string(metadata.guid));
-		mailbox_free(&box);
-		return -1;
-	}
-	*box_r = box;
-	return 1;
-}
-
-static int iter_local_mailbox_open(struct local_dsync_worker_msg_iter *iter)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)iter->iter.worker;
-	mailbox_guid_t *guid;
-	struct mailbox *box;
-	struct mail_search_args *search_args;
-	int ret;
-
-	for (;;) {
-		if (iter->mailbox_idx == iter->mailbox_count) {
-			/* no more mailboxes */
-			return -1;
-		}
-
-		guid = &iter->mailboxes[iter->mailbox_idx];
-		ret = local_mailbox_open(worker, guid, &box);
-		if (ret != 0)
-			break;
-		/* mailbox was deleted. try next one. */
-		iter->mailbox_idx++;
-	}
-	if (ret < 0) {
-		i_error("msg iteration failed: Couldn't open mailbox %s",
-			dsync_guid_to_str(guid));
-		iter->iter.failed = TRUE;
-		return -1;
-	}
-
-	search_args = mail_search_build_init();
-	mail_search_build_add_all(search_args);
-
-	iter->box = box;
-	iter->trans = mailbox_transaction_begin(box, 0);
-	iter->search_ctx =
-		mailbox_search_init(iter->trans, search_args, NULL,
-				    MAIL_FETCH_FLAGS | MAIL_FETCH_GUID, NULL);
-	return 0;
-}
-
-static void
-iter_local_mailbox_close(struct local_dsync_worker_msg_iter *iter)
-{
-	iter->expunges_set = FALSE;
-	if (mailbox_search_deinit(&iter->search_ctx) < 0) {
-		i_error("msg search failed: %s",
-			mailbox_get_last_error(iter->box, NULL));
-		iter->iter.failed = TRUE;
-	}
-	(void)mailbox_transaction_commit(&iter->trans);
-	mailbox_free(&iter->box);
-}
-
-static struct dsync_worker_msg_iter *
-local_worker_msg_iter_init(struct dsync_worker *worker,
-			   const mailbox_guid_t mailboxes[],
-			   unsigned int mailbox_count)
-{
-	struct local_dsync_worker_msg_iter *iter;
-	unsigned int i;
-
-	iter = i_new(struct local_dsync_worker_msg_iter, 1);
-	iter->iter.worker = worker;
-	iter->mailboxes = mailbox_count == 0 ? NULL :
-		i_new(mailbox_guid_t, mailbox_count);
-	iter->mailbox_count = mailbox_count;
-	for (i = 0; i < mailbox_count; i++) {
-		memcpy(iter->mailboxes[i].guid, &mailboxes[i],
-		       sizeof(iter->mailboxes[i].guid));
-	}
-	i_array_init(&iter->expunges, 32);
-	iter->tmp_guid_str = str_new(default_pool, GUID_128_SIZE * 2 + 1);
-	(void)iter_local_mailbox_open(iter);
-	return &iter->iter;
-}
-
-static int mailbox_expunge_rec_cmp(const struct mailbox_expunge_rec *e1,
-				   const struct mailbox_expunge_rec *e2)
-{
-	if (e1->uid < e2->uid)
-		return -1;
-	else if (e1->uid > e2->uid)
-		return 1;
-	else
-		return 0;
-}
-
-static bool
-iter_local_mailbox_next_expunge(struct local_dsync_worker_msg_iter *iter,
-				uint32_t prev_uid, struct dsync_message *msg_r)
-{
-	struct mailbox *box = iter->box;
-	struct mailbox_status status;
-	const uint8_t *guid_128;
-	const struct mailbox_expunge_rec *expunges;
-	unsigned int count;
-
-	if (iter->expunges_set) {
-		expunges = array_get(&iter->expunges, &count);
-		if (iter->expunge_idx == count)
-			return FALSE;
-
-		memset(msg_r, 0, sizeof(*msg_r));
-		str_truncate(iter->tmp_guid_str, 0);
-		guid_128 = expunges[iter->expunge_idx].guid_128;
-		if (!guid_128_is_empty(guid_128)) {
-			binary_to_hex_append(iter->tmp_guid_str, guid_128,
-					     GUID_128_SIZE);
-		}
-		msg_r->guid = str_c(iter->tmp_guid_str);
-		msg_r->uid = expunges[iter->expunge_idx].uid;
-		msg_r->flags = DSYNC_MAIL_FLAG_EXPUNGED;
-		iter->expunge_idx++;
-		return TRUE;
-	}
-
-	/* initialize list of expunged messages at the end of mailbox */
-	iter->expunge_idx = 0;
-	array_clear(&iter->expunges);
-	iter->expunges_set = TRUE;
-
-	mailbox_get_open_status(box, STATUS_UIDNEXT, &status);
-	if (prev_uid + 1 >= status.uidnext) {
-		/* no expunged messages at the end of mailbox */
-		return FALSE;
-	}
-
-	T_BEGIN {
-		ARRAY_TYPE(seq_range) uids_filter;
-
-		t_array_init(&uids_filter, 1);
-		seq_range_array_add_range(&uids_filter, prev_uid + 1,
-					  status.uidnext - 1);
-		(void)mailbox_get_expunges(box, 0, &uids_filter,
-					   &iter->expunges);
-		array_sort(&iter->expunges, mailbox_expunge_rec_cmp);
-	} T_END;
-	return iter_local_mailbox_next_expunge(iter, prev_uid, msg_r);
-}
-
-static int
-local_worker_msg_iter_next(struct dsync_worker_msg_iter *_iter,
-			   unsigned int *mailbox_idx_r,
-			   struct dsync_message *msg_r)
-{
-	struct local_dsync_worker_msg_iter *iter =
-		(struct local_dsync_worker_msg_iter *)_iter;
-	struct mail *mail;
-	const char *guid;
-
-	if (_iter->failed || iter->search_ctx == NULL)
-		return -1;
-
-	if (!mailbox_search_next(iter->search_ctx, &mail)) {
-		if (iter_local_mailbox_next_expunge(iter, iter->prev_uid,
-						    msg_r)) {
-			*mailbox_idx_r = iter->mailbox_idx;
-			return 1;
-		}
-		iter_local_mailbox_close(iter);
-		iter->mailbox_idx++;
-		if (iter_local_mailbox_open(iter) < 0)
-			return -1;
-		return local_worker_msg_iter_next(_iter, mailbox_idx_r, msg_r);
-	}
-	*mailbox_idx_r = iter->mailbox_idx;
-	iter->prev_uid = mail->uid;
-
-	if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0) {
-		if (!mail->expunged) {
-			i_error("msg guid lookup failed: %s",
-				mailbox_get_last_error(mail->box, NULL));
-			_iter->failed = TRUE;
-			return -1;
-		}
-		return local_worker_msg_iter_next(_iter, mailbox_idx_r, msg_r);
-	}
-
-	memset(msg_r, 0, sizeof(*msg_r));
-	msg_r->guid = guid;
-	msg_r->uid = mail->uid;
-	msg_r->flags = mail_get_flags(mail);
-	msg_r->keywords = mail_get_keywords(mail);
-	msg_r->modseq = mail_get_modseq(mail);
-	if (mail_get_save_date(mail, &msg_r->save_date) < 0)
-		msg_r->save_date = (time_t)-1;
-	return 1;
-}
-
-static int
-local_worker_msg_iter_deinit(struct dsync_worker_msg_iter *_iter)
-{
-	struct local_dsync_worker_msg_iter *iter =
-		(struct local_dsync_worker_msg_iter *)_iter;
-	int ret = _iter->failed ? -1 : 0;
-
-	if (iter->box != NULL)
-		iter_local_mailbox_close(iter);
-	array_free(&iter->expunges);
-	str_free(&iter->tmp_guid_str);
-	i_free(iter->mailboxes);
-	i_free(iter);
-	return ret;
-}
-
-static void
-local_worker_copy_mailbox_update(const struct dsync_mailbox *dsync_box,
-				 struct mailbox_update *update_r)
-{
-	memset(update_r, 0, sizeof(*update_r));
-	memcpy(update_r->mailbox_guid, dsync_box->mailbox_guid.guid,
-	       sizeof(update_r->mailbox_guid));
-	update_r->uid_validity = dsync_box->uid_validity;
-	update_r->min_next_uid = dsync_box->uid_next;
-	update_r->min_first_recent_uid = dsync_box->first_recent_uid;
-	update_r->min_highest_modseq = dsync_box->highest_modseq;
-}
-
-static const char *
-mailbox_name_convert(struct local_dsync_worker *worker,
-		     const char *name, char src_sep, char dest_sep)
-{
-	char *dest_name, *p;
-
-	dest_name = t_strdup_noconst(name);
-	for (p = dest_name; *p != '\0'; p++) {
-		if (*p == dest_sep && worker->alt_char != '\0')
-			*p = worker->alt_char;
-		else if (*p == src_sep)
-			*p = dest_sep;
-	}
-	return dest_name;
-}
-
-static const char *
-mailbox_name_cleanup(const char *input, char real_sep, char alt_char)
-{
-	char *output, *p;
-
-	output = t_strdup_noconst(input);
-	for (p = output; *p != '\0'; p++) {
-		if (*p == real_sep || (uint8_t)*input < 32 ||
-		    (uint8_t)*input >= 0x80)
-			*p = alt_char;
-	}
-	return output;
-}
-
-static const char *mailbox_name_force_cleanup(const char *input, char alt_char)
-{
-	char *output, *p;
-
-	output = t_strdup_noconst(input);
-	for (p = output; *p != '\0'; p++) {
-		if (!i_isalnum(*p))
-			*p = alt_char;
-	}
-	return output;
-}
-
-static const char *
-local_worker_convert_mailbox_name(struct local_dsync_worker *worker,
-				  const char *name, struct mail_namespace *ns,
-				  const struct dsync_mailbox *dsync_box,
-				  bool creating)
-{
-	char list_sep, ns_sep = mail_namespace_get_sep(ns);
-
-	if (dsync_box->name_sep != ns_sep) {
-		/* mailbox names use different separators. convert them. */
-		name = mailbox_name_convert(worker, name,
-					    dsync_box->name_sep, ns_sep);
-	}
-	if (creating) {
-		list_sep = mailbox_list_get_hierarchy_sep(ns->list);
-		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
-			/* change any real separators to alt separators,
-			   drop any potentially invalid characters */
-			name = mailbox_name_cleanup(name, list_sep,
-						    worker->alt_char);
-		}
-		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
-			/* still not working, apparently it's not valid mUTF-7.
-			   just drop all non-alphanumeric characters. */
-			name = mailbox_name_force_cleanup(name,
-							  worker->alt_char);
-		}
-		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
-			/* probably some reserved name (e.g. dbox-Mails) */
-			name = t_strconcat("_", name, NULL);
-		}
-		if (!mailbox_list_is_valid_create_name(ns->list, name)) {
-			/* name is too long? just give up and generate a
-			   unique name */
-			guid_128_t guid;
-
-			guid_128_generate(guid);
-			name = guid_128_to_string(guid);
-		}
-		i_assert(mailbox_list_is_valid_create_name(ns->list, name));
-	}
-	return name;
-}
-
-static struct mailbox *
-local_worker_mailbox_alloc(struct local_dsync_worker *worker,
-			   const struct dsync_mailbox *dsync_box, bool creating)
-{
-	struct mail_namespace *ns;
-	struct local_dsync_mailbox *lbox;
-	const char *name;
-
-	lbox = dsync_mailbox_is_noselect(dsync_box) ? NULL :
-		hash_table_lookup(worker->mailbox_hash,
-				  &dsync_box->mailbox_guid);
-	if (lbox != NULL) {
-		/* use the existing known mailbox name */
-		return mailbox_alloc(lbox->ns->list, lbox->name, 0);
-	}
-
-	ns = mail_namespace_find(worker->user->namespaces, dsync_box->name);
-	if (ns == NULL) {
-		i_error("Can't find namespace for mailbox %s", dsync_box->name);
-		return NULL;
-	}
-
-	name = local_worker_convert_mailbox_name(worker, dsync_box->name, ns,
-						 dsync_box, creating);
-	if (!dsync_mailbox_is_noselect(dsync_box)) {
-		local_dsync_worker_add_mailbox(worker, ns, name,
-					       &dsync_box->mailbox_guid);
-	}
-	return mailbox_alloc(ns->list, name, 0);
-}
-
-static int local_worker_create_dir(struct mailbox *box,
-				   const struct dsync_mailbox *dsync_box)
-{
-	struct mailbox_list *list = mailbox_get_namespace(box)->list;
-	const char *errstr;
-	enum mail_error error;
-
-	if (mailbox_list_create_dir(list, mailbox_get_name(box)) == 0)
-		return 0;
-
-	errstr = mailbox_list_get_last_error(list, &error);
-	switch (error) {
-	case MAIL_ERROR_EXISTS:
-		/* directory already exists - that's ok */
-		return 0;
-	case MAIL_ERROR_NOTPOSSIBLE:
-		/* \noselect mailboxes not supported - just ignore them
-		   (we don't want to create a selectable mailbox if the other
-		   side of the sync doesn't support dual-use mailboxes,
-		   e.g. mbox) */
-		return 0;
-	default:
-		i_error("Can't create mailbox %s: %s", dsync_box->name, errstr);
-		return -1;
-	}
-}
-
-static int
-local_worker_create_allocated_mailbox(struct local_dsync_worker *worker,
-				      struct mailbox *box,
-				      const struct dsync_mailbox *dsync_box)
-{
-	struct mailbox_update update;
-	const char *errstr;
-	enum mail_error error;
-
-	local_worker_copy_mailbox_update(dsync_box, &update);
-
-	if (dsync_mailbox_is_noselect(dsync_box)) {
-		if (local_worker_create_dir(box, dsync_box) < 0) {
-			dsync_worker_set_failure(&worker->worker);
-			return -1;
-		}
-		return 1;
-	}
-
-	if (mailbox_create(box, &update, FALSE) < 0) {
-		errstr = mailbox_get_last_error(box, &error);
-		if (error == MAIL_ERROR_EXISTS) {
-			/* mailbox already exists */
-			return 0;
-		}
-
-		dsync_worker_set_failure(&worker->worker);
-		i_error("Can't create mailbox %s: %s", dsync_box->name, errstr);
-		return -1;
-	}
-
-	local_dsync_worker_add_mailbox(worker,
-				       mailbox_get_namespace(box),
-				       mailbox_get_name(box),
-				       &dsync_box->mailbox_guid);
-	return 1;
-}
-
-static void
-local_worker_create_mailbox(struct dsync_worker *_worker,
-			    const struct dsync_mailbox *dsync_box)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mailbox *box;
-	struct mail_namespace *ns;
-	const char *new_name;
-	int ret;
-
-	box = local_worker_mailbox_alloc(worker, dsync_box, TRUE);
-	if (box == NULL) {
-		dsync_worker_set_failure(_worker);
-		return;
-	}
-
-	ret = local_worker_create_allocated_mailbox(worker, box, dsync_box);
-	if (ret != 0) {
-		mailbox_free(&box);
-		return;
-	}
-
-	/* mailbox name already exists. add mailbox guid to the name,
-	   that shouldn't exist. */
-	new_name = t_strconcat(mailbox_get_name(box), "_",
-			       dsync_guid_to_str(&dsync_box->mailbox_guid),
-			       NULL);
-	ns = mailbox_get_namespace(box);
-	mailbox_free(&box);
-
-	local_dsync_worker_add_mailbox(worker, ns, new_name,
-				       &dsync_box->mailbox_guid);
-	box = mailbox_alloc(ns->list, new_name, 0);
-	(void)local_worker_create_allocated_mailbox(worker, box, dsync_box);
-	mailbox_free(&box);
-}
-
-static void
-local_worker_delete_mailbox(struct dsync_worker *_worker,
-			    const struct dsync_mailbox *dsync_box)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct local_dsync_mailbox *lbox;
-	const mailbox_guid_t *mailbox = &dsync_box->mailbox_guid;
-	struct mailbox *box;
-
-	lbox = hash_table_lookup(worker->mailbox_hash, mailbox);
-	if (lbox == NULL) {
-		i_error("Trying to delete a non-listed mailbox with guid=%s",
-			dsync_guid_to_str(mailbox));
-		dsync_worker_set_failure(_worker);
-		return;
-	}
-
-	mailbox_list_set_changelog_timestamp(lbox->ns->list,
-					     dsync_box->last_change);
-	box = mailbox_alloc(lbox->ns->list, lbox->name, 0);
-	if (mailbox_delete(box) < 0) {
-		i_error("Can't delete mailbox %s: %s", lbox->name,
-			mailbox_get_last_error(box, NULL));
-		dsync_worker_set_failure(_worker);
-	} else {
-		lbox->deleted = TRUE;
-	}
-	mailbox_free(&box);
-	mailbox_list_set_changelog_timestamp(lbox->ns->list, (time_t)-1);
-}
-
-static void
-local_worker_delete_dir(struct dsync_worker *_worker,
-			const struct dsync_mailbox *dsync_box)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mail_namespace *ns;
-	const char *storage_name;
-
-	ns = mail_namespace_find(worker->user->namespaces, dsync_box->name);
-	storage_name = mailbox_list_get_storage_name(ns->list, dsync_box->name);
-
-	mailbox_list_set_changelog_timestamp(ns->list, dsync_box->last_change);
-	if (mailbox_list_delete_dir(ns->list, storage_name) < 0) {
-		i_error("Can't delete mailbox directory %s: %s",
-			dsync_box->name,
-			mailbox_list_get_last_error(ns->list, NULL));
-	}
-	mailbox_list_set_changelog_timestamp(ns->list, (time_t)-1);
-}
-
-static void
-local_worker_rename_mailbox(struct dsync_worker *_worker,
-			    const mailbox_guid_t *mailbox,
-			    const struct dsync_mailbox *dsync_box)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct local_dsync_mailbox *lbox;
-	struct mailbox_list *list;
-	struct mailbox *old_box, *new_box;
-	const char *newname;
-
-	lbox = hash_table_lookup(worker->mailbox_hash, mailbox);
-	if (lbox == NULL) {
-		i_error("Trying to rename a non-listed mailbox with guid=%s",
-			dsync_guid_to_str(mailbox));
-		dsync_worker_set_failure(_worker);
-		return;
-	}
-
-	list = lbox->ns->list;
-	newname = local_worker_convert_mailbox_name(worker, dsync_box->name,
-						    lbox->ns, dsync_box, TRUE);
-	if (strcmp(lbox->name, newname) == 0) {
-		/* nothing changed after all. probably because some characters
-		   in mailbox name weren't valid. */
-		return;
-	}
-
-	mailbox_list_set_changelog_timestamp(list, dsync_box->last_change);
-	old_box = mailbox_alloc(list, lbox->name, 0);
-	new_box = mailbox_alloc(list, newname, 0);
-	if (mailbox_rename(old_box, new_box, FALSE) < 0) {
-		i_error("Can't rename mailbox %s to %s: %s", lbox->name,
-			newname, mailbox_get_last_error(old_box, NULL));
-		dsync_worker_set_failure(_worker);
-	} else {
-		lbox->name = p_strdup(worker->pool, newname);
-	}
-	mailbox_free(&old_box);
-	mailbox_free(&new_box);
-	mailbox_list_set_changelog_timestamp(list, (time_t)-1);
-}
-
-static bool
-has_expected_save_uids(struct local_dsync_worker *worker,
-		       const struct mail_transaction_commit_changes *changes)
-{
-	struct seq_range_iter iter;
-	const uint32_t *expected_uids;
-	uint32_t uid;
-	unsigned int i, n, expected_count;
-
-	expected_uids = array_get(&worker->saved_uids, &expected_count);
-	seq_range_array_iter_init(&iter, &changes->saved_uids); i = n = 0;
-	while (seq_range_array_iter_nth(&iter, n++, &uid)) {
-		if (i == expected_count || uid != expected_uids[i++])
-			return FALSE;
-	}
-	return i == expected_count;
-}
-
-static void local_worker_mailbox_close(struct local_dsync_worker *worker)
-{
-	struct mailbox_transaction_context *trans, *ext_trans;
-	struct mail_transaction_commit_changes changes;
-
-	i_assert(worker->save_input == NULL);
-
-	memset(&worker->selected_box_guid, 0,
-	       sizeof(worker->selected_box_guid));
-
-	if (worker->selected_box == NULL)
-		return;
-
-	trans = worker->mail->transaction;
-	ext_trans = worker->ext_mail->transaction;
-	mail_free(&worker->mail);
-	mail_free(&worker->ext_mail);
-
-	/* all saves and copies go to ext_trans */
-	if (mailbox_transaction_commit_get_changes(&ext_trans, &changes) < 0)
-		dsync_worker_set_failure(&worker->worker);
-	else {
-		if (changes.ignored_modseq_changes != 0) {
-			if (worker->worker.verbose) {
-				i_info("%s: Ignored %u modseq changes",
-				       mailbox_get_vname(worker->selected_box),
-				       changes.ignored_modseq_changes);
-			}
-			worker->worker.unexpected_changes = TRUE;
-		}
-		if (!has_expected_save_uids(worker, &changes)) {
-			if (worker->worker.verbose) {
-				i_info("%s: Couldn't keep all uids",
-				       mailbox_get_vname(worker->selected_box));
-			}
-			worker->worker.unexpected_changes = TRUE;
-		}
-		pool_unref(&changes.pool);
-	}
-	array_clear(&worker->saved_uids);
-
-	if (mailbox_transaction_commit(&trans) < 0 ||
-	    mailbox_sync(worker->selected_box, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0)
-		dsync_worker_set_failure(&worker->worker);
-
-	mailbox_free(&worker->selected_box);
-}
-
-static void
-local_worker_update_mailbox(struct dsync_worker *_worker,
-			    const struct dsync_mailbox *dsync_box)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mailbox *box;
-	struct mailbox_update update;
-	bool selected = FALSE;
-
-	/* if we're updating a selected mailbox, close it first so that all
-	   pending changes get committed. */
-	selected = worker->selected_box != NULL &&
-		dsync_guid_equals(&dsync_box->mailbox_guid,
-				  &worker->selected_box_guid);
-	if (selected)
-		local_worker_mailbox_close(worker);
-
-	box = local_worker_mailbox_alloc(worker, dsync_box, FALSE);
-	if (box == NULL) {
-		dsync_worker_set_failure(_worker);
-		return;
-	}
-
-	local_worker_copy_mailbox_update(dsync_box, &update);
-	if (mailbox_update(box, &update) < 0) {
-		dsync_worker_set_failure(_worker);
-		i_error("Can't update mailbox %s: %s", dsync_box->name,
-			mailbox_get_last_error(box, NULL));
-	}
-	mailbox_free(&box);
-
-	if (selected)
-		dsync_worker_select_mailbox(_worker, dsync_box);
-}
-
-static void
-local_worker_set_cache_fields(struct local_dsync_worker *worker,
-			      const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
-{
-	struct mailbox_update update;
-	const struct mailbox_cache_field *fields;
-	struct mailbox_cache_field *new_fields;
-	unsigned int count;
-
-	fields = array_get(cache_fields, &count);
-	new_fields = t_new(struct mailbox_cache_field, count + 1);
-	memcpy(new_fields, fields, sizeof(struct mailbox_cache_field) * count);
-
-	memset(&update, 0, sizeof(update));
-	update.cache_updates = new_fields;
-	mailbox_update(worker->selected_box, &update);
-}
-
-static void
-local_worker_select_mailbox(struct dsync_worker *_worker,
-			    const mailbox_guid_t *mailbox,
-			    const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mailbox_transaction_context *trans, *ext_trans;
-
-	if (dsync_guid_equals(&worker->selected_box_guid, mailbox)) {
-		/* already selected or previous select failed */
-		i_assert(worker->selected_box != NULL || _worker->failed);
-		return;
-	}
-	if (worker->selected_box != NULL)
-		local_worker_mailbox_close(worker);
-	worker->selected_box_guid = *mailbox;
-
-	if (local_mailbox_open(worker, mailbox, &worker->selected_box) <= 0) {
-		dsync_worker_set_failure(_worker);
-		return;
-	}
-	if (cache_fields != NULL && array_is_created(cache_fields))
-		local_worker_set_cache_fields(worker, cache_fields);
-
-	ext_trans = mailbox_transaction_begin(worker->selected_box,
-					MAILBOX_TRANSACTION_FLAG_EXTERNAL |
-					MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
-	trans = mailbox_transaction_begin(worker->selected_box, 0);
-	worker->mail = mail_alloc(trans, 0, NULL);
-	worker->ext_mail = mail_alloc(ext_trans, 0, NULL);
-}
-
-static void
-local_worker_msg_update_metadata(struct dsync_worker *_worker,
-				 const struct dsync_message *msg)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mail_keywords *keywords;
-
-	if (msg->modseq > 1) {
-		(void)mailbox_enable(worker->mail->box,
-				     MAILBOX_FEATURE_CONDSTORE);
-	}
-
-	if (!mail_set_uid(worker->mail, msg->uid))
-		dsync_worker_set_failure(_worker);
-	else {
-		mail_update_flags(worker->mail, MODIFY_REPLACE, msg->flags);
-
-		keywords = mailbox_keywords_create_valid(worker->mail->box,
-							 msg->keywords);
-		mail_update_keywords(worker->mail, MODIFY_REPLACE, keywords);
-		mailbox_keywords_unref(&keywords);
-		mail_update_modseq(worker->mail, msg->modseq);
-	}
-}
-
-static void
-local_worker_msg_update_uid(struct dsync_worker *_worker,
-			    uint32_t old_uid, uint32_t new_uid)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mail_save_context *save_ctx;
-
-	if (!mail_set_uid(worker->ext_mail, old_uid)) {
-		dsync_worker_set_failure(_worker);
-		return;
-	}
-
-	save_ctx = mailbox_save_alloc(worker->ext_mail->transaction);
-	mailbox_save_copy_flags(save_ctx, worker->ext_mail);
-	mailbox_save_set_uid(save_ctx, new_uid);
-	if (mailbox_copy(&save_ctx, worker->ext_mail) == 0)
-		mail_expunge(worker->ext_mail);
-}
-
-static void local_worker_msg_expunge(struct dsync_worker *_worker, uint32_t uid)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-
-	if (mail_set_uid(worker->mail, uid))
-		mail_expunge(worker->mail);
-}
-
-static void
-local_worker_msg_save_set_metadata(struct local_dsync_worker *worker,
-				   struct mailbox *box,
-				   struct mail_save_context *save_ctx,
-				   const struct dsync_message *msg)
-{
-	struct mail_keywords *keywords;
-
-	i_assert(msg->uid != 0);
-
-	if (msg->modseq > 1)
-		(void)mailbox_enable(box, MAILBOX_FEATURE_CONDSTORE);
-
-	keywords = str_array_length(msg->keywords) == 0 ? NULL :
-		mailbox_keywords_create_valid(box, msg->keywords);
-	mailbox_save_set_flags(save_ctx, msg->flags, keywords);
-	if (keywords != NULL)
-		mailbox_keywords_unref(&keywords);
-	mailbox_save_set_uid(save_ctx, msg->uid);
-	mailbox_save_set_save_date(save_ctx, msg->save_date);
-	mailbox_save_set_min_modseq(save_ctx, msg->modseq);
-
-	array_append(&worker->saved_uids, &msg->uid, 1);
-}
-
-static void
-local_worker_msg_copy(struct dsync_worker *_worker,
-		      const mailbox_guid_t *src_mailbox, uint32_t src_uid,
-		      const struct dsync_message *dest_msg,
-		      dsync_worker_copy_callback_t *callback, void *context)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mailbox *src_box;
-	struct mailbox_transaction_context *src_trans;
-	struct mail *src_mail;
-	struct mail_save_context *save_ctx;
-	int ret;
-
-	if (local_mailbox_open(worker, src_mailbox, &src_box) <= 0) {
-		callback(FALSE, context);
-		return;
-	}
-
-	src_trans = mailbox_transaction_begin(src_box, 0);
-	src_mail = mail_alloc(src_trans, 0, NULL);
-	if (!mail_set_uid(src_mail, src_uid))
-		ret = -1;
-	else {
-		save_ctx = mailbox_save_alloc(worker->ext_mail->transaction);
-		local_worker_msg_save_set_metadata(worker, worker->mail->box,
-						   save_ctx, dest_msg);
-		ret = mailbox_copy(&save_ctx, src_mail);
-	}
-
-	mail_free(&src_mail);
-	(void)mailbox_transaction_commit(&src_trans);
-	mailbox_free(&src_box);
-
-	callback(ret == 0, context);
-}
-
-static void dsync_worker_try_finish(struct local_dsync_worker *worker)
-{
-	if (worker->finish_callback == NULL)
-		return;
-	if (worker->save_io != NULL || worker->reading_mail)
-		return;
-
-	i_assert(worker->finishing);
-	i_assert(!worker->finished);
-	worker->finishing = FALSE;
-	worker->finished = TRUE;
-	worker->finish_callback(!worker->worker.failed, worker->finish_context);
-}
-
-static void
-local_worker_save_msg_continue(struct local_dsync_worker *worker)
-{
-	struct mailbox *dest_box = worker->ext_mail->box;
-	dsync_worker_save_callback_t *callback;
-	ssize_t ret;
-
-	while ((ret = i_stream_read(worker->save_input)) > 0) {
-		if (mailbox_save_continue(worker->save_ctx) < 0)
-			break;
-	}
-	if (ret == 0) {
-		if (worker->save_io != NULL)
-			return;
-		worker->save_io =
-			io_add(i_stream_get_fd(worker->save_input), IO_READ,
-			       local_worker_save_msg_continue, worker);
-		return;
-	}
-	i_assert(ret == -1);
-
-	/* drop save_io before destroying save_input, so that save_input's
-	   destroy callback can add io back to its fd. */
-	if (worker->save_io != NULL)
-		io_remove(&worker->save_io);
-	if (worker->save_input->stream_errno != 0) {
-		errno = worker->save_input->stream_errno;
-		i_error("read(msg input) failed: %m");
-		mailbox_save_cancel(&worker->save_ctx);
-		dsync_worker_set_failure(&worker->worker);
-	} else {
-		i_assert(worker->save_input->eof);
-		if (mailbox_save_finish(&worker->save_ctx) < 0) {
-			i_error("Can't save message to mailbox %s: %s",
-				mailbox_get_vname(dest_box),
-				mailbox_get_last_error(dest_box, NULL));
-			dsync_worker_set_failure(&worker->worker);
-		}
-	}
-	callback = worker->save_callback;
-	worker->save_callback = NULL;
-	i_stream_unref(&worker->save_input);
-
-	dsync_worker_try_finish(worker);
-	/* call the callback last, since it could call worker code again and
-	   cause problems (e.g. if _try_finish() is called again, it could
-	   cause a duplicate finish_callback()) */
-	callback(worker->save_context);
-}
-
-static void
-local_worker_msg_save(struct dsync_worker *_worker,
-		      const struct dsync_message *msg,
-		      const struct dsync_msg_static_data *data,
-		      dsync_worker_save_callback_t *callback,
-		      void *context)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct mailbox *dest_box = worker->ext_mail->box;
-	struct mail_save_context *save_ctx;
-
-	i_assert(worker->save_input == NULL);
-
-	save_ctx = mailbox_save_alloc(worker->ext_mail->transaction);
-	if (*msg->guid != '\0')
-		mailbox_save_set_guid(save_ctx, msg->guid);
-	local_worker_msg_save_set_metadata(worker, worker->mail->box,
-					   save_ctx, msg);
-	if (*data->pop3_uidl != '\0')
-		mailbox_save_set_pop3_uidl(save_ctx, data->pop3_uidl);
-
-	mailbox_save_set_received_date(save_ctx, data->received_date, 0);
-
-	if (mailbox_save_begin(&save_ctx, data->input) < 0) {
-		i_error("Can't save message to mailbox %s: %s",
-			mailbox_get_vname(dest_box),
-			mailbox_get_last_error(dest_box, NULL));
-		dsync_worker_set_failure(_worker);
-		callback(context);
-		return;
-	}
-
-	worker->save_callback = callback;
-	worker->save_context = context;
-	worker->save_input = data->input;
-	worker->save_ctx = save_ctx;
-	i_stream_ref(worker->save_input);
-	local_worker_save_msg_continue(worker);
-}
-
-static void local_worker_msg_save_cancel(struct dsync_worker *_worker)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-
-	if (worker->save_input == NULL)
-		return;
-
-	if (worker->save_io != NULL)
-		io_remove(&worker->save_io);
-	mailbox_save_cancel(&worker->save_ctx);
-	i_stream_unref(&worker->save_input);
-}
-
-static void local_worker_msg_get_done(struct local_dsync_worker *worker)
-{
-	const struct local_dsync_worker_msg_get *gets;
-	struct local_dsync_worker_msg_get get;
-	unsigned int count;
-
-	worker->reading_mail = FALSE;
-
-	gets = array_get(&worker->msg_get_queue, &count);
-	if (count == 0)
-		dsync_worker_try_finish(worker);
-	else {
-		get = gets[0];
-		array_delete(&worker->msg_get_queue, 0, 1);
-		local_worker_msg_get_next(worker, &get);
-	}
-}
-
-static void local_worker_msg_box_close(struct local_dsync_worker *worker)
-{
-	struct mailbox_transaction_context *trans;
-	struct mailbox *box;
-
-	if (worker->get_mail == NULL)
-		return;
-
-	box = worker->get_mail->box;
-	trans = worker->get_mail->transaction;
-
-	mail_free(&worker->get_mail);
-	(void)mailbox_transaction_commit(&trans);
-	mailbox_free(&box);
-	memset(&worker->get_mailbox, 0, sizeof(worker->get_mailbox));
-}
-
-static void
-local_worker_msg_get_next(struct local_dsync_worker *worker,
-			  const struct local_dsync_worker_msg_get *get)
-{
-	struct dsync_msg_static_data data;
-	struct mailbox_transaction_context *trans;
-	struct mailbox *box;
-
-	i_assert(!worker->reading_mail);
-
-	if (!dsync_guid_equals(&worker->get_mailbox, &get->mailbox)) {
-		local_worker_msg_box_close(worker);
-		if (local_mailbox_open(worker, &get->mailbox, &box) <= 0) {
-			get->callback(DSYNC_MSG_GET_RESULT_FAILED,
-				      NULL, get->context);
-			return;
-		}
-		worker->get_mailbox = get->mailbox;
-
-		trans = mailbox_transaction_begin(box, 0);
-		worker->get_mail = mail_alloc(trans, MAIL_FETCH_UIDL_BACKEND |
-					      MAIL_FETCH_RECEIVED_DATE |
-					      MAIL_FETCH_STREAM_HEADER |
-					      MAIL_FETCH_STREAM_BODY, NULL);
-	}
-
-	if (!mail_set_uid(worker->get_mail, get->uid)) {
-		get->callback(DSYNC_MSG_GET_RESULT_EXPUNGED,
-			      NULL, get->context);
-		return;
-	}
-
-	memset(&data, 0, sizeof(data));
-	if (mail_get_special(worker->get_mail, MAIL_FETCH_UIDL_BACKEND,
-			     &data.pop3_uidl) < 0)
-		data.pop3_uidl = "";
-	else
-		data.pop3_uidl = t_strdup(data.pop3_uidl);
-	if (mail_get_received_date(worker->get_mail, &data.received_date) < 0 ||
-	    mail_get_stream(worker->get_mail, NULL, NULL, &data.input) < 0) {
-		get->callback(worker->get_mail->expunged ?
-			      DSYNC_MSG_GET_RESULT_EXPUNGED :
-			      DSYNC_MSG_GET_RESULT_FAILED, NULL, get->context);
-	} else {
-		worker->reading_mail = TRUE;
-		data.input = i_stream_create_limit(data.input, (uoff_t)-1);
-		i_stream_set_destroy_callback(data.input,
-					      local_worker_msg_get_done,
-					      worker);
-		get->callback(DSYNC_MSG_GET_RESULT_SUCCESS,
-			      &data, get->context);
-	}
-}
-
-static void
-local_worker_msg_get(struct dsync_worker *_worker,
-		     const mailbox_guid_t *mailbox, uint32_t uid,
-		     dsync_worker_msg_callback_t *callback, void *context)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-	struct local_dsync_worker_msg_get get;
-
-	memset(&get, 0, sizeof(get));
-	get.mailbox = *mailbox;
-	get.uid = uid;
-	get.callback = callback;
-	get.context = context;
-
-	if (!worker->reading_mail)
-		local_worker_msg_get_next(worker, &get);
-	else
-		array_append(&worker->msg_get_queue, &get, 1);
-}
-
-static void
-local_worker_finish(struct dsync_worker *_worker,
-		    dsync_worker_finish_callback_t *callback, void *context)
-{
-	struct local_dsync_worker *worker =
-		(struct local_dsync_worker *)_worker;
-
-	i_assert(!worker->finishing);
-
-	worker->finishing = TRUE;
-	worker->finished = FALSE;
-	worker->finish_callback = callback;
-	worker->finish_context = context;
-
-	dsync_worker_try_finish(worker);
-}
-
-struct dsync_worker_vfuncs local_dsync_worker = {
-	local_worker_deinit,
-
-	local_worker_is_output_full,
-	local_worker_output_flush,
-
-	local_worker_mailbox_iter_init,
-	local_worker_mailbox_iter_next,
-	local_worker_mailbox_iter_deinit,
-
-	local_worker_subs_iter_init,
-	local_worker_subs_iter_next,
-	local_worker_subs_iter_next_un,
-	local_worker_subs_iter_deinit,
-	local_worker_set_subscribed,
-
-	local_worker_msg_iter_init,
-	local_worker_msg_iter_next,
-	local_worker_msg_iter_deinit,
-
-	local_worker_create_mailbox,
-	local_worker_delete_mailbox,
-	local_worker_delete_dir,
-	local_worker_rename_mailbox,
-	local_worker_update_mailbox,
-
-	local_worker_select_mailbox,
-	local_worker_msg_update_metadata,
-	local_worker_msg_update_uid,
-	local_worker_msg_expunge,
-	local_worker_msg_copy,
-	local_worker_msg_save,
-	local_worker_msg_save_cancel,
-	local_worker_msg_get,
-	local_worker_finish
-};
--- a/src/dsync/dsync-worker-private.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-#ifndef DSYNC_WORKER_PRIVATE_H
-#define DSYNC_WORKER_PRIVATE_H
-
-#include "dsync-worker.h"
-
-struct mail_user;
-
-struct dsync_worker_vfuncs {
-	void (*deinit)(struct dsync_worker *);
-
-	bool (*is_output_full)(struct dsync_worker *worker);
-	int (*output_flush)(struct dsync_worker *worker);
-
-	struct dsync_worker_mailbox_iter *
-		(*mailbox_iter_init)(struct dsync_worker *worker);
-	int (*mailbox_iter_next)(struct dsync_worker_mailbox_iter *iter,
-				 struct dsync_mailbox *dsync_box_r);
-	int (*mailbox_iter_deinit)(struct dsync_worker_mailbox_iter *iter);
-
-	struct dsync_worker_subs_iter *
-		(*subs_iter_init)(struct dsync_worker *worker);
-	int (*subs_iter_next)(struct dsync_worker_subs_iter *iter,
-			      struct dsync_worker_subscription *rec_r);
-	int (*subs_iter_next_un)(struct dsync_worker_subs_iter *iter,
-				 struct dsync_worker_unsubscription *rec_r);
-	int (*subs_iter_deinit)(struct dsync_worker_subs_iter *iter);
-	void (*set_subscribed)(struct dsync_worker *worker,
-			       const char *name, time_t last_change, bool set);
-
-	struct dsync_worker_msg_iter *
-		(*msg_iter_init)(struct dsync_worker *worker,
-				 const mailbox_guid_t mailboxes[],
-				 unsigned int mailbox_count);
-	int (*msg_iter_next)(struct dsync_worker_msg_iter *iter,
-			     unsigned int *mailbox_idx_r,
-			     struct dsync_message *msg_r);
-	int (*msg_iter_deinit)(struct dsync_worker_msg_iter *iter);
-
-	void (*create_mailbox)(struct dsync_worker *worker,
-			       const struct dsync_mailbox *dsync_box);
-	void (*delete_mailbox)(struct dsync_worker *worker,
-			       const struct dsync_mailbox *dsync_box);
-	void (*delete_dir)(struct dsync_worker *worker,
-			   const struct dsync_mailbox *dsync_box);
-	void (*rename_mailbox)(struct dsync_worker *worker,
-			       const mailbox_guid_t *mailbox,
-			       const struct dsync_mailbox *dsync_box);
-	void (*update_mailbox)(struct dsync_worker *worker,
-			       const struct dsync_mailbox *dsync_box);
-
-	void (*select_mailbox)(struct dsync_worker *worker,
-			       const mailbox_guid_t *mailbox,
-			       const ARRAY_TYPE(mailbox_cache_field) *cache_fields);
-	void (*msg_update_metadata)(struct dsync_worker *worker,
-				    const struct dsync_message *msg);
-	void (*msg_update_uid)(struct dsync_worker *worker,
-			       uint32_t old_uid, uint32_t new_uid);
-	void (*msg_expunge)(struct dsync_worker *worker, uint32_t uid);
-	void (*msg_copy)(struct dsync_worker *worker,
-			 const mailbox_guid_t *src_mailbox, uint32_t src_uid,
-			 const struct dsync_message *dest_msg,
-			 dsync_worker_copy_callback_t *callback, void *context);
-	void (*msg_save)(struct dsync_worker *worker,
-			 const struct dsync_message *msg,
-			 const struct dsync_msg_static_data *data,
-			 dsync_worker_save_callback_t *callback,
-			 void *context);
-	void (*msg_save_cancel)(struct dsync_worker *worker);
-	void (*msg_get)(struct dsync_worker *worker,
-			const mailbox_guid_t *mailbox, uint32_t uid,
-			dsync_worker_msg_callback_t *callback, void *context);
-	void (*finish)(struct dsync_worker *worker,
-		       dsync_worker_finish_callback_t *callback, void *context);
-};
-
-struct dsync_worker {
-	struct dsync_worker_vfuncs v;
-
-	io_callback_t *input_callback, *output_callback;
-	void *input_context, *output_context;
-
-	unsigned int readonly:1;
-	unsigned int failed:1;
-	unsigned int verbose:1;
-	unsigned int unexpected_changes:1;
-};
-
-struct dsync_worker_mailbox_iter {
-	struct dsync_worker *worker;
-	bool failed;
-};
-
-struct dsync_worker_subs_iter {
-	struct dsync_worker *worker;
-	bool failed;
-};
-
-struct dsync_worker_msg_iter {
-	struct dsync_worker *worker;
-	bool failed;
-};
-
-void dsync_worker_set_failure(struct dsync_worker *worker);
-
-#endif
--- a/src/dsync/dsync-worker.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "istream.h"
-#include "dsync-worker-private.h"
-
-void dsync_worker_deinit(struct dsync_worker **_worker)
-{
-	struct dsync_worker *worker = *_worker;
-
-	*_worker = NULL;
-	worker->v.deinit(worker);
-}
-
-void dsync_worker_set_readonly(struct dsync_worker *worker)
-{
-	worker->readonly = TRUE;
-}
-
-void dsync_worker_set_verbose(struct dsync_worker *worker)
-{
-	worker->verbose = TRUE;
-}
-
-void dsync_worker_set_input_callback(struct dsync_worker *worker,
-				     io_callback_t *callback, void *context)
-{
-	worker->input_callback = callback;
-	worker->input_context = context;
-}
-
-bool dsync_worker_is_output_full(struct dsync_worker *worker)
-{
-	return worker->v.is_output_full(worker);
-}
-
-void dsync_worker_set_output_callback(struct dsync_worker *worker,
-				      io_callback_t *callback, void *context)
-{
-	worker->output_callback = callback;
-	worker->output_context = context;
-}
-
-int dsync_worker_output_flush(struct dsync_worker *worker)
-{
-	return worker->v.output_flush(worker);
-}
-
-struct dsync_worker_mailbox_iter *
-dsync_worker_mailbox_iter_init(struct dsync_worker *worker)
-{
-	return worker->v.mailbox_iter_init(worker);
-}
-
-int dsync_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *iter,
-				   struct dsync_mailbox *dsync_box_r)
-{
-	int ret;
-
-	T_BEGIN {
-		ret = iter->worker->v.mailbox_iter_next(iter, dsync_box_r);
-	} T_END;
-	return ret;
-}
-
-int dsync_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter **_iter)
-{
-	struct dsync_worker_mailbox_iter *iter = *_iter;
-
-	*_iter = NULL;
-	return iter->worker->v.mailbox_iter_deinit(iter);
-}
-
-struct dsync_worker_subs_iter *
-dsync_worker_subs_iter_init(struct dsync_worker *worker)
-{
-	return worker->v.subs_iter_init(worker);
-}
-
-int dsync_worker_subs_iter_next(struct dsync_worker_subs_iter *iter,
-				struct dsync_worker_subscription *rec_r)
-{
-	return iter->worker->v.subs_iter_next(iter, rec_r);
-}
-
-int dsync_worker_subs_iter_next_un(struct dsync_worker_subs_iter *iter,
-				   struct dsync_worker_unsubscription *rec_r)
-{
-	return iter->worker->v.subs_iter_next_un(iter, rec_r);
-}
-
-int dsync_worker_subs_iter_deinit(struct dsync_worker_subs_iter **_iter)
-{
-	struct dsync_worker_subs_iter *iter = *_iter;
-
-	*_iter = NULL;
-	return iter->worker->v.subs_iter_deinit(iter);
-}
-
-void dsync_worker_set_subscribed(struct dsync_worker *worker,
-				 const char *name, time_t last_change, bool set)
-{
-	worker->v.set_subscribed(worker, name, last_change, set);
-}
-
-struct dsync_worker_msg_iter *
-dsync_worker_msg_iter_init(struct dsync_worker *worker,
-			   const mailbox_guid_t mailboxes[],
-			   unsigned int mailbox_count)
-{
-	i_assert(mailbox_count > 0);
-	return worker->v.msg_iter_init(worker, mailboxes, mailbox_count);
-}
-
-int dsync_worker_msg_iter_next(struct dsync_worker_msg_iter *iter,
-			       unsigned int *mailbox_idx_r,
-			       struct dsync_message *msg_r)
-{
-	int ret;
-
-	T_BEGIN {
-		ret = iter->worker->v.msg_iter_next(iter, mailbox_idx_r, msg_r);
-	} T_END;
-	return ret;
-}
-
-int dsync_worker_msg_iter_deinit(struct dsync_worker_msg_iter **_iter)
-{
-	struct dsync_worker_msg_iter *iter = *_iter;
-
-	*_iter = NULL;
-	return iter->worker->v.msg_iter_deinit(iter);
-}
-
-void dsync_worker_create_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *dsync_box)
-{
-	i_assert(dsync_box->uid_validity != 0 ||
-		 dsync_mailbox_is_noselect(dsync_box));
-
-	if (!worker->readonly) T_BEGIN {
-		worker->v.create_mailbox(worker, dsync_box);
-	} T_END;
-}
-
-void dsync_worker_delete_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *dsync_box)
-{
-	if (!worker->readonly) T_BEGIN {
-		worker->v.delete_mailbox(worker, dsync_box);
-	} T_END;
-}
-
-void dsync_worker_delete_dir(struct dsync_worker *worker,
-			     const struct dsync_mailbox *dsync_box)
-{
-	if (!worker->readonly) T_BEGIN {
-		worker->v.delete_dir(worker, dsync_box);
-	} T_END;
-}
-
-void dsync_worker_rename_mailbox(struct dsync_worker *worker,
-				 const mailbox_guid_t *mailbox,
-				 const struct dsync_mailbox *dsync_box)
-{
-	if (!worker->readonly) T_BEGIN {
-		worker->v.rename_mailbox(worker, mailbox, dsync_box);
-	} T_END;
-}
-
-void dsync_worker_update_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *dsync_box)
-{
-	if (!worker->readonly) T_BEGIN {
-		worker->v.update_mailbox(worker, dsync_box);
-	} T_END;
-}
-
-void dsync_worker_select_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *box)
-{
-	T_BEGIN {
-		worker->v.select_mailbox(worker, &box->mailbox_guid,
-					 &box->cache_fields);
-	} T_END;
-}
-
-void dsync_worker_msg_update_metadata(struct dsync_worker *worker,
-				      const struct dsync_message *msg)
-{
-	if (!worker->failed && !worker->readonly)
-		worker->v.msg_update_metadata(worker, msg);
-}
-
-void dsync_worker_msg_update_uid(struct dsync_worker *worker,
-				 uint32_t old_uid, uint32_t new_uid)
-{
-	if (!worker->failed && !worker->readonly)
-		worker->v.msg_update_uid(worker, old_uid, new_uid);
-}
-
-void dsync_worker_msg_expunge(struct dsync_worker *worker, uint32_t uid)
-{
-	if (!worker->failed && !worker->readonly)
-		worker->v.msg_expunge(worker, uid);
-}
-
-void dsync_worker_msg_copy(struct dsync_worker *worker,
-			   const mailbox_guid_t *src_mailbox, uint32_t src_uid,
-			   const struct dsync_message *dest_msg,
-			   dsync_worker_copy_callback_t *callback,
-			   void *context)
-{
-	if (!worker->failed && !worker->readonly) {
-		T_BEGIN {
-			worker->v.msg_copy(worker, src_mailbox, src_uid,
-					   dest_msg, callback, context);
-		} T_END;
-	} else {
-		callback(FALSE, context);
-	}
-}
-
-void dsync_worker_msg_save(struct dsync_worker *worker,
-			   const struct dsync_message *msg,
-			   const struct dsync_msg_static_data *data,
-			   dsync_worker_save_callback_t *callback,
-			   void *context)
-{
-	if (!worker->readonly) {
-		if (worker->failed)
-			callback(context);
-		else T_BEGIN {
-			worker->v.msg_save(worker, msg, data,
-					   callback, context);
-		} T_END;
-	} else {
-		const unsigned char *d;
-		size_t size;
-
-		while ((i_stream_read_data(data->input, &d, &size, 0)) > 0)
-			i_stream_skip(data->input, size);
-		callback(context);
-	}
-}
-
-void dsync_worker_msg_save_cancel(struct dsync_worker *worker)
-{
-	worker->v.msg_save_cancel(worker);
-}
-
-void dsync_worker_msg_get(struct dsync_worker *worker,
-			  const mailbox_guid_t *mailbox, uint32_t uid,
-			  dsync_worker_msg_callback_t *callback, void *context)
-{
-	i_assert(uid != 0);
-
-	if (worker->failed)
-		callback(DSYNC_MSG_GET_RESULT_FAILED, NULL, context);
-	else T_BEGIN {
-		worker->v.msg_get(worker, mailbox, uid, callback, context);
-	} T_END;
-}
-
-void dsync_worker_finish(struct dsync_worker *worker,
-			 dsync_worker_finish_callback_t *callback,
-			 void *context)
-{
-	worker->v.finish(worker, callback, context);
-}
-
-void dsync_worker_set_failure(struct dsync_worker *worker)
-{
-	worker->failed = TRUE;
-}
-
-bool dsync_worker_has_failed(struct dsync_worker *worker)
-{
-	return worker->failed;
-}
-
-bool dsync_worker_has_unexpected_changes(struct dsync_worker *worker)
-{
-	return worker->unexpected_changes;
-}
--- a/src/dsync/dsync-worker.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-#ifndef DSYNC_WORKER_H
-#define DSYNC_WORKER_H
-
-#include "ioloop.h"
-#include "dsync-data.h"
-
-enum dsync_msg_get_result {
-	DSYNC_MSG_GET_RESULT_SUCCESS,
-	DSYNC_MSG_GET_RESULT_EXPUNGED,
-	DSYNC_MSG_GET_RESULT_FAILED
-};
-
-struct dsync_worker_subscription {
-	const char *vname, *storage_name, *ns_prefix;
-	time_t last_change;
-};
-struct dsync_worker_unsubscription {
-	/* SHA1 sum of the mailbox's storage name, i.e. without namespace
-	   prefix */
-	mailbox_guid_t name_sha1;
-	const char *ns_prefix;
-	time_t last_change;
-};
-
-typedef void dsync_worker_copy_callback_t(bool success, void *context);
-typedef void dsync_worker_save_callback_t(void *context);
-typedef void dsync_worker_msg_callback_t(enum dsync_msg_get_result result,
-					 const struct dsync_msg_static_data *data,
-					 void *context);
-typedef void dsync_worker_finish_callback_t(bool success, void *context);
-
-struct dsync_worker *
-dsync_worker_init_local(struct mail_user *user, char alt_char);
-struct dsync_worker *dsync_worker_init_proxy_client(int fd_in, int fd_out);
-void dsync_worker_deinit(struct dsync_worker **worker);
-
-/* Set this worker as read-only. All attempted changes are ignored. */
-void dsync_worker_set_readonly(struct dsync_worker *worker);
-/* Log verbosely */
-void dsync_worker_set_verbose(struct dsync_worker *worker);
-
-/* If any function returns with "waiting for more data", the given callback
-   gets called when more data is available. */
-void dsync_worker_set_input_callback(struct dsync_worker *worker,
-				     io_callback_t *callback, void *context);
-
-/* Returns TRUE if command queue is full and caller should stop sending
-   more commands. */
-bool dsync_worker_is_output_full(struct dsync_worker *worker);
-/* The given callback gets called when more commands can be queued. */
-void dsync_worker_set_output_callback(struct dsync_worker *worker,
-				      io_callback_t *callback, void *context);
-/* Try to flush command queue. Returns 1 if all flushed, 0 if something is
-   still in queue, -1 if failed. */
-int dsync_worker_output_flush(struct dsync_worker *worker);
-
-/* Iterate though all mailboxes */
-struct dsync_worker_mailbox_iter *
-dsync_worker_mailbox_iter_init(struct dsync_worker *worker);
-/* Get the next available mailbox. Returns 1 if ok, 0 if waiting for more data,
-   -1 if there are no more mailboxes. */
-int dsync_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *iter,
-				   struct dsync_mailbox *dsync_box_r);
-/* Finish mailbox iteration. Returns 0 if ok, -1 if iteration failed. */
-int dsync_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter **iter);
-
-/* Iterate though all subscriptions */
-struct dsync_worker_subs_iter *
-dsync_worker_subs_iter_init(struct dsync_worker *worker);
-/* Get the next subscription. Returns 1 if ok, 0 if waiting for more data,
-   -1 if there are no more subscriptions. */
-int dsync_worker_subs_iter_next(struct dsync_worker_subs_iter *iter,
-				struct dsync_worker_subscription *rec_r);
-/* Like _iter_next(), but list known recent unsubscriptions. */
-int dsync_worker_subs_iter_next_un(struct dsync_worker_subs_iter *iter,
-				   struct dsync_worker_unsubscription *rec_r);
-/* Finish subscription iteration. Returns 0 if ok, -1 if iteration failed. */
-int dsync_worker_subs_iter_deinit(struct dsync_worker_subs_iter **iter);
-/* Subscribe/unsubscribe mailbox */
-void dsync_worker_set_subscribed(struct dsync_worker *worker,
-				 const char *name, time_t last_change,
-				 bool set);
-
-/* Iterate through all messages in given mailboxes. The mailboxes are iterated
-   in the given order. */
-struct dsync_worker_msg_iter *
-dsync_worker_msg_iter_init(struct dsync_worker *worker,
-			   const mailbox_guid_t mailboxes[],
-			   unsigned int mailbox_count);
-/* Get the next available message. Also returns all expunged messages from
-   the end of mailbox (if next_uid-1 message exists, nothing is returned).
-   mailbox_idx_r contains the mailbox's index in mailboxes[] array given
-   to _iter_init(). Returns 1 if ok, 0 if waiting for more data, -1 if there
-   are no more messages. */
-int dsync_worker_msg_iter_next(struct dsync_worker_msg_iter *iter,
-			       unsigned int *mailbox_idx_r,
-			       struct dsync_message *msg_r);
-/* Finish message iteration. Returns 0 if ok, -1 if iteration failed. */
-int dsync_worker_msg_iter_deinit(struct dsync_worker_msg_iter **iter);
-
-/* Create mailbox with given name, GUID and UIDVALIDITY. */
-void dsync_worker_create_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *dsync_box);
-/* Delete mailbox/dir with given GUID. */
-void dsync_worker_delete_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *dsync_box);
-/* Delete mailbox's directory. Fail if it would also delete mailbox. */
-void dsync_worker_delete_dir(struct dsync_worker *worker,
-			     const struct dsync_mailbox *dsync_box);
-/* Change a mailbox and its childrens' name. The name is taken from the given
-   dsync_box (applying name_sep if necessary). */
-void dsync_worker_rename_mailbox(struct dsync_worker *worker,
-				 const mailbox_guid_t *mailbox,
-				 const struct dsync_mailbox *dsync_box);
-/* Find mailbox with given GUID and make sure its uid_next and
-   highest_modseq are up to date (but don't shrink them). */
-void dsync_worker_update_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *dsync_box);
-
-/* The following message syncing functions access the this selected mailbox. */
-void dsync_worker_select_mailbox(struct dsync_worker *worker,
-				 const struct dsync_mailbox *box);
-/* Update message's metadata (flags, keywords, modseq). */
-void dsync_worker_msg_update_metadata(struct dsync_worker *worker,
-				      const struct dsync_message *msg);
-/* Change message's UID. */
-void dsync_worker_msg_update_uid(struct dsync_worker *worker,
-				 uint32_t old_uid, uint32_t new_uid);
-/* Expunge given message. */
-void dsync_worker_msg_expunge(struct dsync_worker *worker, uint32_t uid);
-/* Copy given message. */
-void dsync_worker_msg_copy(struct dsync_worker *worker,
-			   const mailbox_guid_t *src_mailbox, uint32_t src_uid,
-			   const struct dsync_message *dest_msg,
-			   dsync_worker_copy_callback_t *callback,
-			   void *context);
-/* Save given message from the given input stream. Once saving is finished,
-   the given callback is called and the stream is destroyed. */
-void dsync_worker_msg_save(struct dsync_worker *worker,
-			   const struct dsync_message *msg,
-			   const struct dsync_msg_static_data *data,
-			   dsync_worker_save_callback_t *callback,
-			   void *context);
-/* Cancel any pending saves */
-void dsync_worker_msg_save_cancel(struct dsync_worker *worker);
-/* Get message data for saving. The callback is called once when the static
-   data has been received. The whole message may not have been downloaded yet,
-   so the caller must read the input stream until it returns EOF and then
-   unreference it. */
-void dsync_worker_msg_get(struct dsync_worker *worker,
-			  const mailbox_guid_t *mailbox, uint32_t uid,
-			  dsync_worker_msg_callback_t *callback, void *context);
-/* Call the callback once all the pending commands are finished. */
-void dsync_worker_finish(struct dsync_worker *worker,
-			 dsync_worker_finish_callback_t *callback,
-			 void *context);
-
-/* Returns TRUE if some commands have failed. */
-bool dsync_worker_has_failed(struct dsync_worker *worker);
-/* Returns TRUE if some UID or modseq changes didn't get assigned as
-   requested. */
-bool dsync_worker_has_unexpected_changes(struct dsync_worker *worker);
-
-#endif
--- a/src/dsync/dsync.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,364 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "lib-signals.h"
-#include "array.h"
-#include "execv-const.h"
-#include "settings-parser.h"
-#include "master-service.h"
-#include "master-service-settings.h"
-#include "mail-storage-service.h"
-#include "mail-user.h"
-#include "mail-namespace.h"
-#include "dsync-brain.h"
-#include "dsync-worker.h"
-#include "dsync-proxy-server.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <ctype.h>
-
-static const char *ssh_cmd = "ssh";
-static struct dsync_brain *brain;
-static struct dsync_proxy_server *server;
-
-static void run_cmd(const char *const *args, int *fd_in_r, int *fd_out_r)
-{
-	int fd_in[2], fd_out[2];
-
-	if (pipe(fd_in) < 0 || pipe(fd_out) < 0)
-		i_fatal("pipe() failed: %m");
-
-	switch (fork()) {
-	case -1:
-		i_fatal("fork() failed: %m");
-		break;
-	case 0:
-		/* child, which will execute the proxy server. stdin/stdout
-		   goes to pipes which we'll pass to proxy client. */
-		if (dup2(fd_in[0], STDIN_FILENO) < 0 ||
-		    dup2(fd_out[1], STDOUT_FILENO) < 0)
-			i_fatal("dup2() failed: %m");
-
-		(void)close(fd_in[0]);
-		(void)close(fd_in[1]);
-		(void)close(fd_out[0]);
-		(void)close(fd_out[1]);
-
-		execvp_const(args[0], args);
-		break;
-	default:
-		/* parent */
-		(void)close(fd_in[0]);
-		(void)close(fd_out[1]);
-		*fd_in_r = fd_out[0];
-		*fd_out_r = fd_in[1];
-		break;
-	}
-}
-
-static void
-mirror_get_remote_cmd_line(char **argv, const char *const **cmd_args_r)
-{
-	ARRAY_TYPE(const_string) cmd_args;
-	unsigned int i;
-	const char *p;
-
-	t_array_init(&cmd_args, 16);
-	for (i = 0; argv[i] != NULL; i++) {
-		p = argv[i];
-		array_append(&cmd_args, &p, 1);
-	}
-
-	p = "server"; array_append(&cmd_args, &p, 1);
-	(void)array_append_space(&cmd_args);
-	*cmd_args_r = array_idx(&cmd_args, 0);
-}
-
-static bool mirror_get_remote_cmd(char **argv, const char *const **cmd_args_r)
-{
-	ARRAY_TYPE(const_string) cmd_args;
-	const char *p, *user, *host;
-
-	if (argv[1] != NULL) {
-		/* more than one parameter, so it contains a full command
-		   (e.g. ssh host dsync) */
-		mirror_get_remote_cmd_line(argv, cmd_args_r);
-		return TRUE;
-	}
-
-	/* if it begins with /[a-z0-9]+:/, it's a mail location
-	   (e.g. mdbox:~/mail) */
-	for (p = argv[0]; *p != '\0'; p++) {
-		if (!i_isalnum(*p)) {
-			if (*p == ':')
-				return FALSE;
-			break;
-		}
-	}
-
-	if (strchr(argv[0], ' ') != NULL || strchr(argv[0], '/') != NULL) {
-		/* a) the whole command is in one string. this is mainly for
-		      backwards compatibility.
-		   b) script/path */
-		argv = p_strsplit(pool_datastack_create(), argv[0], " ");
-		mirror_get_remote_cmd_line(argv, cmd_args_r);
-		return TRUE;
-	}
-
-	/* [user@]host */
-	host = strchr(argv[0], '@');
-	if (host != NULL)
-		user = t_strdup_until(argv[0], host++);
-	else {
-		user = "";
-		host = argv[0];
-	}
-
-	/* we'll assume virtual users, so in user@host it really means not to
-	   give ssh a username, but to give dsync -u user parameter. */
-	t_array_init(&cmd_args, 8);
-	array_append(&cmd_args, &ssh_cmd, 1);
-	array_append(&cmd_args, &host, 1);
-	p = "dsync"; array_append(&cmd_args, &p, 1);
-	if (*user != '\0') {
-		p = "-u"; array_append(&cmd_args, &p, 1);
-		array_append(&cmd_args, &user, 1);
-	}
-	p = "server"; array_append(&cmd_args, &p, 1);
-	(void)array_append_space(&cmd_args);
-	*cmd_args_r = array_idx(&cmd_args, 0);
-	return TRUE;
-}
-
-static void ATTR_NORETURN
-usage(void)
-{
-	fprintf(stderr,
-"usage: dsync [-C <alt char>] [-m <mailbox>] [-u <user>] [-frRv]\n"
-"  mirror <local mail_location> | [<user>@]<host> | <remote dsync command>\n"
-);
-	exit(1);
-}
-
-static void
-dsync_connected(struct master_service_connection *conn ATTR_UNUSED)
-{
-	i_fatal("Running as service not supported currently");
-}
-
-int main(int argc, char *argv[])
-{
-	enum mail_storage_service_flags ssflags =
-		MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR |
-		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
-	enum dsync_brain_flags brain_flags = 0;
-	struct mail_storage_service_ctx *storage_service;
-	struct mail_storage_service_user *service_user;
-	struct mail_storage_service_input input;
-	struct mail_user *mail_user, *mail_user2 = NULL;
-	struct dsync_worker *worker1, *worker2, *workertmp;
-	const char *error, *username, *cmd_name, *mailbox = NULL;
-	const char *local_location = NULL, *const *remote_cmd_args = NULL;
-	const char *path1, *path2;
-	bool dsync_server = FALSE, unexpected_changes = FALSE;
-	bool dsync_debug = FALSE, reverse_workers = FALSE;
-	char alt_char = '_';
-	int c, ret, fd_in = STDIN_FILENO, fd_out = STDOUT_FILENO;
-
-	master_service = master_service_init("dsync",
-					     MASTER_SERVICE_FLAG_STANDALONE,
-					     &argc, &argv, "+C:Dfm:Ru:v");
-
-	username = getenv("USER");
-	while ((c = master_getopt(master_service)) > 0) {
-		if (c == '-')
-			break;
-		switch (c) {
-		case 'C':
-			alt_char = optarg[0];
-			break;
-		case 'D':
-			dsync_debug = TRUE;
-			brain_flags |= DSYNC_BRAIN_FLAG_VERBOSE;
-			ssflags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
-			break;
-		case 'm':
-			mailbox = optarg;
-			break;
-		case 'R':
-			reverse_workers = TRUE;
-			break;
-		case 'f':
-			brain_flags |= DSYNC_BRAIN_FLAG_FULL_SYNC;
-			break;
-		case 'u':
-			username = optarg;
-			ssflags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
-			ssflags &= ~MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR;
-			break;
-		case 'v':
-			brain_flags |= DSYNC_BRAIN_FLAG_VERBOSE;
-			break;
-		default:
-			usage();
-		}
-	}
-	if (optind == argc)
-		usage();
-	if (username == NULL)
-		i_fatal("USER environment not set and -u parameter not given");
-	cmd_name = argv[optind++];
-
-	if (strcmp(cmd_name, "mirror") == 0 ||
-	    strcmp(cmd_name, "convert") == 0 ||
-	    strcmp(cmd_name, "backup") == 0) {
-		if (optind == argc)
-			usage();
-
-		if (strcmp(cmd_name, "backup") == 0)
-			brain_flags |= DSYNC_BRAIN_FLAG_BACKUP;
-		if (!mirror_get_remote_cmd(argv+optind, &remote_cmd_args)) {
-			if (optind+1 != argc)
-				usage();
-			local_location = argv[optind];
-		}
-		optind++;
-	} else if (strcmp(cmd_name, "server") == 0) {
-		dsync_server = TRUE;
-	} else {
-		usage();
-	}
-	master_service_init_finish(master_service);
-	lib_signals_ignore(SIGHUP, TRUE);
-
-	if (!dsync_debug) {
-		/* disable debugging unless -D is given */
-		i_set_debug_file("/dev/null");
-	}
-
-	memset(&input, 0, sizeof(input));
-	input.module = "mail";
-	input.service = "dsync";
-	input.username = username;
-
-	storage_service = mail_storage_service_init(master_service, NULL,
-						    ssflags);
-	if (mail_storage_service_lookup(storage_service, &input,
-					&service_user, &error) <= 0)
-		i_fatal("User lookup failed: %s", error);
-
-	if (remote_cmd_args != NULL) {
-		/* _service_lookup() may exec doveconf, so do our forking
-		   after that. but do it before _service_next() in case it
-		   drops process privileges */
-		run_cmd(remote_cmd_args, &fd_in, &fd_out);
-	}
-
-	if (mail_storage_service_next(storage_service, service_user,
-				      &mail_user) < 0)
-		i_fatal("User init failed");
-	mail_user->admin = TRUE;
-
-	/* create the first local worker */
-	worker1 = dsync_worker_init_local(mail_user, alt_char);
-	if (local_location != NULL) {
-		/* update mail_location and create another user for the
-		   second location. */
-		struct setting_parser_context *set_parser;
-		const char *set_line =
-			t_strconcat("mail_location=", local_location, NULL);
-
-		set_parser = mail_storage_service_user_get_settings_parser(service_user);
-		if (settings_parse_line(set_parser, set_line) < 0)
-			i_unreached();
-		if (mail_storage_service_next(storage_service, service_user,
-					      &mail_user2) < 0)
-			i_fatal("User init failed");
-		mail_user2->admin = TRUE;
-
-		if (mail_namespaces_get_root_sep(mail_user->namespaces) !=
-		    mail_namespaces_get_root_sep(mail_user2->namespaces)) {
-			i_fatal("Mail locations must use the same "
-				"virtual mailbox hierarchy separator "
-				"(specify separator for the default namespace)");
-		}
-		path1 = mailbox_list_get_path(mail_user->namespaces->list, NULL,
-					      MAILBOX_LIST_PATH_TYPE_MAILBOX);
-		path2 = mailbox_list_get_path(mail_user2->namespaces->list, NULL,
-					      MAILBOX_LIST_PATH_TYPE_MAILBOX);
-		if (path1 != NULL && path2 != NULL &&
-		    strcmp(path1, path2) == 0) {
-			i_fatal("Both source and destination mail_location "
-				"points to same directory: %s", path1);
-		}
-
-		worker2 = dsync_worker_init_local(mail_user2, alt_char);
-		if (reverse_workers) {
-			workertmp = worker1;
-			worker1 = worker2;
-			worker2 = workertmp;
-		}
-
-		i_set_failure_prefix(t_strdup_printf("dsync(%s): ", username));
-		brain = dsync_brain_init(worker1, worker2, mailbox,
-					 brain_flags | DSYNC_BRAIN_FLAG_LOCAL);
-		server = NULL;
-		dsync_brain_sync_all(brain);
-	} else if (dsync_server) {
-		i_set_failure_prefix(t_strdup_printf("dsync-remote(%s): ",
-						     username));
-		server = dsync_proxy_server_init(fd_in, fd_out, worker1);
-		worker2 = NULL;
-
-		master_service_run(master_service, dsync_connected);
-	} else {
-		i_assert(remote_cmd_args != NULL);
-		i_set_failure_prefix(t_strdup_printf("dsync-local(%s): ",
-						     username));
-
-		worker2 = dsync_worker_init_proxy_client(fd_in, fd_out);
-		if (reverse_workers) {
-			workertmp = worker1;
-			worker1 = worker2;
-			worker2 = workertmp;
-		}
-
-		brain = dsync_brain_init(worker1, worker2,
-					 mailbox, brain_flags);
-		server = NULL;
-		dsync_brain_sync(brain);
-
-		if (!dsync_brain_has_failed(brain))
-			master_service_run(master_service, dsync_connected);
-	}
-
-	if (brain == NULL)
-		ret = 0;
-	else {
-		if (dsync_brain_has_unexpected_changes(brain))
-			unexpected_changes = TRUE;
-		ret = dsync_brain_deinit(&brain);
-	}
-	if (server != NULL)
-		dsync_proxy_server_deinit(&server);
-
-	dsync_worker_deinit(&worker1);
-	if (worker2 != NULL)
-		dsync_worker_deinit(&worker2);
-
-	mail_user_unref(&mail_user);
-	if (mail_user2 != NULL)
-		mail_user_unref(&mail_user2);
-	mail_storage_service_user_free(&service_user);
-
-	if (unexpected_changes) {
-		i_warning("Mailbox changes caused a desync. "
-			  "You may want to run dsync again.");
-	}
-
-	mail_storage_service_deinit(&storage_service);
-	master_service_deinit(&master_service);
-	return ret < 0 ? 1 : (unexpected_changes ? 2 : 0);
-}
--- a/src/dsync/test-dsync-brain-msgs.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,670 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "crc32.h"
-#include "dsync-brain-private.h"
-#include "test-dsync-worker.h"
-#include "test-dsync-common.h"
-
-enum test_box_add_type {
-	ADD_SRC,
-	ADD_DEST,
-	ADD_BOTH
-};
-
-struct test_dsync_mailbox {
-	struct dsync_brain_mailbox box;
-	ARRAY_DEFINE(src_msgs, struct dsync_message);
-	ARRAY_DEFINE(dest_msgs, struct dsync_message);
-};
-ARRAY_DEFINE_TYPE(test_dsync_mailbox, struct test_dsync_mailbox);
-
-static ARRAY_TYPE(test_dsync_mailbox) mailboxes;
-static struct test_dsync_worker *test_src_worker, *test_dest_worker;
-
-void dsync_brain_fail(struct dsync_brain *brain ATTR_UNUSED) {}
-void dsync_brain_msg_sync_new_msgs(struct dsync_brain_mailbox_sync *sync ATTR_UNUSED) {}
-
-static struct test_dsync_mailbox *test_box_find(const char *name)
-{
-	struct test_dsync_mailbox *boxes;
-	unsigned int i, count;
-
-	boxes = array_get_modifiable(&mailboxes, &count);
-	for (i = 0; i < count; i++) {
-		if (strcmp(boxes[i].box.box.name, name) == 0)
-			return &boxes[i];
-	}
-	return NULL;
-}
-
-static bool
-test_box_has_guid(const char *name, const mailbox_guid_t *guid)
-{
-	const struct test_dsync_mailbox *box;
-
-	box = test_box_find(name);
-	return box != NULL &&
-		memcmp(box->box.box.mailbox_guid.guid, guid->guid,
-		       sizeof(box->box.box.mailbox_guid.guid)) == 0;
-}
-
-static struct test_dsync_mailbox *
-test_box_add(enum test_box_add_type type, const char *name)
-{
-	struct test_dsync_mailbox *tbox;
-	struct dsync_mailbox *box;
-
-	tbox = test_box_find(name);
-	if (tbox == NULL) {
-		tbox = array_append_space(&mailboxes);
-		i_array_init(&tbox->src_msgs, 16);
-		i_array_init(&tbox->dest_msgs, 16);
-	}
-
-	box = i_new(struct dsync_mailbox, 1);
-	box->name = i_strdup(name);
-
-	dsync_str_sha_to_guid(t_strconcat("box-", name, NULL),
-			      &box->mailbox_guid);
-	dsync_str_sha_to_guid(name, &box->name_sha1);
-
-	box->uid_validity = crc32_str(name);
-	box->highest_modseq = 1;
-
-	switch (type) {
-	case ADD_SRC:
-		tbox->box.src = box;
-		break;
-	case ADD_DEST:
-		tbox->box.dest = box;
-		break;
-	case ADD_BOTH:
-		tbox->box.src = box;
-		tbox->box.dest = box;
-		break;
-	}
-	tbox->box.box.name = box->name;
-	tbox->box.box.mailbox_guid = box->mailbox_guid;
-	tbox->box.box.name_sha1 = box->name_sha1;
-	tbox->box.box.uid_validity = box->uid_validity;
-	return tbox;
-}
-
-static void test_msg_add(struct test_dsync_mailbox *box,
-			 enum test_box_add_type type,
-			 const char *guid, uint32_t uid)
-{
-	static int msg_date = 0;
-	struct dsync_message msg;
-
-	memset(&msg, 0, sizeof(msg));
-	msg.guid = i_strdup(guid);
-	msg.uid = uid;
-	msg.modseq = ++box->box.box.highest_modseq;
-	msg.save_date = ++msg_date;
-
-	switch (type) {
-	case ADD_SRC:
-		box->box.src->highest_modseq++;
-		box->box.src->uid_next = uid + 1;
-		array_append(&box->src_msgs, &msg, 1);
-		break;
-	case ADD_DEST:
-		box->box.dest->highest_modseq++;
-		box->box.dest->uid_next = uid + 1;
-		array_append(&box->dest_msgs, &msg, 1);
-		break;
-	case ADD_BOTH:
-		box->box.src->highest_modseq++;
-		box->box.dest->highest_modseq++;
-		box->box.src->uid_next = uid + 1;
-		box->box.dest->uid_next = uid + 1;
-		array_append(&box->src_msgs, &msg, 1);
-		array_append(&box->dest_msgs, &msg, 1);
-		break;
-	}
-	if (box->box.box.uid_next <= uid)
-		box->box.box.uid_next = uid + 1;
-}
-
-static void test_msg_set_modseq(struct test_dsync_mailbox *box,
-				enum test_box_add_type type,
-				uint32_t uid, uint64_t modseq)
-{
-	struct dsync_message *msgs;
-	unsigned int i, count;
-
-	i_assert(modseq <= box->box.box.highest_modseq);
-	if (type != ADD_DEST) {
-		msgs = array_get_modifiable(&box->src_msgs, &count);
-		for (i = 0; i < count; i++) {
-			if (msgs[i].uid == uid) {
-				msgs[i].modseq = modseq;
-				break;
-			}
-		}
-		i_assert(i < count);
-	}
-	if (type != ADD_SRC) {
-		msgs = array_get_modifiable(&box->dest_msgs, &count);
-		for (i = 0; i < count; i++) {
-			if (msgs[i].uid == uid) {
-				msgs[i].modseq = modseq;
-				break;
-			}
-		}
-		i_assert(i < count);
-	}
-}
-
-static void test_msg_set_flags(struct test_dsync_mailbox *box,
-			       enum test_box_add_type type,
-			       uint32_t uid, enum mail_flags flags)
-{
-	unsigned char guid_128_data[GUID_128_SIZE * 2 + 1];
-	struct dsync_message *msgs;
-	unsigned int i, count;
-
-	box->box.box.highest_modseq++;
-	if (type != ADD_DEST) {
-		box->box.src->highest_modseq = box->box.box.highest_modseq;
-		msgs = array_get_modifiable(&box->src_msgs, &count);
-		for (i = 0; i < count; i++) {
-			if (msgs[i].uid == uid) {
-				if ((flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0) {
-					msgs[i].guid = i_strdup(dsync_get_guid_128_str(msgs[i].guid,
-						guid_128_data, sizeof(guid_128_data)));
-				}
-				msgs[i].flags = flags;
-				msgs[i].modseq = box->box.src->highest_modseq;
-				break;
-			}
-		}
-		i_assert(i < count);
-	}
-	if (type != ADD_SRC) {
-		box->box.dest->highest_modseq = box->box.box.highest_modseq;
-		msgs = array_get_modifiable(&box->dest_msgs, &count);
-		for (i = 0; i < count; i++) {
-			if (msgs[i].uid == uid) {
-				if ((flags & DSYNC_MAIL_FLAG_EXPUNGED) != 0) {
-					msgs[i].guid = i_strdup(dsync_get_guid_128_str(msgs[i].guid,
-						guid_128_data, sizeof(guid_128_data)));
-				}
-				msgs[i].flags = flags;
-				msgs[i].modseq = box->box.dest->highest_modseq;
-				break;
-			}
-		}
-		i_assert(i < count);
-	}
-}
-
-static void ATTR_SENTINEL
-test_msg_set_keywords(struct test_dsync_mailbox *box,
-		      enum test_box_add_type type,
-		      uint32_t uid, const char *kw, ...)
-{
-	struct dsync_message *msgs;
-	unsigned int i, count;
-	va_list va;
-	ARRAY_TYPE(const_string) keywords;
-
-	t_array_init(&keywords, 8);
-	array_append(&keywords, &kw, 1);
-	va_start(va, kw);
-	while ((kw = va_arg(va, const char *)) != NULL)
-		array_append(&keywords, &kw, 1);
-	va_end(va);
-	(void)array_append_space(&keywords);
-
-	box->box.box.highest_modseq++;
-	if (type != ADD_DEST) {
-		box->box.src->highest_modseq = box->box.box.highest_modseq;
-		msgs = array_get_modifiable(&box->src_msgs, &count);
-		for (i = 0; i < count; i++) {
-			if (msgs[i].uid == uid) {
-				msgs[i].keywords = array_idx(&keywords, 0);
-				msgs[i].modseq = box->box.src->highest_modseq;
-				break;
-			}
-		}
-		i_assert(i < count);
-	}
-	if (type != ADD_SRC) {
-		box->box.dest->highest_modseq = box->box.box.highest_modseq;
-		msgs = array_get_modifiable(&box->dest_msgs, &count);
-		for (i = 0; i < count; i++) {
-			if (msgs[i].uid == uid) {
-				msgs[i].keywords = array_idx(&keywords, 0);
-				msgs[i].modseq = box->box.src->highest_modseq;
-				break;
-			}
-		}
-		i_assert(i < count);
-	}
-}
-
-static void
-test_dsync_sync_msgs(struct test_dsync_worker *worker, bool dest)
-{
-	const struct test_dsync_mailbox *boxes;
-	const struct dsync_message *msgs;
-	struct test_dsync_worker_msg test_msg;
-	unsigned int i, j, box_count, msg_count;
-
-	boxes = array_get(&mailboxes, &box_count);
-	for (i = 0; i < box_count; i++) {
-		msgs = dest ? array_get(&boxes[i].dest_msgs, &msg_count) :
-			array_get(&boxes[i].src_msgs, &msg_count);
-		for (j = 0; j < msg_count; j++) {
-			test_msg.msg = msgs[j];
-			test_msg.mailbox_idx = i;
-			array_append(&worker->msg_iter.msgs, &test_msg, 1);
-			worker->worker.input_callback(worker->worker.input_context);
-		}
-	}
-
-	worker->msg_iter.last = TRUE;
-	worker->worker.input_callback(worker->worker.input_context);
-}
-
-static struct dsync_brain *test_dsync_brain_init(void)
-{
-	struct dsync_brain *brain;
-
-	brain = i_new(struct dsync_brain, 1);
-	brain->src_worker = dsync_worker_init_test();
-	brain->dest_worker = dsync_worker_init_test();
-
-	test_src_worker = (struct test_dsync_worker *)brain->src_worker;
-	test_dest_worker = (struct test_dsync_worker *)brain->dest_worker;
-	return brain;
-}
-
-static struct dsync_brain_mailbox_sync *
-test_dsync_brain_sync_init(void)
-{
-	ARRAY_TYPE(dsync_brain_mailbox) brain_boxes;
-	struct dsync_brain_mailbox_sync *sync;
-	const struct test_dsync_mailbox *tboxes;
-	unsigned int i, count;
-
-	tboxes = array_get(&mailboxes, &count);
-	t_array_init(&brain_boxes, count);
-	for (i = 0; i < count; i++)
-		array_append(&brain_boxes, &tboxes[i].box, 1);
-
-	sync = dsync_brain_msg_sync_init(test_dsync_brain_init(), &brain_boxes);
-	dsync_brain_msg_sync_more(sync);
-	test_dsync_sync_msgs(test_dest_worker, TRUE);
-	test_dsync_sync_msgs(test_src_worker, FALSE);
-	return sync;
-}
-
-static void test_dsync_brain_msg_sync_box_multi(void)
-{
-	struct test_dsync_mailbox *box;
-	struct dsync_brain_mailbox_sync *sync;
-	struct test_dsync_msg_event msg_event;
-	const struct dsync_brain_new_msg *new_msgs;
-	unsigned int count;
-
-	/* test that msg syncing finds and syncs all mailboxes */
-	test_begin("dsync brain msg sync box multi");
-
-	i_array_init(&mailboxes, 32);
-	box = test_box_add(ADD_BOTH, "both");
-	test_msg_add(box, ADD_BOTH, "guid1", 1);
-	test_msg_set_flags(box, ADD_SRC, 1, MAIL_SEEN);
-	test_msg_set_flags(box, ADD_DEST, 1, MAIL_DRAFT);
-	test_msg_set_flags(box, ADD_SRC, 1, MAIL_ANSWERED);
-	box = test_box_add(ADD_SRC, "src");
-	test_msg_add(box, ADD_SRC, "guid2", 5);
-	box = test_box_add(ADD_DEST, "dest");
-	test_msg_add(box, ADD_DEST, "guid3", 3);
-
-	sync = test_dsync_brain_sync_init();
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(test_box_has_guid("both", &msg_event.mailbox));
-	test_assert(msg_event.msg.uid == 1);
-	test_assert(msg_event.msg.flags == MAIL_ANSWERED);
-	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-
-	new_msgs = array_get(&sync->dest_msg_iter->new_msgs, &count);
-	test_assert(count == 1);
-	test_assert(new_msgs[0].mailbox_idx == 1);
-	test_assert(new_msgs[0].msg->uid == 5);
-	test_assert(strcmp(new_msgs[0].msg->guid, "guid2") == 0);
-
-	new_msgs = array_get(&sync->src_msg_iter->new_msgs, &count);
-	test_assert(count == 1);
-	test_assert(new_msgs[0].mailbox_idx == 2);
-	test_assert(new_msgs[0].msg->uid == 3);
-	test_assert(strcmp(new_msgs[0].msg->guid, "guid3") == 0);
-
-	test_end();
-}
-
-static void test_dsync_brain_msg_sync_box(enum test_box_add_type type)
-{
-	struct test_dsync_mailbox *box;
-	struct dsync_brain_mailbox_sync *sync;
-	struct test_dsync_msg_event msg_event;
-	const struct dsync_brain_new_msg *new_msgs;
-	unsigned int count;
-
-	i_array_init(&mailboxes, 32);
-	box = test_box_add(type, "box1");
-	test_msg_add(box, type, "guid1", 1);
-	box = test_box_add(type, "box2");
-	test_msg_add(box, type, "guid2", 2);
-
-	sync = test_dsync_brain_sync_init();
-
-	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-
-	new_msgs = array_get(type == ADD_DEST ? &sync->src_msg_iter->new_msgs :
-			     &sync->dest_msg_iter->new_msgs, &count);
-	test_assert(count == 2);
-	test_assert(new_msgs[0].mailbox_idx == 0);
-	test_assert(new_msgs[0].msg->uid == 1);
-	test_assert(strcmp(new_msgs[0].msg->guid, "guid1") == 0);
-	test_assert(new_msgs[1].mailbox_idx == 1);
-	test_assert(new_msgs[1].msg->uid == 2);
-	test_assert(strcmp(new_msgs[1].msg->guid, "guid2") == 0);
-}
-
-static void test_dsync_brain_msg_sync_box_single(void)
-{
-	test_begin("dsync brain msg sync box src");
-	test_dsync_brain_msg_sync_box(ADD_SRC);
-	test_end();
-
-	test_begin("dsync brain msg sync box dest");
-	test_dsync_brain_msg_sync_box(ADD_DEST);
-	test_end();
-}
-
-static void test_dsync_brain_msg_sync_existing(void)
-{
-	struct test_dsync_mailbox *box;
-	struct dsync_brain_mailbox_sync *sync;
-	struct test_dsync_msg_event msg_event;
-
-	test_begin("dsync brain msg sync existing");
-
-	i_array_init(&mailboxes, 1);
-	box = test_box_add(ADD_BOTH, "box");
-	test_msg_add(box, ADD_BOTH, "guid1", 1);
-	test_msg_add(box, ADD_BOTH, "guid2", 2);
-	test_msg_add(box, ADD_BOTH, "guid3", 3);
-	test_msg_add(box, ADD_BOTH, "guid5", 5);
-	test_msg_add(box, ADD_BOTH, "guid6", 6);
-	test_msg_add(box, ADD_BOTH, "guid9", 9);
-	test_msg_add(box, ADD_BOTH, "guid10", 10);
-	test_msg_add(box, ADD_BOTH, "guid11", 11);
-	test_msg_add(box, ADD_BOTH, "guid12", 12);
-
-	/* unchanged */
-	test_msg_set_flags(box, ADD_BOTH, 1, MAIL_SEEN);
-
-	/* changed, same modseq - dest has more flags so it will be used */
-	test_msg_set_flags(box, ADD_SRC, 2, MAIL_ANSWERED);
-	test_msg_set_flags(box, ADD_DEST, 2, MAIL_ANSWERED | MAIL_SEEN);
-	test_msg_set_modseq(box, ADD_BOTH, 2, 2);
-
-	/* changed, same modseq - src has more flags so it will be used */
-	test_msg_set_flags(box, ADD_SRC, 3, MAIL_ANSWERED | MAIL_SEEN);
-	test_msg_set_flags(box, ADD_DEST, 3, MAIL_ANSWERED);
-	test_msg_set_modseq(box, ADD_BOTH, 3, 3);
-
-	/* changed, dest has higher modseq */
-	test_msg_set_flags(box, ADD_BOTH, 5, MAIL_DRAFT);
-	test_msg_set_flags(box, ADD_DEST, 5, MAIL_FLAGGED);
-
-	/* changed, src has higher modseq */
-	test_msg_set_flags(box, ADD_DEST, 6, MAIL_FLAGGED);
-	test_msg_set_flags(box, ADD_SRC, 6, 0);
-
-	/* keywords changed, src has higher modseq */
-	test_msg_set_keywords(box, ADD_SRC, 9, "hello", "world", NULL);
-
-	/* flag/keyword conflict, same modseq - src has more so it
-	   will be used */
-	test_msg_set_keywords(box, ADD_SRC, 10, "foo", NULL);
-	test_msg_set_flags(box, ADD_SRC, 10, MAIL_SEEN);
-	test_msg_set_flags(box, ADD_DEST, 10, MAIL_DRAFT);
-	test_msg_set_modseq(box, ADD_BOTH, 10, 5);
-
-	/* flag/keyword conflict, same modseq - dest has more so it
-	   will be used */
-	test_msg_set_keywords(box, ADD_DEST, 11, "foo", NULL);
-	test_msg_set_flags(box, ADD_SRC, 11, MAIL_SEEN);
-	test_msg_set_flags(box, ADD_DEST, 11, MAIL_DRAFT);
-	test_msg_set_modseq(box, ADD_BOTH, 11, 5);
-
-	/* flag/keyword conflict, same modseq - both have same number of
-	   flags so src will be used */
-	test_msg_set_keywords(box, ADD_SRC, 12, "bar", NULL);
-	test_msg_set_keywords(box, ADD_DEST, 12, "foo", NULL);
-	test_msg_set_flags(box, ADD_SRC, 12, MAIL_SEEN);
-	test_msg_set_flags(box, ADD_DEST, 12, MAIL_DRAFT);
-	test_msg_set_modseq(box, ADD_BOTH, 12, 5);
-
-	sync = test_dsync_brain_sync_init();
-	test_assert(array_count(&sync->src_msg_iter->new_msgs) == 0);
-	test_assert(array_count(&sync->dest_msg_iter->new_msgs) == 0);
-
-	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 2);
-	test_assert(msg_event.msg.flags == (MAIL_ANSWERED | MAIL_SEEN));
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 3);
-	test_assert(msg_event.msg.flags == (MAIL_ANSWERED | MAIL_SEEN));
-
-	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 5);
-	test_assert(msg_event.msg.flags == MAIL_FLAGGED);
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 6);
-	test_assert(msg_event.msg.flags == 0);
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 9);
-	test_assert(msg_event.msg.flags == 0);
-	test_assert(strcmp(msg_event.msg.keywords[0], "hello") == 0);
-	test_assert(strcmp(msg_event.msg.keywords[1], "world") == 0);
-	test_assert(msg_event.msg.keywords[2] == NULL);
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 10);
-	test_assert(msg_event.msg.flags == MAIL_SEEN);
-	test_assert(strcmp(msg_event.msg.keywords[0], "foo") == 0);
-	test_assert(msg_event.msg.keywords[1] == NULL);
-
-	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 11);
-	test_assert(msg_event.msg.flags == MAIL_DRAFT);
-	test_assert(strcmp(msg_event.msg.keywords[0], "foo") == 0);
-	test_assert(msg_event.msg.keywords[1] == NULL);
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(msg_event.msg.uid == 12);
-	test_assert(msg_event.msg.flags == MAIL_SEEN);
-	test_assert(strcmp(msg_event.msg.keywords[0], "bar") == 0);
-	test_assert(msg_event.msg.keywords[1] == NULL);
-
-	test_assert(!test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_end();
-}
-
-static void test_dsync_brain_msg_sync_expunges(void)
-{
-	struct test_dsync_mailbox *box;
-	struct dsync_brain_mailbox_sync *sync;
-	struct test_dsync_msg_event msg_event;
-
-	test_begin("dsync brain msg sync expunges");
-
-	i_array_init(&mailboxes, 1);
-	box = test_box_add(ADD_BOTH, "box");
-
-	/* expunged from dest */
-	test_msg_add(box, ADD_SRC, "guid1", 1);
-	/* expunged from src */
-	test_msg_add(box, ADD_DEST, "guid2", 2);
-	/* expunged from dest with expunge record */
-	test_msg_add(box, ADD_BOTH, "guid3", 3);
-	test_msg_set_flags(box, ADD_DEST, 3, DSYNC_MAIL_FLAG_EXPUNGED);
-	/* expunged from src with expunge record */
-	test_msg_add(box, ADD_BOTH, "guid4", 4);
-	test_msg_set_flags(box, ADD_SRC, 4, DSYNC_MAIL_FLAG_EXPUNGED);
-	/* expunged from both, with expunge record in src */
-	test_msg_add(box, ADD_SRC, "guid5", 5);
-	test_msg_set_flags(box, ADD_SRC, 5, DSYNC_MAIL_FLAG_EXPUNGED);
-	/* expunged from both, with expunge record in dest */
-	test_msg_add(box, ADD_DEST, "guid6", 6);
-	test_msg_set_flags(box, ADD_DEST, 6, DSYNC_MAIL_FLAG_EXPUNGED);
-	/* expunged from both, with expunge record in both */
-	test_msg_add(box, ADD_BOTH, "guid7", 7);
-	test_msg_set_flags(box, ADD_BOTH, 7, DSYNC_MAIL_FLAG_EXPUNGED);
-
-	sync = test_dsync_brain_sync_init();
-	test_assert(array_count(&sync->src_msg_iter->new_msgs) == 0);
-	test_assert(array_count(&sync->dest_msg_iter->new_msgs) == 0);
-
-	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
-	test_assert(msg_event.msg.uid == 1);
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
-	test_assert(msg_event.msg.uid == 2);
-
-	test_assert(test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
-	test_assert(msg_event.msg.uid == 3);
-
-	test_assert(test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_EXPUNGE);
-	test_assert(msg_event.msg.uid == 4);
-
-	test_assert(!test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-	test_end();
-}
-
-static void test_dsync_brain_msg_sync_uid_conflicts(void)
-{
-	struct test_dsync_mailbox *box;
-	struct dsync_brain_mailbox_sync *sync;
-	struct test_dsync_msg_event msg_event;
-	const struct dsync_brain_uid_conflict *conflicts;
-	const struct dsync_brain_new_msg *src_msgs, *dest_msgs;
-	unsigned int src_count, dest_count;
-
-	test_begin("dsync brain msg sync uid conflicts");
-
-	i_array_init(&mailboxes, 16);
-
-	/* existing guid mismatch */
-	box = test_box_add(ADD_BOTH, "box1");
-	test_msg_add(box, ADD_SRC, "guid1", 1);
-	test_msg_add(box, ADD_DEST, "guid2", 1);
-
-	/* preserve uid */
-	test_msg_add(box, ADD_BOTH, "guid3", 3);
-	/* extra message in src */
-	test_msg_add(box, ADD_SRC, "guid4", 4);
-	/* extra message in dest */
-	test_msg_add(box, ADD_DEST, "guid5", 5);
-
-	/* conflict in expunged message expunged in dest */
-	test_msg_add(box, ADD_SRC, "guid6", 6);
-	test_msg_add(box, ADD_DEST, "guid7", 6);
-	test_msg_set_flags(box, ADD_DEST, 6, DSYNC_MAIL_FLAG_EXPUNGED);
-
-	/* conflict in expunged message expunged in src */
-	test_msg_add(box, ADD_SRC, "guid8", 8);
-	test_msg_set_flags(box, ADD_SRC, 8, DSYNC_MAIL_FLAG_EXPUNGED);
-	test_msg_add(box, ADD_DEST, "guid9", 8);
-
-	/* conflict in expunged message expunged in both */
-	test_msg_add(box, ADD_SRC, "guid10", 10);
-	test_msg_set_flags(box, ADD_SRC, 10, DSYNC_MAIL_FLAG_EXPUNGED);
-	test_msg_add(box, ADD_DEST, "guid11", 10);
-	test_msg_set_flags(box, ADD_DEST, 10, DSYNC_MAIL_FLAG_EXPUNGED);
-
-	sync = test_dsync_brain_sync_init();
-
-	conflicts = array_get(&sync->src_msg_iter->uid_conflicts, &src_count);
-	test_assert(src_count == 3);
-	test_assert(conflicts[0].old_uid == 1);
-	test_assert(conflicts[0].new_uid == 12);
-	test_assert(conflicts[1].old_uid == 4);
-	test_assert(conflicts[1].new_uid == 13);
-	test_assert(conflicts[2].old_uid == 6);
-	test_assert(conflicts[2].new_uid == 15);
-
-	conflicts = array_get(&sync->dest_msg_iter->uid_conflicts, &dest_count);
-	test_assert(dest_count == 3);
-	test_assert(conflicts[0].old_uid == 1);
-	test_assert(conflicts[0].new_uid == 11);
-	test_assert(conflicts[1].old_uid == 5);
-	test_assert(conflicts[1].new_uid == 14);
-	test_assert(conflicts[2].old_uid == 8);
-	test_assert(conflicts[2].new_uid == 16);
-
-	test_assert(!test_dsync_worker_next_msg_event(test_src_worker, &msg_event));
-	test_assert(!test_dsync_worker_next_msg_event(test_dest_worker, &msg_event));
-
-	src_msgs = array_get(&sync->src_msg_iter->new_msgs, &src_count);
-	dest_msgs = array_get(&sync->dest_msg_iter->new_msgs, &dest_count);
-	test_assert(src_count == 3);
-	test_assert(dest_count == 3);
-
-	test_assert(dest_msgs[0].msg->uid == 12);
-	test_assert(strcmp(dest_msgs[0].msg->guid, "guid1") == 0);
-	test_assert(src_msgs[0].msg->uid == 11);
-	test_assert(strcmp(src_msgs[0].msg->guid, "guid2") == 0);
-	test_assert(dest_msgs[1].msg->uid == 13);
-	test_assert(strcmp(dest_msgs[1].msg->guid, "guid4") == 0);
-	test_assert(src_msgs[1].msg->uid == 14);
-	test_assert(strcmp(src_msgs[1].msg->guid, "guid5") == 0);
-	test_assert(dest_msgs[2].msg->uid == 15);
-	test_assert(strcmp(dest_msgs[2].msg->guid, "guid6") == 0);
-	test_assert(src_msgs[2].msg->uid == 16);
-	test_assert(strcmp(src_msgs[2].msg->guid, "guid9") == 0);
-
-	test_end();
-}
-
-int main(void)
-{
-	static void (*test_functions[])(void) = {
-		test_dsync_brain_msg_sync_box_multi,
-		test_dsync_brain_msg_sync_box_single,
-		test_dsync_brain_msg_sync_existing,
-		test_dsync_brain_msg_sync_expunges,
-		test_dsync_brain_msg_sync_uid_conflicts,
-		NULL
-	};
-
-	return test_run(test_functions);
-}
--- a/src/dsync/test-dsync-brain.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,294 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "master-service.h"
-#include "dsync-brain-private.h"
-#include "test-dsync-worker.h"
-#include "test-dsync-common.h"
-
-struct master_service *master_service;
-static struct test_dsync_worker *src_test_worker, *dest_test_worker;
-
-void master_service_stop(struct master_service *master_service ATTR_UNUSED)
-{
-}
-
-struct dsync_brain_mailbox_sync *
-dsync_brain_msg_sync_init(struct dsync_brain *brain,
-			  const ARRAY_TYPE(dsync_brain_mailbox) *mailboxes)
-{
-	struct dsync_brain_mailbox_sync *sync;
-
-	sync = i_new(struct dsync_brain_mailbox_sync, 1);
-	sync->brain = brain;
-	i_array_init(&sync->mailboxes, array_count(mailboxes));
-	array_append_array(&sync->mailboxes, mailboxes);
-	return sync;
-}
-void dsync_brain_msg_sync_more(struct dsync_brain_mailbox_sync *sync ATTR_UNUSED) {}
-
-void dsync_brain_msg_sync_deinit(struct dsync_brain_mailbox_sync **_sync)
-{
-	array_free(&(*_sync)->mailboxes);
-	i_free(*_sync);
-}
-
-static void mailboxes_set_guids(struct dsync_mailbox *boxes)
-{
-	for (; boxes->name != NULL; boxes++) {
-		dsync_str_sha_to_guid(t_strconcat("box-", boxes->name, NULL),
-				      &boxes->mailbox_guid);
-		dsync_str_sha_to_guid(boxes->name, &boxes->name_sha1);
-	}
-}
-
-static void mailboxes_send_to_worker(struct test_dsync_worker *test_worker,
-				     struct dsync_mailbox *boxes)
-{
-	unsigned int i;
-
-	for (i = 0; boxes[i].name != NULL; i++) {
-		test_worker->box_iter.next_box = &boxes[i];
-		test_worker->worker.input_callback(test_worker->worker.input_context);
-	}
-	test_worker->box_iter.last = TRUE;
-	test_worker->worker.input_callback(test_worker->worker.input_context);
-}
-
-static void subscriptions_send_to_worker(struct test_dsync_worker *test_worker)
-{
-	test_worker->subs_iter.last_subs = TRUE;
-	test_worker->subs_iter.last_unsubs = TRUE;
-	test_worker->worker.input_callback(test_worker->worker.input_context);
-}
-
-static bool
-test_dsync_mailbox_create_equals(const struct dsync_mailbox *cbox,
-				 const struct dsync_mailbox *obox)
-{
-	return strcmp(cbox->name, obox->name) == 0 &&
-		memcmp(cbox->mailbox_guid.guid, obox->mailbox_guid.guid,
-		       sizeof(cbox->mailbox_guid.guid)) == 0 &&
-		memcmp(cbox->name_sha1.guid, obox->name_sha1.guid,
-		       sizeof(cbox->name_sha1.guid)) == 0 &&
-		cbox->uid_validity == obox->uid_validity &&
-		cbox->uid_next == 1 && cbox->highest_modseq == 0;
-}
-
-static bool
-test_dsync_mailbox_delete_equals(const struct dsync_mailbox *dbox,
-				 const struct dsync_mailbox *obox)
-{
-	return memcmp(dbox->mailbox_guid.guid, obox->mailbox_guid.guid,
-		      sizeof(dbox->mailbox_guid.guid)) == 0 &&
-		dbox->last_change == obox->last_change;
-}
-
-static void
-test_dsync_mailbox_update(const struct dsync_mailbox *bbox,
-			  const struct dsync_mailbox *box)
-{
-	struct test_dsync_box_event src_event, dest_event;
-
-	test_assert(test_dsync_worker_next_box_event(src_test_worker, &src_event));
-	test_assert(test_dsync_worker_next_box_event(dest_test_worker, &dest_event));
-	test_assert(src_event.type == dest_event.type &&
-		    dsync_mailboxes_equal(&src_event.box, &dest_event.box));
-
-	test_assert(src_event.type == LAST_BOX_TYPE_UPDATE);
-	test_assert(dsync_mailboxes_equal(&src_event.box, box));
-	test_assert(dsync_mailboxes_equal(bbox, box));
-}
-
-static int
-dsync_brain_mailbox_name_cmp(const struct dsync_brain_mailbox *box1,
-			     const struct dsync_brain_mailbox *box2)
-{
-	return strcmp(box1->box.name, box2->box.name);
-}
-
-static void test_dsync_brain(void)
-{
-	static struct dsync_mailbox src_boxes[] = {
-		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123124ULL, 3636, 0, ARRAY_INIT },
-		{ "boxx", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
-		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
-	};
-	static struct dsync_mailbox dest_boxes[] = {
-		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567891, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123124ULL, 3636, 0, ARRAY_INIT },
-		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxy", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
-		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
-	};
-	struct dsync_brain *brain;
-	struct dsync_worker *src_worker, *dest_worker;
-	struct test_dsync_box_event box_event;
-	const struct dsync_brain_mailbox *brain_boxes;
-	unsigned int i, count;
-
-	test_begin("dsync brain");
-
-	mailboxes_set_guids(src_boxes);
-	mailboxes_set_guids(dest_boxes);
-
-	src_worker = dsync_worker_init_test();
-	dest_worker = dsync_worker_init_test();
-	src_test_worker = (struct test_dsync_worker *)src_worker;
-	dest_test_worker = (struct test_dsync_worker *)dest_worker;
-
-	brain = dsync_brain_init(src_worker, dest_worker, NULL,
-				 DSYNC_BRAIN_FLAG_LOCAL);
-	dsync_brain_sync(brain);
-
-	/* have brain read the mailboxes */
-	mailboxes_send_to_worker(src_test_worker, src_boxes);
-	mailboxes_send_to_worker(dest_test_worker, dest_boxes);
-
-	subscriptions_send_to_worker(src_test_worker);
-	subscriptions_send_to_worker(dest_test_worker);
-
-	test_assert(brain->state == DSYNC_STATE_SYNC_MSGS);
-
-	/* check that it created/deleted missing mailboxes */
-	test_assert(test_dsync_worker_next_box_event(dest_test_worker, &box_event));
-	test_assert(box_event.type == LAST_BOX_TYPE_DELETE);
-	test_assert(test_dsync_mailbox_delete_equals(&box_event.box, &dest_boxes[8]));
-
-	test_assert(test_dsync_worker_next_box_event(src_test_worker, &box_event));
-	test_assert(box_event.type == LAST_BOX_TYPE_DELETE);
-	test_assert(test_dsync_mailbox_delete_equals(&box_event.box, &src_boxes[7]));
-
-	test_assert(test_dsync_worker_next_box_event(dest_test_worker, &box_event));
-	test_assert(box_event.type == LAST_BOX_TYPE_CREATE);
-	test_assert(test_dsync_mailbox_create_equals(&box_event.box, &src_boxes[6]));
-
-	test_assert(test_dsync_worker_next_box_event(src_test_worker, &box_event));
-	test_assert(box_event.type == LAST_BOX_TYPE_CREATE);
-	test_assert(test_dsync_mailbox_create_equals(&box_event.box, &dest_boxes[6]));
-
-	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
-	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
-
-	array_sort(&brain->mailbox_sync->mailboxes,
-		   dsync_brain_mailbox_name_cmp);
-
-	/* check mailbox updates */
-	brain->state++;
-	dsync_brain_sync(brain);
-	test_assert(brain->state == DSYNC_STATE_SYNC_UPDATE_MAILBOXES);
-	dsync_brain_sync(brain);
-	test_assert(brain->state == DSYNC_STATE_SYNC_END);
-
-	brain_boxes = array_get(&brain->mailbox_sync->mailboxes, &count);
-	test_assert(count == 7);
-	for (i = 0; i < 5; i++) {
-		test_assert(dsync_mailboxes_equal(brain_boxes[i].src, &src_boxes[i+1]));
-		test_assert(dsync_mailboxes_equal(brain_boxes[i].dest, &dest_boxes[i+1]));
-	}
-	test_assert(dsync_mailboxes_equal(brain_boxes[5].src, &src_boxes[6]));
-	test_assert(brain_boxes[5].dest == NULL);
-	test_assert(brain_boxes[6].src == NULL);
-	test_assert(dsync_mailboxes_equal(brain_boxes[6].dest, &dest_boxes[6]));
-
-	test_dsync_mailbox_update(&brain_boxes[0].box, &src_boxes[1]);
-	test_dsync_mailbox_update(&brain_boxes[1].box, &dest_boxes[2]);
-	test_dsync_mailbox_update(&brain_boxes[2].box, &dest_boxes[3]);
-	test_dsync_mailbox_update(&brain_boxes[3].box, &src_boxes[4]);
-	test_dsync_mailbox_update(&brain_boxes[4].box, &src_boxes[5]);
-	test_dsync_mailbox_update(&brain_boxes[5].box, &src_boxes[6]);
-	test_dsync_mailbox_update(&brain_boxes[6].box, &dest_boxes[6]);
-
-	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
-	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
-
-	dsync_brain_deinit(&brain);
-	dsync_worker_deinit(&src_worker);
-	dsync_worker_deinit(&dest_worker);
-
-	test_end();
-}
-
-static void test_dsync_brain_full(void)
-{
-	static struct dsync_mailbox boxes[] = {
-		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 2352, 0, ARRAY_INIT },
-		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
-	};
-	struct dsync_brain *brain;
-	struct dsync_worker *src_worker, *dest_worker;
-	struct test_dsync_box_event box_event;
-	const struct dsync_brain_mailbox *brain_boxes;
-	unsigned int count;
-
-	test_begin("dsync brain full");
-
-	mailboxes_set_guids(boxes);
-
-	src_worker = dsync_worker_init_test();
-	dest_worker = dsync_worker_init_test();
-	src_test_worker = (struct test_dsync_worker *)src_worker;
-	dest_test_worker = (struct test_dsync_worker *)dest_worker;
-
-	brain = dsync_brain_init(src_worker, dest_worker, NULL,
-				 DSYNC_BRAIN_FLAG_FULL_SYNC |
-				 DSYNC_BRAIN_FLAG_LOCAL);
-	dsync_brain_sync(brain);
-
-	/* have brain read the mailboxes */
-	mailboxes_send_to_worker(src_test_worker, boxes);
-	mailboxes_send_to_worker(dest_test_worker, boxes);
-
-	subscriptions_send_to_worker(src_test_worker);
-	subscriptions_send_to_worker(dest_test_worker);
-
-	test_assert(brain->state == DSYNC_STATE_SYNC_MSGS);
-
-	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
-	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
-
-	/* check mailbox updates */
-	brain->state++;
-	dsync_brain_sync(brain);
-	test_assert(brain->state == DSYNC_STATE_SYNC_UPDATE_MAILBOXES);
-	dsync_brain_sync(brain);
-	test_assert(brain->state == DSYNC_STATE_SYNC_END);
-
-	brain_boxes = array_get(&brain->mailbox_sync->mailboxes, &count);
-	test_assert(count == 1);
-	test_assert(dsync_mailboxes_equal(brain_boxes[0].src, &boxes[0]));
-	test_assert(dsync_mailboxes_equal(brain_boxes[0].dest, &boxes[0]));
-	test_dsync_mailbox_update(&brain_boxes[0].box, &boxes[0]);
-
-	test_assert(!test_dsync_worker_next_box_event(src_test_worker, &box_event));
-	test_assert(!test_dsync_worker_next_box_event(dest_test_worker, &box_event));
-
-	dsync_brain_deinit(&brain);
-	dsync_worker_deinit(&src_worker);
-	dsync_worker_deinit(&dest_worker);
-
-	test_end();
-}
-
-int main(void)
-{
-	static void (*test_functions[])(void) = {
-		test_dsync_brain,
-		test_dsync_brain_full,
-		NULL
-	};
-	return test_run(test_functions);
-}
--- a/src/dsync/test-dsync-common.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "hex-binary.h"
-#include "sha1.h"
-#include "dsync-data.h"
-#include "test-dsync-common.h"
-
-const guid_128_t test_mailbox_guid1 = {
-	0x12, 0x34, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
-	0x21, 0x43, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe
-};
-
-const guid_128_t test_mailbox_guid2 = {
-	0xa3, 0xbd, 0x78, 0x24, 0xde, 0xfe, 0x08, 0xf7,
-	0xac, 0xc7, 0xca, 0x8c, 0xe7, 0x39, 0xdb, 0xca
-};
-
-bool dsync_messages_equal(const struct dsync_message *m1,
-			  const struct dsync_message *m2)
-{
-	unsigned int i;
-
-	if (strcmp(m1->guid, m2->guid) != 0 ||
-	    m1->uid != m2->uid || m1->flags != m2->flags ||
-	    m1->modseq != m2->modseq || m1->save_date != m2->save_date)
-		return FALSE;
-
-	if (m1->keywords == m2->keywords)
-		return TRUE;
-	if (m1->keywords == NULL)
-		return m2->keywords == NULL || m2->keywords[0] == NULL;
-	if (m2->keywords == NULL)
-		return m1->keywords[0] == NULL;
-
-	for (i = 0; m1->keywords[i] != NULL && m2->keywords[i] != NULL; i++) {
-		if (strcasecmp(m1->keywords[i], m2->keywords[i]) != 0)
-			return FALSE;
-	}
-	return m1->keywords[i] == NULL && m2->keywords[i] == NULL;
-}
-
-bool dsync_mailboxes_equal(const struct dsync_mailbox *box1,
-			   const struct dsync_mailbox *box2)
-{
-	const struct mailbox_cache_field *f1 = NULL, *f2 = NULL;
-	unsigned int i, f1_count = 0, f2_count = 0;
-
-	if (strcmp(box1->name, box2->name) != 0 ||
-	    box1->name_sep != box2->name_sep ||
-	    memcmp(box1->mailbox_guid.guid, box2->mailbox_guid.guid,
-		   sizeof(box1->mailbox_guid.guid)) != 0 ||
-	    box1->uid_validity != box2->uid_validity ||
-	    box1->uid_next != box2->uid_next ||
-	    box1->highest_modseq != box2->highest_modseq)
-		return FALSE;
-
-	if (array_is_created(&box1->cache_fields))
-		f1 = array_get(&box1->cache_fields, &f1_count);
-	if (array_is_created(&box2->cache_fields))
-		f2 = array_get(&box2->cache_fields, &f2_count);
-	if (f1_count != f2_count)
-		return FALSE;
-	for (i = 0; i < f1_count; i++) {
-		if (strcmp(f1[i].name, f2[i].name) != 0 ||
-		    f1[i].decision != f2[i].decision ||
-		    f1[i].last_used != f2[i].last_used)
-			return FALSE;
-	}
-	return TRUE;
-}
-
-void mail_generate_guid_128_hash(const char *guid, guid_128_t guid_128_r)
-{
-	unsigned char sha1_sum[SHA1_RESULTLEN];
-
-	sha1_get_digest(guid, strlen(guid), sha1_sum);
-	memcpy(guid_128_r, sha1_sum, GUID_128_SIZE);
-}
--- a/src/dsync/test-dsync-common.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#ifndef TEST_DSYNC_COMMON_H
-#define TEST_DSYNC_COMMON_H
-
-#include "test-common.h"
-#include "dsync-data.h"
-
-#define TEST_MAILBOX_GUID1 "1234456789abcdef2143547698badcfe"
-#define TEST_MAILBOX_GUID2 "a3bd7824defe08f7acc7ca8ce739dbca"
-
-extern const guid_128_t test_mailbox_guid1;
-extern const guid_128_t test_mailbox_guid2;
-
-bool dsync_messages_equal(const struct dsync_message *m1,
-			  const struct dsync_message *m2);
-bool dsync_mailboxes_equal(const struct dsync_mailbox *box1,
-			   const struct dsync_mailbox *box2);
-
-#endif
--- a/src/dsync/test-dsync-proxy-server-cmd.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,485 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "strescape.h"
-#include "istream.h"
-#include "ostream.h"
-#include "master-service.h"
-#include "test-common.h"
-#include "dsync-proxy-server.h"
-#include "test-dsync-worker.h"
-#include "test-dsync-common.h"
-
-#define ALL_MAIL_FLAGS "\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent"
-
-struct master_service *master_service;
-static string_t *out;
-static struct dsync_proxy_server *server;
-static struct test_dsync_worker *test_worker;
-static struct dsync_proxy_server_command *cur_cmd;
-static const char *cur_cmd_args[20];
-
-void master_service_stop(struct master_service *service ATTR_UNUSED) {}
-
-static void out_clear(void)
-{
-	o_stream_seek(server->output, 0);
-	str_truncate(out, 0);
-}
-
-static int run_more(void)
-{
-	int ret;
-
-	ret = cur_cmd->func(server, cur_cmd_args);
-	if (ret == 0)
-		return 0;
-
-	cur_cmd = NULL;
-	return ret;
-}
-
-static int ATTR_SENTINEL
-run_cmd(const char *cmd_name, ...)
-{
-	va_list va;
-	const char *str;
-	unsigned int i = 0;
-
-	i_assert(cur_cmd == NULL);
-
-	va_start(va, cmd_name);
-	while ((str = va_arg(va, const char *)) != NULL) {
-		i_assert(i < N_ELEMENTS(cur_cmd_args)+1);
-		cur_cmd_args[i++] = str;
-	}
-	cur_cmd_args[i] = NULL;
-	va_end(va);
-
-	cur_cmd = dsync_proxy_server_command_find(cmd_name);
-	i_assert(cur_cmd != NULL);
-	return run_more();
-}
-
-static void test_dsync_proxy_box_list(void)
-{
-	struct dsync_mailbox box;
-
-	test_begin("proxy server box list");
-
-	test_assert(run_cmd("BOX-LIST", NULL) == 0);
-
-	/* \noselect mailbox */
-	memset(&box, 0, sizeof(box));
-	box.name = "\t\001\r\nname\t\001\n\r";
-	box.name_sep = '/';
-	box.last_change = 992;
-	box.flags = DSYNC_MAILBOX_FLAG_NOSELECT;
-	test_worker->box_iter.next_box = &box;
-	test_assert(run_more() == 0);
-	test_assert(strcmp(str_c(out), t_strconcat(str_tabescape(box.name),
-		"\t/\t992\t1\n", NULL)) == 0);
-	out_clear();
-
-	/* selectable mailbox */
-	memset(&box, 0, sizeof(box));
-	box.name = "foo/bar";
-	box.name_sep = '/';
-	memcpy(box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE);
-	box.uid_validity = 4275878552;
-	box.uid_next = 4023233417;
-	box.message_count = 4525;
-	box.highest_modseq = 18080787909545915012ULL;
-	box.first_recent_uid = 353;
-	test_worker->box_iter.next_box = &box;
-
-	test_assert(run_more() == 0);
-
-	test_assert(strcmp(str_c(out), "foo/bar\t/\t0\t0\t"
-			   TEST_MAILBOX_GUID1"\t"
-			   "4275878552\t"
-			   "4023233417\t"
-			   "4525\t"
-			   "18080787909545915012\t"
-			   "353\n") == 0);
-	out_clear();
-
-	/* last mailbox */
-	test_worker->box_iter.last = TRUE;
-	test_assert(run_more() == 1);
-	test_assert(strcmp(str_c(out), "+\n") == 0);
-	out_clear();
-
-	test_end();
-}
-
-static void test_dsync_proxy_subs_list(void)
-{
-	const char *name;
-	struct dsync_worker_subscription subs;
-	struct dsync_worker_unsubscription unsubs;
-
-	test_begin("proxy server subs list");
-
-	test_assert(run_cmd("SUBS-LIST", NULL) == 0);
-
-	/* subscription */
-	name = "\t\001\r\nname\t\001\n\r";
-	subs.vname = name;
-	subs.storage_name = "\tstorage_name\n";
-	subs.last_change = 1234567890;
-	subs.ns_prefix = "\t\001\r\nprefix\t\001\n\r";
-	test_worker->subs_iter.next_subscription = &subs;
-	test_assert(run_more() == 0);
-	test_assert(strcmp(str_c(out), t_strconcat(
-		str_tabescape(name), "\t",
-		str_tabescape(subs.storage_name), "\t",
-		str_tabescape(subs.ns_prefix),
-		"\t1234567890\n", NULL)) == 0);
-	out_clear();
-
-	test_worker->subs_iter.last_subs = TRUE;
-	test_assert(run_more() == 0);
-	test_assert(strcmp(str_c(out), "+\n") == 0);
-	out_clear();
-
-	/* unsubscription */
-	memcpy(unsubs.name_sha1.guid, test_mailbox_guid1,
-	       sizeof(unsubs.name_sha1.guid));
-	unsubs.ns_prefix = "\t\001\r\nprefix2\t\001\n\r";
-	unsubs.last_change = 987654321;
-	test_worker->subs_iter.next_unsubscription = &unsubs;
-	test_assert(run_more() == 0);
-	test_assert(strcmp(str_c(out), t_strconcat(TEST_MAILBOX_GUID1, "\t",
-		str_tabescape(unsubs.ns_prefix), "\t987654321\n", NULL)) == 0);
-	out_clear();
-
-	test_worker->subs_iter.last_unsubs = TRUE;
-	test_assert(run_more() == 1);
-	test_assert(strcmp(str_c(out), "+\n") == 0);
-	out_clear();
-
-	test_end();
-}
-
-static void test_dsync_proxy_msg_list(void)
-{
-	static const char *test_keywords[] = {
-		"kw1", "kw2", NULL
-	};
-	struct dsync_message msg;
-	struct test_dsync_worker_msg test_msg;
-
-	test_begin("proxy server msg list");
-
-	test_assert(run_cmd("MSG-LIST", TEST_MAILBOX_GUID1, TEST_MAILBOX_GUID2, NULL) == 0);
-
-	memset(&msg, 0, sizeof(msg));
-	msg.guid = "\t\001\r\nguid\t\001\n\r";
-	msg.uid = 123;
-	msg.modseq = 98765432101234;
-	msg.save_date = 1234567890;
-
-	/* no flags */
-	test_msg.msg = msg;
-	test_msg.mailbox_idx = 98;
-	array_append(&test_worker->msg_iter.msgs, &test_msg, 1);
-	test_assert(run_more() == 0);
-	test_assert(strcmp(str_c(out), t_strconcat(
-		"98\t", str_tabescape(msg.guid),
-		"\t123\t98765432101234\t\t1234567890\n", NULL)) == 0);
-	out_clear();
-
-	/* all flags, some keywords */
-	msg.modseq = 1;
-	msg.save_date = 2;
-	msg.guid = "guid";
-	msg.flags = MAIL_FLAGS_MASK;
-	msg.keywords = test_keywords;
-	test_msg.msg = msg;
-	test_msg.mailbox_idx = 76;
-	array_append(&test_worker->msg_iter.msgs, &test_msg, 1);
-	test_assert(run_more() == 0);
-	test_assert(strcmp(str_c(out), "76\tguid\t123\t1\t"
-			   ALL_MAIL_FLAGS" kw1 kw2\t2\n") == 0);
-	out_clear();
-
-	/* last message */
-	test_worker->msg_iter.last = TRUE;
-	test_assert(run_more() == 1);
-	test_assert(strcmp(str_c(out), "+\n") == 0);
-	out_clear();
-
-	test_end();
-}
-
-static void test_dsync_proxy_box_create(void)
-{
-	struct test_dsync_box_event event;
-
-	test_begin("proxy server box create");
-
-	test_assert(run_cmd("BOX-CREATE", "noselect", "/",
-			    "553", "1", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_CREATE);
-	test_assert(strcmp(event.box.name, "noselect") == 0);
-	test_assert(event.box.name_sep == '/');
-	test_assert(event.box.last_change == 553);
-	test_assert(event.box.flags == DSYNC_MAILBOX_FLAG_NOSELECT);
-	test_assert(event.box.uid_validity == 0);
-
-	test_assert(run_cmd("BOX-CREATE", "selectable", "?",
-			    "61", "2", TEST_MAILBOX_GUID2, "1234567890", "9876",
-			    "4610", "28427847284728", "853", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_CREATE);
-	test_assert(strcmp(event.box.name, "selectable") == 0);
-	test_assert(event.box.name_sep == '?');
-	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
-	test_assert(event.box.flags == 2);
-	test_assert(event.box.uid_validity == 1234567890);
-	test_assert(event.box.uid_next == 9876);
-	test_assert(event.box.message_count == 4610);
-	test_assert(event.box.highest_modseq == 28427847284728);
-	test_assert(event.box.first_recent_uid == 853);
-	test_assert(event.box.last_change == 61);
-
-	test_end();
-}
-
-static void test_dsync_proxy_box_delete(void)
-{
-	struct test_dsync_box_event event;
-
-	test_begin("proxy server box delete");
-
-	test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID1, "4351", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_DELETE);
-	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
-	test_assert(event.box.last_change == 4351);
-
-	test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID2, "653", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_DELETE);
-	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
-	test_assert(event.box.last_change == 653);
-
-	test_end();
-}
-
-static void test_dsync_proxy_box_rename(void)
-{
-	struct test_dsync_box_event event;
-
-	test_begin("proxy server box rename");
-
-	test_assert(run_cmd("BOX-RENAME", TEST_MAILBOX_GUID1, "name\t1", "/", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_RENAME);
-	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
-	test_assert(strcmp(event.box.name, "name\t1") == 0);
-	test_assert(event.box.name_sep == '/');
-
-	test_assert(run_cmd("BOX-RENAME", TEST_MAILBOX_GUID2, "", "?", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_RENAME);
-	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
-	test_assert(strcmp(event.box.name, "") == 0);
-	test_assert(event.box.name_sep == '?');
-
-	test_end();
-}
-
-static void test_dsync_proxy_box_update(void)
-{
-	struct test_dsync_box_event event;
-
-	test_begin("proxy server box update");
-
-	test_assert(run_cmd("BOX-UPDATE", "updated", "/",
-			    "53", "2", TEST_MAILBOX_GUID1, "34343", "22",
-			    "58293", "2238427847284728", "2482", NULL) == 1);
-	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
-	test_assert(event.type == LAST_BOX_TYPE_UPDATE);
-	test_assert(strcmp(event.box.name, "updated") == 0);
-	test_assert(event.box.name_sep == '/');
-	test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
-	test_assert(event.box.flags == DSYNC_MAILBOX_FLAG_DELETED_MAILBOX);
-	test_assert(event.box.uid_validity == 34343);
-	test_assert(event.box.uid_next == 22);
-	test_assert(event.box.message_count == 58293);
-	test_assert(event.box.highest_modseq == 2238427847284728);
-	test_assert(event.box.first_recent_uid == 2482);
-	test_assert(event.box.last_change == 53);
-
-	test_end();
-}
-
-static void test_dsync_proxy_box_select(void)
-{
-	test_begin("proxy server box select");
-
-	test_assert(run_cmd("BOX-SELECT", TEST_MAILBOX_GUID1, NULL) == 1);
-	test_assert(memcmp(test_worker->selected_mailbox.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
-
-	test_assert(run_cmd("BOX-SELECT", TEST_MAILBOX_GUID2, NULL) == 1);
-	test_assert(memcmp(test_worker->selected_mailbox.guid, test_mailbox_guid2, GUID_128_SIZE) == 0);
-
-	test_end();
-}
-
-static void test_dsync_proxy_msg_update(void)
-{
-	struct test_dsync_msg_event event;
-
-	test_begin("proxy server msg update");
-
-	test_assert(run_cmd("MSG-UPDATE", "123", "4782782842924",
-			    "kw1 "ALL_MAIL_FLAGS" kw2", NULL) == 1);
-	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
-	test_assert(event.type == LAST_MSG_TYPE_UPDATE);
-	test_assert(event.msg.uid == 123);
-	test_assert(event.msg.modseq == 4782782842924);
-	test_assert(event.msg.flags == MAIL_FLAGS_MASK);
-	test_assert(strcmp(event.msg.keywords[0], "kw1") == 0);
-	test_assert(strcmp(event.msg.keywords[1], "kw2") == 0);
-	test_assert(event.msg.keywords[2] == NULL);
-
-	test_end();
-}
-
-static void test_dsync_proxy_msg_uid_change(void)
-{
-	struct test_dsync_msg_event event;
-
-	test_begin("proxy server msg uid change");
-
-	test_assert(run_cmd("MSG-UID-CHANGE", "454", "995", NULL) == 1);
-	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
-	test_assert(event.type == LAST_MSG_TYPE_UPDATE_UID);
-	test_assert(event.msg.uid == 454);
-	test_assert(event.msg.modseq == 995);
-
-	test_end();
-}
-
-static void test_dsync_proxy_msg_expunge(void)
-{
-	struct test_dsync_msg_event event;
-
-	test_begin("proxy server msg expunge");
-
-	test_assert(run_cmd("MSG-EXPUNGE", "8585", NULL) == 1);
-	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
-	test_assert(event.type == LAST_MSG_TYPE_EXPUNGE);
-	test_assert(event.msg.uid == 8585);
-
-	test_end();
-}
-
-static void test_dsync_proxy_msg_copy(void)
-{
-	struct test_dsync_msg_event msg_event;
-
-	test_begin("proxy server msg copy");
-
-	test_assert(run_cmd("MSG-COPY", TEST_MAILBOX_GUID1, "5454",
-			    "copyguid", "5678", "74782482882924", "\\Seen foo \\Draft",
-			    "8294284", NULL) == 1);
-	test_assert(test_dsync_worker_next_msg_event(test_worker, &msg_event));
-	test_assert(msg_event.type == LAST_MSG_TYPE_COPY);
-	test_assert(memcmp(msg_event.copy_src_mailbox.guid, test_mailbox_guid1, GUID_128_SIZE) == 0);
-	test_assert(msg_event.copy_src_uid == 5454);
-	test_assert(strcmp(msg_event.msg.guid, "copyguid") == 0);
-	test_assert(msg_event.msg.uid == 5678);
-	test_assert(msg_event.msg.modseq == 74782482882924);
-	test_assert(msg_event.msg.flags == (MAIL_SEEN | MAIL_DRAFT));
-	test_assert(strcmp(msg_event.msg.keywords[0], "foo") == 0);
-	test_assert(msg_event.msg.keywords[1] == NULL);
-	test_assert(msg_event.msg.save_date == 8294284);
-
-	test_end();
-}
-
-static void test_dsync_proxy_msg_save(void)
-{
-	static const char *input = "..dotty\n..behavior\nfrom you\n.\nstop";
-	struct test_dsync_msg_event event;
-	const unsigned char *data;
-	size_t size;
-
-	test_begin("proxy server msg save");
-
-	server->input = i_stream_create_from_data(input, strlen(input));
-
-	test_assert(run_cmd("MSG-SAVE", "28492428", "pop3uidl",
-			    "saveguid", "874", "33982482882924", "\\Flagged bar \\Answered",
-			    "8294284", NULL) == 1);
-	test_assert(test_dsync_worker_next_msg_event(test_worker, &event));
-	test_assert(event.type == LAST_MSG_TYPE_SAVE);
-	test_assert(event.save_data.received_date == 28492428);
-	test_assert(strcmp(event.save_data.pop3_uidl, "pop3uidl") == 0);
-	test_assert(strcmp(event.save_body, ".dotty\n.behavior\nfrom you") == 0);
-
-	test_assert(strcmp(event.msg.guid, "saveguid") == 0);
-	test_assert(event.msg.uid == 874);
-	test_assert(event.msg.modseq == 33982482882924);
-	test_assert(event.msg.flags == (MAIL_FLAGGED | MAIL_ANSWERED));
-	test_assert(strcmp(event.msg.keywords[0], "bar") == 0);
-	test_assert(event.msg.keywords[1] == NULL);
-	test_assert(event.msg.save_date == 8294284);
-
-	data = i_stream_get_data(server->input, &size);
-	test_assert(size == 4 && memcmp(data, "stop", 4) == 0);
-	i_stream_destroy(&server->input);
-
-	test_end();
-}
-
-static struct dsync_proxy_server *
-dsync_proxy_server_init_test(buffer_t *outbuf)
-{
-	struct dsync_proxy_server *server;
-
-	server = i_new(struct dsync_proxy_server, 1);
-	server->worker = dsync_worker_init_test();
-	server->fd_in = 0;
-	server->fd_out = 0;
-
-	server->cmd_pool = pool_alloconly_create("worker server cmd", 1024);
-	server->output = o_stream_create_buffer(outbuf);
-	return server;
-}
-
-int main(void)
-{
-	static void (*test_functions[])(void) = {
-		test_dsync_proxy_box_list,
-		test_dsync_proxy_subs_list,
-		test_dsync_proxy_msg_list,
-		test_dsync_proxy_box_create,
-		test_dsync_proxy_box_delete,
-		test_dsync_proxy_box_rename,
-		test_dsync_proxy_box_update,
-		test_dsync_proxy_box_select,
-		test_dsync_proxy_msg_update,
-		test_dsync_proxy_msg_uid_change,
-		test_dsync_proxy_msg_expunge,
-		test_dsync_proxy_msg_copy,
-		test_dsync_proxy_msg_save,
-		NULL
-	};
-
-	test_init();
-
-	out = buffer_create_dynamic(default_pool, 1024);
-	server = dsync_proxy_server_init_test(out);
-	test_worker = (struct test_dsync_worker *)server->worker;
-
-	test_run_funcs(test_functions);
-	return test_deinit();
-}
--- a/src/dsync/test-dsync-proxy.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "mail-cache.h"
-#include "dsync-proxy.h"
-#include "test-dsync-common.h"
-#include "test-common.h"
-
-static void test_dsync_proxy_msg(void)
-{
-	static const char *test_keywords[] = {
-		"kw1", "kw2", NULL
-	};
-	string_t *str;
-	struct dsync_message msg_in, msg_out;
-	const char *error;
-	pool_t pool;
-
-	memset(&msg_in, 0, sizeof(msg_in));
-	memset(&msg_out, 0, sizeof(msg_out));
-
-	pool = pool_alloconly_create("msg pool", 1024);
-	str = t_str_new(256);
-	msg_in.guid = "\t\001\r\nguid\t\001\n\r";
-	msg_in.uid = (uint32_t)-1;
-	msg_in.modseq = (uint64_t)-1;
-	msg_in.save_date = (1U << 31)-1;
-
-	test_begin("dsync proxy msg");
-
-	/* no flags */
-	dsync_proxy_msg_export(str, &msg_in);
-	test_assert(dsync_proxy_msg_import(pool, str_c(str),
-					   &msg_out, &error) == 0);
-	test_assert(dsync_messages_equal(&msg_in, &msg_out));
-
-	/* expunged flag */
-	msg_in.flags = DSYNC_MAIL_FLAG_EXPUNGED;
-	str_truncate(str, 0);
-	dsync_proxy_msg_export(str, &msg_in);
-	test_assert(dsync_proxy_msg_import(pool, str_c(str),
-					   &msg_out, &error) == 0);
-	test_assert(dsync_messages_equal(&msg_in, &msg_out));
-
-	/* expunged flag and another flag */
-	msg_in.flags = DSYNC_MAIL_FLAG_EXPUNGED | MAIL_DRAFT;
-	str_truncate(str, 0);
-	dsync_proxy_msg_export(str, &msg_in);
-	test_assert(dsync_proxy_msg_import(pool, str_c(str),
-					   &msg_out, &error) == 0);
-	test_assert(dsync_messages_equal(&msg_in, &msg_out));
-
-	/* all flags, some keywords */
-	msg_in.flags = MAIL_FLAGS_MASK;
-	msg_in.keywords = test_keywords;
-	str_truncate(str, 0);
-	dsync_proxy_msg_export(str, &msg_in);
-	test_assert(dsync_proxy_msg_import(pool, str_c(str),
-					   &msg_out, &error) == 0);
-	test_assert(dsync_messages_equal(&msg_in, &msg_out));
-
-	/* errors */
-	test_assert(dsync_proxy_msg_import(pool, "0", &msg_out, &error) < 0);
-	test_assert(dsync_proxy_msg_import(pool, "0\t0", &msg_out, &error) < 0);
-	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0", &msg_out, &error) < 0);
-	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0\t0", &msg_out, &error) < 0);
-	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0\t\\\t0", &msg_out, &error) < 0);
-	test_assert(dsync_proxy_msg_import(pool, "0\t0\t0\t\\seen foo \\foo\t0", &msg_out, &error) < 0);
-
-	/* flags */
-	test_assert(dsync_proxy_msg_parse_flags(pool, "\\seen \\draft", &msg_out) == 0);
-	test_assert(msg_out.flags == (MAIL_SEEN | MAIL_DRAFT));
-	test_assert(dsync_proxy_msg_parse_flags(pool, "\\answered \\flagged", &msg_out) == 0);
-	test_assert(msg_out.flags == (MAIL_ANSWERED | MAIL_FLAGGED));
-	test_assert(dsync_proxy_msg_parse_flags(pool, "\\deleted \\recent", &msg_out) == 0);
-	test_assert(msg_out.flags == (MAIL_DELETED | MAIL_RECENT));
-	test_assert(dsync_proxy_msg_parse_flags(pool, "\\draft draft \\seen", &msg_out) == 0);
-	test_assert(msg_out.flags == (MAIL_DRAFT | MAIL_SEEN));
-	test_assert(strcasecmp(msg_out.keywords[0], "draft") == 0 && msg_out.keywords[1] == NULL);
-
-	test_end();
-	pool_unref(&pool);
-}
-
-static void test_dsync_proxy_mailbox(void)
-{
-	static struct mailbox_cache_field cache1 =
-		{ "cache1", MAIL_CACHE_DECISION_NO, 1234 };
-	static struct mailbox_cache_field cache2 =
-		{ "cache2", MAIL_CACHE_DECISION_TEMP | MAIL_CACHE_DECISION_FORCED, 0 };
-	string_t *str;
-	struct dsync_mailbox box_in, box_out;
-	const char *error;
-	pool_t pool;
-
-	memset(&box_in, 0, sizeof(box_in));
-	memset(&box_out, 0, sizeof(box_out));
-
-	pool = pool_alloconly_create("mailbox pool", 1024);
-	str = t_str_new(256);
-
-	test_begin("dsync proxy mailbox");
-
-	/* test \noselect mailbox */
-	box_in.name = "\t\001\r\nname\t\001\n\r";
-	box_in.name_sep = '/';
-	box_in.flags = DSYNC_MAILBOX_FLAG_NOSELECT;
-	dsync_proxy_mailbox_export(str, &box_in);
-	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
-					       &box_out, &error) == 0);
-	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
-
-	/* real mailbox */
-	i_assert(sizeof(box_in.mailbox_guid.guid) == sizeof(test_mailbox_guid1));
-	memcpy(box_in.mailbox_guid.guid, test_mailbox_guid2, GUID_128_SIZE);
-	box_in.flags = 24242 & ~DSYNC_MAILBOX_FLAG_NOSELECT;
-	box_in.uid_validity = 0xf74d921b;
-	box_in.uid_next = 73529472;
-	box_in.highest_modseq = 0x123456789abcdef0ULL;
-
-	str_truncate(str, 0);
-	dsync_proxy_mailbox_export(str, &box_in);
-	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
-					       &box_out, &error) == 0);
-	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
-
-	/* limits */
-	box_in.uid_next = (uint32_t)-1;
-	box_in.highest_modseq = (uint64_t)-1;
-
-	str_truncate(str, 0);
-	dsync_proxy_mailbox_export(str, &box_in);
-	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
-					       &box_out, &error) == 0);
-	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
-
-	/* mailbox with cache fields */
-	t_array_init(&box_in.cache_fields, 10);
-	array_append(&box_in.cache_fields, &cache1, 1);
-	array_append(&box_in.cache_fields, &cache2, 1);
-
-	str_truncate(str, 0);
-	dsync_proxy_mailbox_export(str, &box_in);
-	test_assert(dsync_proxy_mailbox_import(pool, str_c(str),
-					       &box_out, &error) == 0);
-	test_assert(dsync_mailboxes_equal(&box_in, &box_out));
-
-	test_end();
-	pool_unref(&pool);
-}
-
-static void test_dsync_proxy_guid(void)
-{
-	mailbox_guid_t guid_in, guid_out;
-	string_t *str;
-
-	test_begin("dsync proxy mailbox guid");
-
-	str = t_str_new(128);
-	memcpy(guid_in.guid, test_mailbox_guid1, sizeof(guid_in.guid));
-	dsync_proxy_mailbox_guid_export(str, &guid_in);
-	test_assert(dsync_proxy_mailbox_guid_import(str_c(str), &guid_out) == 0);
-	test_assert(memcmp(guid_in.guid, guid_out.guid, sizeof(guid_in.guid)) == 0);
-
-	test_assert(dsync_proxy_mailbox_guid_import("12345678901234567890123456789012", &guid_out) == 0);
-	test_assert(dsync_proxy_mailbox_guid_import("1234567890123456789012345678901", &guid_out) < 0);
-	test_assert(dsync_proxy_mailbox_guid_import("1234567890123456789012345678901g", &guid_out) < 0);
-	test_assert(dsync_proxy_mailbox_guid_import("", &guid_out) < 0);
-
-	test_end();
-}
-
-int main(void)
-{
-	static void (*test_functions[])(void) = {
-		test_dsync_proxy_msg,
-		test_dsync_proxy_mailbox,
-		test_dsync_proxy_guid,
-		NULL
-	};
-	return test_run(test_functions);
-}
--- a/src/dsync/test-dsync-worker.c	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,481 +0,0 @@
-/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "istream.h"
-#include "test-dsync-worker.h"
-
-extern struct dsync_worker_vfuncs test_dsync_worker;
-
-struct dsync_worker *dsync_worker_init_test(void)
-{
-	struct test_dsync_worker *worker;
-
-	worker = i_new(struct test_dsync_worker, 1);
-	worker->worker.v = test_dsync_worker;
-	worker->tmp_pool = pool_alloconly_create("test worker", 256);
-	i_array_init(&worker->box_events, 64);
-	i_array_init(&worker->msg_events, 64);
-	i_array_init(&worker->results, 64);
-	worker->body_stream = i_stream_create_from_data("hdr\n\nbody", 9);
-	return &worker->worker;
-}
-
-static void test_worker_deinit(struct dsync_worker *_worker)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-
-	pool_unref(&worker->tmp_pool);
-	array_free(&worker->box_events);
-	array_free(&worker->msg_events);
-	array_free(&worker->results);
-	i_stream_unref(&worker->body_stream);
-	i_free(worker);
-}
-
-static bool test_worker_is_output_full(struct dsync_worker *worker ATTR_UNUSED)
-{
-	return FALSE;
-}
-
-static int test_worker_output_flush(struct dsync_worker *worker ATTR_UNUSED)
-{
-	return 1;
-}
-
-static struct dsync_worker_mailbox_iter *
-test_worker_mailbox_iter_init(struct dsync_worker *_worker)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-
-	i_assert(worker->box_iter.iter.worker == NULL);
-
-	worker->box_iter.iter.worker = _worker;
-	return &worker->box_iter.iter;
-}
-
-static int
-test_worker_mailbox_iter_next(struct dsync_worker_mailbox_iter *_iter,
-			      struct dsync_mailbox *dsync_box_r)
-{
-	struct test_dsync_worker_mailbox_iter *iter =
-		(struct test_dsync_worker_mailbox_iter *)_iter;
-
-	if (iter->next_box == NULL)
-		return iter->last ? -1 : 0;
-
-	*dsync_box_r = *iter->next_box;
-	iter->next_box = NULL;
-	return 1;
-}
-
-static int
-test_worker_mailbox_iter_deinit(struct dsync_worker_mailbox_iter *iter)
-{
-	struct test_dsync_worker *worker =
-		(struct test_dsync_worker *)iter->worker;
-
-	memset(&worker->box_iter, 0, sizeof(worker->box_iter));
-	return 0;
-}
-
-static struct dsync_worker_subs_iter *
-test_worker_subs_iter_init(struct dsync_worker *_worker)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-
-	i_assert(worker->subs_iter.iter.worker == NULL);
-
-	worker->subs_iter.iter.worker = _worker;
-	return &worker->subs_iter.iter;
-}
-
-static int
-test_worker_subs_iter_next(struct dsync_worker_subs_iter *_iter,
-			   struct dsync_worker_subscription *rec_r)
-{
-	struct test_dsync_worker_subs_iter *iter =
-		(struct test_dsync_worker_subs_iter *)_iter;
-
-	if (iter->next_subscription == NULL)
-		return iter->last_subs ? -1 : 0;
-
-	*rec_r = *iter->next_subscription;
-	iter->next_subscription = NULL;
-	return 1;
-}
-
-static int
-test_worker_subs_iter_next_un(struct dsync_worker_subs_iter *_iter,
-			      struct dsync_worker_unsubscription *rec_r)
-{
-	struct test_dsync_worker_subs_iter *iter =
-		(struct test_dsync_worker_subs_iter *)_iter;
-
-	if (iter->next_unsubscription == NULL)
-		return iter->last_unsubs ? -1 : 0;
-
-	*rec_r = *iter->next_unsubscription;
-	iter->next_unsubscription = NULL;
-	return 1;
-}
-
-static int
-test_worker_subs_iter_deinit(struct dsync_worker_subs_iter *iter)
-{
-	struct test_dsync_worker *worker =
-		(struct test_dsync_worker *)iter->worker;
-
-	memset(&worker->subs_iter, 0, sizeof(worker->subs_iter));
-	return 0;
-}
-
-static struct dsync_worker_msg_iter *
-test_worker_msg_iter_init(struct dsync_worker *_worker,
-			  const mailbox_guid_t mailboxes[],
-			  unsigned int mailbox_count)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-
-	i_assert(worker->msg_iter.iter.worker == NULL);
-
-	worker->msg_iter_mailboxes =
-		p_new(worker->tmp_pool, mailbox_guid_t, mailbox_count);
-	memcpy(worker->msg_iter_mailboxes, mailboxes,
-	       sizeof(mailboxes[0]) * mailbox_count);
-	worker->msg_iter_mailbox_count = mailbox_count;
-	i_array_init(&worker->msg_iter.msgs, 64);
-
-	worker->msg_iter.iter.worker = _worker;
-	return &worker->msg_iter.iter;
-}
-
-static int
-test_worker_msg_iter_next(struct dsync_worker_msg_iter *_iter,
-			  unsigned int *mailbox_idx_r,
-			  struct dsync_message *msg_r)
-{
-	struct test_dsync_worker_msg_iter *iter =
-		(struct test_dsync_worker_msg_iter *)_iter;
-	const struct test_dsync_worker_msg *msg;
-
-	if (iter->idx == array_count(&iter->msgs))
-		return iter->last ? -1 : 0;
-
-	msg = array_idx(&iter->msgs, iter->idx++);
-	*msg_r = msg->msg;
-	*mailbox_idx_r = msg->mailbox_idx;
-	return 1;
-}
-
-static int
-test_worker_msg_iter_deinit(struct dsync_worker_msg_iter *iter)
-{
-	struct test_dsync_worker *worker =
-		(struct test_dsync_worker *)iter->worker;
-
-	array_free(&worker->msg_iter.msgs);
-	memset(&worker->msg_iter, 0, sizeof(worker->msg_iter));
-	return 0;
-}
-
-static void
-test_worker_set_last_box(struct dsync_worker *_worker,
-			 const struct dsync_mailbox *dsync_box,
-			 enum test_dsync_last_box_type type)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct test_dsync_box_event event;
-
-	event.type = type;
-
-	event.box = *dsync_box;
-	event.box.name = p_strdup(worker->tmp_pool, dsync_box->name);
-	array_append(&worker->box_events, &event, 1);
-}
-
-bool test_dsync_worker_next_box_event(struct test_dsync_worker *worker,
-				      struct test_dsync_box_event *event_r)
-{
-	const struct test_dsync_box_event *events;
-	unsigned int count;
-
-	events = array_get(&worker->box_events, &count);
-	if (count == 0)
-		return FALSE;
-
-	*event_r = events[0];
-	array_delete(&worker->box_events, 0, 1);
-	return TRUE;
-}
-
-static void
-test_worker_set_subscribed(struct dsync_worker *_worker,
-			   const char *name, time_t last_change, bool set)
-{
-	struct dsync_mailbox dsync_box;
-
-	memset(&dsync_box, 0, sizeof(dsync_box));
-	dsync_box.name = name;
-	dsync_box.last_change = last_change;
-	test_worker_set_last_box(_worker, &dsync_box,
-				 set ? LAST_BOX_TYPE_SUBSCRIBE :
-				 LAST_BOX_TYPE_UNSUBSCRIBE);
-}
-
-static void
-test_worker_create_mailbox(struct dsync_worker *_worker,
-			   const struct dsync_mailbox *dsync_box)
-{
-	test_worker_set_last_box(_worker, dsync_box, LAST_BOX_TYPE_CREATE);
-}
-
-static void
-test_worker_delete_mailbox(struct dsync_worker *_worker,
-			   const struct dsync_mailbox *dsync_box)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct test_dsync_box_event event;
-
-	memset(&event, 0, sizeof(event));
-	event.type = LAST_BOX_TYPE_DELETE;
-
-	event.box = *dsync_box;
-	array_append(&worker->box_events, &event, 1);
-}
-
-static void
-test_worker_delete_dir(struct dsync_worker *_worker,
-		       const struct dsync_mailbox *dsync_box)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct test_dsync_box_event event;
-
-	memset(&event, 0, sizeof(event));
-	event.type = LAST_BOX_TYPE_DELETE_DIR;
-
-	event.box = *dsync_box;
-	array_append(&worker->box_events, &event, 1);
-}
-
-static void
-test_worker_rename_mailbox(struct dsync_worker *_worker,
-			   const mailbox_guid_t *mailbox,
-			   const struct dsync_mailbox *dsync_box)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct test_dsync_box_event event;
-
-	memset(&event, 0, sizeof(event));
-	event.type = LAST_BOX_TYPE_RENAME;
-
-	event.box = *dsync_box;
-	event.box.mailbox_guid = *mailbox;
-	array_append(&worker->box_events, &event, 1);
-}
-
-static void
-test_worker_update_mailbox(struct dsync_worker *_worker,
-			   const struct dsync_mailbox *dsync_box)
-{
-	test_worker_set_last_box(_worker, dsync_box, LAST_BOX_TYPE_UPDATE);
-}
-
-static void
-test_worker_select_mailbox(struct dsync_worker *_worker,
-			   const mailbox_guid_t *mailbox,
-			   const ARRAY_TYPE(mailbox_cache_field) *cache_fields)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct dsync_mailbox box;
-
-	worker->selected_mailbox = *mailbox;
-	worker->cache_fields = cache_fields;
-
-	memset(&box, 0, sizeof(box));
-	memcpy(box.mailbox_guid.guid, mailbox, sizeof(box.mailbox_guid.guid));
-}
-
-static struct test_dsync_msg_event *
-test_worker_set_last_msg(struct test_dsync_worker *worker,
-			 const struct dsync_message *msg,
-			 enum test_dsync_last_msg_type type)
-{
-	struct test_dsync_msg_event *event;
-	const char **keywords;
-	unsigned int i, count;
-
-	event = array_append_space(&worker->msg_events);
-	event->type = type;
-	event->msg = *msg;
-	event->mailbox = worker->selected_mailbox;
-	event->msg.guid = p_strdup(worker->tmp_pool, msg->guid);
-	if (msg->keywords != NULL) {
-		count = str_array_length(msg->keywords);
-		keywords = p_new(worker->tmp_pool, const char *, count+1);
-		for (i = 0; i < count; i++) {
-			keywords[i] = p_strdup(worker->tmp_pool,
-					       msg->keywords[i]);
-		}
-		event->msg.keywords = keywords;
-	}
-	return event;
-}
-
-bool test_dsync_worker_next_msg_event(struct test_dsync_worker *worker,
-				      struct test_dsync_msg_event *event_r)
-{
-	const struct test_dsync_msg_event *events;
-	unsigned int count;
-
-	events = array_get(&worker->msg_events, &count);
-	if (count == 0)
-		return FALSE;
-
-	*event_r = events[0];
-	array_delete(&worker->msg_events, 0, 1);
-	return TRUE;
-}
-
-static void
-test_worker_msg_update_metadata(struct dsync_worker *_worker,
-				const struct dsync_message *msg)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-
-	test_worker_set_last_msg(worker, msg, LAST_MSG_TYPE_UPDATE);
-}
-
-static void
-test_worker_msg_update_uid(struct dsync_worker *_worker,
-			   uint32_t old_uid, uint32_t new_uid)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct dsync_message msg;
-
-	memset(&msg, 0, sizeof(msg));
-	msg.uid = old_uid;
-	msg.modseq = new_uid;
-	test_worker_set_last_msg(worker, &msg, LAST_MSG_TYPE_UPDATE_UID);
-}
-
-static void test_worker_msg_expunge(struct dsync_worker *_worker, uint32_t uid)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct dsync_message msg;
-
-	memset(&msg, 0, sizeof(msg));
-	msg.uid = uid;
-	test_worker_set_last_msg(worker, &msg, LAST_MSG_TYPE_EXPUNGE);
-}
-
-static void
-test_worker_msg_copy(struct dsync_worker *_worker,
-		     const mailbox_guid_t *src_mailbox,
-		     uint32_t src_uid, const struct dsync_message *dest_msg,
-		     dsync_worker_copy_callback_t *callback, void *context)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct test_dsync_msg_event *event;
-
-	event = test_worker_set_last_msg(worker, dest_msg, LAST_MSG_TYPE_COPY);
-	event->copy_src_mailbox = *src_mailbox;
-	event->copy_src_uid = src_uid;
-	callback(TRUE, context);
-}
-
-static void
-test_worker_msg_save(struct dsync_worker *_worker,
-		     const struct dsync_message *msg,
-		     const struct dsync_msg_static_data *data,
-		     dsync_worker_save_callback_t *callback,
-		     void *context)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct test_dsync_msg_event *event;
-	const unsigned char *d;
-	size_t size;
-	ssize_t ret;
-	string_t *body;
-
-	event = test_worker_set_last_msg(worker, msg, LAST_MSG_TYPE_SAVE);
-	event->save_data.pop3_uidl = p_strdup(worker->tmp_pool, data->pop3_uidl);
-	event->save_data.received_date = data->received_date;
-
-	body = t_str_new(256);
-	while ((ret = i_stream_read_data(data->input, &d, &size, 0)) > 0) {
-		str_append_n(body, d, size);
-		i_stream_skip(data->input, size);
-	}
-	i_assert(ret == -1);
-	event->save_body = p_strdup(worker->tmp_pool, str_c(body));
-
-	callback(context);
-}
-
-static void
-test_worker_msg_save_cancel(struct dsync_worker *_worker ATTR_UNUSED)
-{
-}
-
-static void
-test_worker_msg_get(struct dsync_worker *_worker,
-		    const mailbox_guid_t *mailbox ATTR_UNUSED,
-		    uint32_t uid ATTR_UNUSED,
-		    dsync_worker_msg_callback_t *callback, void *context)
-{
-	struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
-	struct dsync_msg_static_data data;
-
-	memset(&data, 0, sizeof(data));
-	data.pop3_uidl = "uidl";
-	data.received_date = 123456;
-	data.input = worker->body_stream;
-	i_stream_seek(data.input, 0);
-	callback(DSYNC_MSG_GET_RESULT_SUCCESS, &data, context);
-}
-
-static void
-test_worker_finish(struct dsync_worker *_worker ATTR_UNUSED,
-		   dsync_worker_finish_callback_t *callback, void *context)
-{
-	callback(TRUE, context);
-}
-
-struct dsync_worker_vfuncs test_dsync_worker = {
-	test_worker_deinit,
-
-	test_worker_is_output_full,
-	test_worker_output_flush,
-
-	test_worker_mailbox_iter_init,
-	test_worker_mailbox_iter_next,
-	test_worker_mailbox_iter_deinit,
-
-	test_worker_subs_iter_init,
-	test_worker_subs_iter_next,
-	test_worker_subs_iter_next_un,
-	test_worker_subs_iter_deinit,
-	test_worker_set_subscribed,
-
-	test_worker_msg_iter_init,
-	test_worker_msg_iter_next,
-	test_worker_msg_iter_deinit,
-
-	test_worker_create_mailbox,
-	test_worker_delete_mailbox,
-	test_worker_delete_dir,
-	test_worker_rename_mailbox,
-	test_worker_update_mailbox,
-
-	test_worker_select_mailbox,
-	test_worker_msg_update_metadata,
-	test_worker_msg_update_uid,
-	test_worker_msg_expunge,
-	test_worker_msg_copy,
-	test_worker_msg_save,
-	test_worker_msg_save_cancel,
-	test_worker_msg_get,
-	test_worker_finish
-};
--- a/src/dsync/test-dsync-worker.h	Thu Dec 29 11:19:52 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-#ifndef TEST_DSYNC_WORKER_H
-#define TEST_DSYNC_WORKER_H
-
-#include "dsync-worker-private.h"
-
-enum test_dsync_last_box_type {
-	LAST_BOX_TYPE_CREATE,
-	LAST_BOX_TYPE_DELETE,
-	LAST_BOX_TYPE_DELETE_DIR,
-	LAST_BOX_TYPE_RENAME,
-	LAST_BOX_TYPE_UPDATE,
-	LAST_BOX_TYPE_SUBSCRIBE,
-	LAST_BOX_TYPE_UNSUBSCRIBE
-};
-
-enum test_dsync_last_msg_type {
-	LAST_MSG_TYPE_UPDATE,
-	LAST_MSG_TYPE_UPDATE_UID,
-	LAST_MSG_TYPE_EXPUNGE,
-	LAST_MSG_TYPE_COPY,
-	LAST_MSG_TYPE_SAVE
-};
-
-struct test_dsync_worker_mailbox_iter {
-	struct dsync_worker_mailbox_iter iter;
-	struct dsync_mailbox *next_box;
-	bool last;
-};
-
-struct test_dsync_worker_subs_iter {
-	struct dsync_worker_subs_iter iter;
-	struct dsync_worker_subscription *next_subscription;
-	struct dsync_worker_unsubscription *next_unsubscription;
-	bool last_subs, last_unsubs;
-};
-
-struct test_dsync_worker_msg {
-	struct dsync_message msg;
-	unsigned int mailbox_idx;
-};
-
-struct test_dsync_worker_msg_iter {
-	struct dsync_worker_msg_iter iter;
-	ARRAY_DEFINE(msgs, struct test_dsync_worker_msg);
-	unsigned int idx;
-	bool last;
-};
-
-struct test_dsync_worker_result {
-	uint32_t tag;
-	int result;
-};
-
-struct test_dsync_box_event {
-	enum test_dsync_last_box_type type;
-	struct dsync_mailbox box;
-};
-
-struct test_dsync_msg_event {
-	enum test_dsync_last_msg_type type;
-	struct dsync_message msg;
-
-	mailbox_guid_t mailbox, copy_src_mailbox;
-	uint32_t copy_src_uid;
-	struct dsync_msg_static_data save_data;
-	const char *save_body;
-};
-
-struct test_dsync_worker {
-	struct dsync_worker worker;
-	struct istream *body_stream;
-
-	struct test_dsync_worker_mailbox_iter box_iter;
-	struct test_dsync_worker_subs_iter subs_iter;
-	struct test_dsync_worker_msg_iter msg_iter;
-	ARRAY_DEFINE(results, struct test_dsync_worker_result);
-
-	pool_t tmp_pool;
-
-	ARRAY_DEFINE(box_events, struct test_dsync_box_event);
-	ARRAY_DEFINE(msg_events, struct test_dsync_msg_event);
-
-	mailbox_guid_t selected_mailbox;
-	mailbox_guid_t *msg_iter_mailboxes;
-	unsigned int msg_iter_mailbox_count;
-	const ARRAY_TYPE(mailbox_cache_field) *cache_fields;
-};
-
-struct dsync_worker *dsync_worker_init_test(void);
-
-bool test_dsync_worker_next_box_event(struct test_dsync_worker *worker,
-				      struct test_dsync_box_event *event_r);
-bool test_dsync_worker_next_msg_event(struct test_dsync_worker *worker,
-				      struct test_dsync_msg_event *event_r);
-
-#endif