Mercurial > dovecot > core-2.2
changeset 15782:6f024e0289da
dsync: -l parameter locking is now done on the server with "lower" hostname.
This allows running multi-master replication on two servers without two
dsyncs mixing up changes by running at the same time.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sat, 16 Feb 2013 16:47:40 +0200 |
parents | 5a3586ffc644 |
children | 502a50925641 |
files | src/doveadm/dsync/doveadm-dsync.c src/doveadm/dsync/dsync-brain-private.h src/doveadm/dsync/dsync-brain.c src/doveadm/dsync/dsync-brain.h src/doveadm/dsync/dsync-ibc-stream.c src/doveadm/dsync/dsync-ibc.h |
diffstat | 6 files changed, 161 insertions(+), 85 deletions(-) [+] |
line wrap: on
line diff
--- a/src/doveadm/dsync/doveadm-dsync.c Sat Feb 16 16:45:47 2013 +0200 +++ b/src/doveadm/dsync/doveadm-dsync.c Sat Feb 16 16:47:40 2013 +0200 @@ -30,7 +30,6 @@ #include <ctype.h> #include <sys/wait.h> -#define DSYNC_LOCK_FILENAME ".dovecot-sync.lock" #define DSYNC_COMMON_GETOPT_ARGS "+adEfl:m:n:r:Rs:" #define DSYNC_REMOTE_CMD_EXIT_WAIT_SECS 30 @@ -165,14 +164,12 @@ } static const char *const * -get_ssh_cmd_args(struct dsync_cmd_context *ctx, - const char *host, const char *login, const char *mail_user) +get_ssh_cmd_args(const char *host, const char *login, const char *mail_user) { static struct var_expand_table static_tab[] = { { 'u', NULL, "user" }, { '\0', NULL, "login" }, { '\0', NULL, "host" }, - { '\0', NULL, "lock_timeout" }, { '\0', NULL, NULL } }; struct var_expand_table *tab; @@ -186,7 +183,6 @@ tab[0].value = mail_user; tab[1].value = login; tab[2].value = host; - tab[3].value = dec2str(ctx->lock_timeout); t_array_init(&cmd_args, 8); str = t_str_new(128); @@ -255,7 +251,7 @@ /* 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. */ - *cmd_args_r = get_ssh_cmd_args(ctx, host, "", user); + *cmd_args_r = get_ssh_cmd_args(host, "", user); return TRUE; } @@ -371,8 +367,7 @@ } static const char *const * -parse_ssh_location(struct dsync_cmd_context *ctx, - const char *location, const char *username) +parse_ssh_location(const char *location, const char *username) { const char *host, *login; @@ -383,7 +378,7 @@ host = location; login = ""; } - return get_ssh_cmd_args(ctx, host, login, username); + return get_ssh_cmd_args(host, login, username); } static struct dsync_ibc * @@ -404,8 +399,9 @@ } static int -cmd_dsync_run_real(struct dsync_cmd_context *ctx, struct mail_user *user) +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_ibc *ibc, *ibc2 = NULL; struct dsync_brain *brain; struct mail_namespace *sync_ns = NULL; @@ -445,6 +441,7 @@ brain_flags |= DSYNC_BRAIN_FLAG_DEBUG; brain = dsync_brain_master_init(user, ibc, sync_ns, ctx->mailbox, ctx->sync_type, brain_flags, + ctx->lock_timeout, ctx->state_input == NULL ? "" : ctx->state_input); @@ -490,62 +487,6 @@ return ret; } -static int dsync_lock(struct mail_user *user, unsigned int lock_timeout, - const char **path_r, struct file_lock **lock_r) -{ - const char *home, *path; - int ret, fd; - - if ((ret = mail_user_get_home(user, &home)) < 0) { - i_error("Couldn't look up user's home dir"); - return -1; - } - if (ret == 0) { - i_error("User has no home directory"); - return -1; - } - - path = t_strconcat(home, "/"DSYNC_LOCK_FILENAME, NULL); - fd = creat(path, 0600); - if (fd == -1) { - i_error("Couldn't create lock %s: %m", path); - return -1; - } - - if (file_wait_lock(fd, path, F_WRLCK, FILE_LOCK_METHOD_FCNTL, - lock_timeout, lock_r) <= 0) { - i_error("Couldn't lock %s: %m", path); - (void)close(fd); - return -1; - } - *path_r = path; - return fd; -} - -static int -cmd_dsync_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) -{ - struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; - const char *lock_path; - struct file_lock *lock; - int lock_fd, ret; - - if (!ctx->lock) - return cmd_dsync_run_real(ctx, user); - - lock_fd = dsync_lock(user, ctx->lock_timeout, &lock_path, &lock); - if (lock_fd == -1) { - _ctx->exit_code = EX_TEMPFAIL; - return -1; - } else { - ret = cmd_dsync_run_real(ctx, user); - file_lock_free(&lock); - if (close(lock_fd) < 0) - i_error("close(%s) failed: %m", lock_path); - return ret; - } -} - static int cmd_dsync_prerun(struct doveadm_mail_cmd_context *_ctx, struct mail_storage_service_user *service_user, const char **error_r) @@ -599,7 +540,7 @@ ctx->remote_name = NULL; } remote_cmd_args = ctx->remote_name == NULL ? NULL : - parse_ssh_location(ctx, ctx->remote_name, + parse_ssh_location(ctx->remote_name, _ctx->cur_username); }
--- a/src/doveadm/dsync/dsync-brain-private.h Sat Feb 16 16:45:47 2013 +0200 +++ b/src/doveadm/dsync/dsync-brain-private.h Sat Feb 16 16:47:40 2013 +0200 @@ -6,9 +6,12 @@ #include "dsync-mailbox.h" #include "dsync-mailbox-state.h" +#define DSYNC_LOCK_FILENAME ".dovecot-sync.lock" + struct dsync_mailbox_tree_sync_change; enum dsync_state { + DSYNC_STATE_MASTER_RECV_HANDSHAKE, DSYNC_STATE_SLAVE_RECV_HANDSHAKE, /* if sync_type=STATE, the master brain knows the saved "last common mailbox state". this state is sent to the slave. */ @@ -49,6 +52,11 @@ char *sync_box; enum dsync_brain_sync_type sync_type; + unsigned int lock_timeout; + int lock_fd; + const char *lock_path; + struct file_lock *lock; + char hierarchy_sep; struct dsync_mailbox_tree *local_mailbox_tree; struct dsync_mailbox_tree *remote_mailbox_tree;
--- a/src/doveadm/dsync/dsync-brain.c Sat Feb 16 16:45:47 2013 +0200 +++ b/src/doveadm/dsync/dsync-brain.c Sat Feb 16 16:47:40 2013 +0200 @@ -3,11 +3,14 @@ #include "lib.h" #include "array.h" #include "hash.h" +#include "hostpid.h" #include "mail-namespace.h" #include "dsync-mailbox-tree.h" #include "dsync-ibc.h" #include "dsync-brain-private.h" +#include <sys/stat.h> + static const char *dsync_state_names[DSYNC_STATE_DONE+1] = { "recv_handshake", "send_last_common", @@ -61,6 +64,7 @@ brain->user = user; brain->ibc = ibc; brain->sync_type = DSYNC_BRAIN_SYNC_TYPE_UNKNOWN; + brain->lock_fd = -1; hash_table_create(&brain->mailbox_states, pool, 0, guid_128_hash, guid_128_cmp); p_array_init(&brain->remote_mailbox_states, pool, 64); @@ -83,7 +87,7 @@ dsync_brain_master_init(struct mail_user *user, struct dsync_ibc *ibc, struct mail_namespace *sync_ns, const char *sync_box, enum dsync_brain_sync_type sync_type, - enum dsync_brain_flags flags, + enum dsync_brain_flags flags, unsigned int lock_timeout, const char *state) { struct dsync_ibc_settings ibc_set; @@ -98,29 +102,26 @@ if (sync_ns != NULL) brain->sync_ns = sync_ns; brain->sync_box = p_strdup(brain->pool, sync_box); + brain->lock_timeout = lock_timeout; brain->master_brain = TRUE; dsync_brain_set_flags(brain, flags); - brain->state = DSYNC_STATE_SEND_MAILBOX_TREE; - if (sync_type == DSYNC_BRAIN_SYNC_TYPE_STATE) { - if (dsync_mailbox_states_import(brain->mailbox_states, - brain->pool, state, - &error) < 0) { - hash_table_clear(brain->mailbox_states, FALSE); - i_error("Saved sync state is invalid, " - "falling back to full sync: %s", error); - brain->sync_type = sync_type = - DSYNC_BRAIN_SYNC_TYPE_FULL; - } else { - brain->state = DSYNC_STATE_MASTER_SEND_LAST_COMMON; - } + if (sync_type == DSYNC_BRAIN_SYNC_TYPE_STATE && + dsync_mailbox_states_import(brain->mailbox_states, + brain->pool, state, &error) < 0) { + hash_table_clear(brain->mailbox_states, FALSE); + i_error("Saved sync state is invalid, " + "falling back to full sync: %s", error); + brain->sync_type = sync_type = DSYNC_BRAIN_SYNC_TYPE_FULL; } dsync_brain_mailbox_trees_init(brain); memset(&ibc_set, 0, sizeof(ibc_set)); + ibc_set.hostname = my_hostname; ibc_set.sync_ns_prefix = sync_ns == NULL ? NULL : sync_ns->prefix; ibc_set.sync_box = sync_box; ibc_set.sync_type = sync_type; + ibc_set.lock_timeout = lock_timeout; /* reverse the backup direction for the slave */ ibc_set.brain_flags = flags & ~(DSYNC_BRAIN_FLAG_BACKUP_SEND | DSYNC_BRAIN_FLAG_BACKUP_RECV); @@ -131,17 +132,23 @@ dsync_ibc_send_handshake(ibc, &ibc_set); dsync_ibc_set_io_callback(ibc, dsync_brain_run_io, brain); + brain->state = DSYNC_STATE_MASTER_RECV_HANDSHAKE; return brain; } struct dsync_brain * dsync_brain_slave_init(struct mail_user *user, struct dsync_ibc *ibc) { + struct dsync_ibc_settings ibc_set; struct dsync_brain *brain; brain = dsync_brain_common_init(user, ibc); brain->state = DSYNC_STATE_SLAVE_RECV_HANDSHAKE; + memset(&ibc_set, 0, sizeof(ibc_set)); + ibc_set.hostname = my_hostname; + dsync_ibc_send_handshake(ibc, &ibc_set); + dsync_ibc_set_io_callback(ibc, dsync_brain_run_io, brain); return brain; } @@ -166,11 +173,102 @@ hash_table_iterate_deinit(&brain->mailbox_states_iter); hash_table_destroy(&brain->mailbox_states); + if (brain->lock_fd != -1) { + /* unlink the lock file before it gets unlocked */ + if (unlink(brain->lock_path) < 0) + i_error("unlink(%s) failed: %m", brain->lock_path); + file_lock_free(&brain->lock); + i_close_fd(&brain->lock_fd); + } + ret = brain->failed ? -1 : 0; pool_unref(&brain->pool); return ret; } +static int +dsync_brain_lock(struct dsync_brain *brain, const char *remote_hostname) +{ + struct stat st1, st2; + const char *home; + int ret; + + if ((ret = strcmp(remote_hostname, my_hostname)) < 0) { + /* locking done by remote */ + return 0; + } + if (ret == 0 && !brain->master_brain) { + /* running dsync within the same server. + locking done by master brain. */ + return 0; + } + + if ((ret = mail_user_get_home(brain->user, &home)) < 0) { + i_error("Couldn't look up user's home dir"); + return -1; + } + if (ret == 0) { + i_error("User has no home directory"); + return -1; + } + + brain->lock_path = p_strconcat(brain->pool, home, + "/"DSYNC_LOCK_FILENAME, NULL); + for (;;) { + brain->lock_fd = creat(brain->lock_path, 0600); + if (brain->lock_fd == -1) { + i_error("Couldn't create lock %s: %m", + brain->lock_path); + return -1; + } + + if (file_wait_lock(brain->lock_fd, brain->lock_path, F_WRLCK, + FILE_LOCK_METHOD_FLOCK, brain->lock_timeout, + &brain->lock) <= 0) { + i_error("Couldn't lock %s: %m", brain->lock_path); + break; + } + if (fstat(brain->lock_fd, &st1) < 0) { + if (errno != ESTALE) { + i_error("fstat(%s) failed: %m", brain->lock_path); + break; + } + } else if (stat(brain->lock_path, &st2) < 0) { + if (errno != ENOENT) { + i_error("stat(%s) failed: %m", brain->lock_path); + break; + } + } else if (st1.st_ino == st2.st_ino) { + /* success */ + return 0; + } + /* file was recreated, try again */ + i_close_fd(&brain->lock_fd); + } + i_close_fd(&brain->lock_fd); + return -1; +} + +static bool dsync_brain_master_recv_handshake(struct dsync_brain *brain) +{ + const struct dsync_ibc_settings *ibc_set; + + i_assert(brain->master_brain); + + if (dsync_ibc_recv_handshake(brain->ibc, &ibc_set) == 0) + return FALSE; + + if (brain->lock_timeout > 0) { + if (dsync_brain_lock(brain, ibc_set->hostname) < 0) + return FALSE; + } + + brain->state = brain->sync_type == DSYNC_BRAIN_SYNC_TYPE_STATE ? + DSYNC_STATE_MASTER_SEND_LAST_COMMON : + DSYNC_STATE_SEND_MAILBOX_TREE; + return TRUE; +} + static bool dsync_brain_slave_recv_handshake(struct dsync_brain *brain) { const struct dsync_ibc_settings *ibc_set; @@ -180,6 +278,12 @@ if (dsync_ibc_recv_handshake(brain->ibc, &ibc_set) == 0) return FALSE; + if (ibc_set->lock_timeout > 0) { + brain->lock_timeout = ibc_set->lock_timeout; + if (dsync_brain_lock(brain, ibc_set->hostname) < 0) + return FALSE; + } + if (ibc_set->sync_ns_prefix != NULL) { brain->sync_ns = mail_namespace_find(brain->user->namespaces, ibc_set->sync_ns_prefix); @@ -268,6 +372,9 @@ dsync_state_names[brain->state]); } switch (brain->state) { + case DSYNC_STATE_MASTER_RECV_HANDSHAKE: + changed = dsync_brain_master_recv_handshake(brain); + break; case DSYNC_STATE_SLAVE_RECV_HANDSHAKE: changed = dsync_brain_slave_recv_handshake(brain); break;
--- a/src/doveadm/dsync/dsync-brain.h Sat Feb 16 16:45:47 2013 +0200 +++ b/src/doveadm/dsync/dsync-brain.h Sat Feb 16 16:47:40 2013 +0200 @@ -29,7 +29,7 @@ dsync_brain_master_init(struct mail_user *user, struct dsync_ibc *ibc, struct mail_namespace *sync_ns, const char *sync_box, enum dsync_brain_sync_type sync_type, - enum dsync_brain_flags flags, + enum dsync_brain_flags flags, unsigned int lock_timeout, const char *state); struct dsync_brain * dsync_brain_slave_init(struct mail_user *user, struct dsync_ibc *ibc);
--- a/src/doveadm/dsync/dsync-ibc-stream.c Sat Feb 16 16:45:47 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc-stream.c Sat Feb 16 16:47:40 2013 +0200 @@ -65,8 +65,9 @@ }, { .name = "handshake", .chr = 'H', + .required_keys = "hostname", .optional_keys = "sync_ns_prefix sync_box sync_type debug sync_all_namespaces " - "send_mail_requests backup_send backup_recv" + "send_mail_requests backup_send backup_recv lock_timeout" }, { .name = "mailbox_state", .chr = 'S', @@ -533,6 +534,7 @@ str_append_c(str, items[ITEM_HANDSHAKE].chr); encoder = dsync_serializer_encode_begin(ibc->serializers[ITEM_HANDSHAKE]); + dsync_serializer_encode_add(encoder, "hostname", set->hostname); if (set->sync_ns_prefix != NULL) { dsync_serializer_encode_add(encoder, "sync_ns_prefix", set->sync_ns_prefix); @@ -554,8 +556,12 @@ sync_type[0] = 's'; break; } - i_assert(sync_type[0] != '\0'); - dsync_serializer_encode_add(encoder, "sync_type", sync_type); + if (sync_type[0] != '\0') + dsync_serializer_encode_add(encoder, "sync_type", sync_type); + if (set->lock_timeout > 0) { + dsync_serializer_encode_add(encoder, "lock_timeout", + t_strdup_printf("%u", set->lock_timeout)); + } if ((set->brain_flags & DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS) != 0) dsync_serializer_encode_add(encoder, "send_mail_requests", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_BACKUP_SEND) != 0) @@ -595,6 +601,8 @@ p_clear(pool); set = p_new(pool, struct dsync_ibc_settings, 1); + value = dsync_deserializer_decode_get(decoder, "hostname"); + set->hostname = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "sync_ns_prefix", &value)) set->sync_ns_prefix = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "sync_box", &value)) @@ -616,6 +624,14 @@ return DSYNC_IBC_RECV_RET_TRYAGAIN; } } + if (dsync_deserializer_decode_try(decoder, "lock_timeout", &value)) { + if (str_to_uint(value, &set->lock_timeout) < 0 || + set->lock_timeout == 0) { + dsync_ibc_input_error(ibc, decoder, + "Invalid lock_timeout: %s", value); + return DSYNC_IBC_RECV_RET_TRYAGAIN; + } + } if (dsync_deserializer_decode_try(decoder, "send_mail_requests", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS; if (dsync_deserializer_decode_try(decoder, "backup_send", &value))
--- a/src/doveadm/dsync/dsync-ibc.h Sat Feb 16 16:45:47 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc.h Sat Feb 16 16:47:40 2013 +0200 @@ -29,6 +29,9 @@ }; struct dsync_ibc_settings { + /* Server hostname. Used for determining which server does the + locking. */ + const char *hostname; /* if non-NULL, sync only this namespace */ const char *sync_ns_prefix; /* if non-NULL, sync only this mailbox name */ @@ -36,6 +39,7 @@ enum dsync_brain_sync_type sync_type; enum dsync_brain_flags brain_flags; + unsigned int lock_timeout; }; void dsync_ibc_init_pipe(struct dsync_ibc **ibc1_r,