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,