Mercurial > dovecot > core-2.2
changeset 13875:4c827134997f
Merged dsync into "doveadm dsync".
dsync symlink is installed for backwards compatibility.
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