summaryrefslogtreecommitdiffstats
path: root/src/doveadm/dsync/dsync-brain-mailbox.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/doveadm/dsync/dsync-brain-mailbox.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/doveadm/dsync/dsync-brain-mailbox.c')
-rw-r--r--src/doveadm/dsync/dsync-brain-mailbox.c923
1 files changed, 923 insertions, 0 deletions
diff --git a/src/doveadm/dsync/dsync-brain-mailbox.c b/src/doveadm/dsync/dsync-brain-mailbox.c
new file mode 100644
index 0000000..759018f
--- /dev/null
+++ b/src/doveadm/dsync/dsync-brain-mailbox.c
@@ -0,0 +1,923 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "mail-cache-private.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "dsync-ibc.h"
+#include "dsync-mailbox-tree.h"
+#include "dsync-mailbox-import.h"
+#include "dsync-mailbox-export.h"
+#include "dsync-transaction-log-scan.h"
+#include "dsync-brain-private.h"
+
+static int
+ns_mailbox_try_alloc(struct dsync_brain *brain, struct mail_namespace *ns,
+ const guid_128_t guid, struct mailbox **box_r,
+ const char **errstr_r, enum mail_error *error_r)
+{
+ enum mailbox_flags flags = 0;
+ struct mailbox *box;
+ enum mailbox_existence existence;
+ int ret;
+
+ if (brain->backup_send) {
+ /* make sure mailbox isn't modified */
+ flags |= MAILBOX_FLAG_READONLY;
+ }
+
+ box = mailbox_alloc_guid(ns->list, guid, flags);
+ ret = mailbox_exists(box, FALSE, &existence);
+ if (ret < 0) {
+ *errstr_r = mailbox_get_last_internal_error(box, error_r);
+ mailbox_free(&box);
+ return -1;
+ }
+ if (existence != MAILBOX_EXISTENCE_SELECT) {
+ mailbox_free(&box);
+ *errstr_r = existence == MAILBOX_EXISTENCE_NONE ?
+ "Mailbox was already deleted" :
+ "Mailbox is no longer selectable";
+ return 0;
+ }
+ *box_r = box;
+ return 1;
+}
+
+int dsync_brain_mailbox_alloc(struct dsync_brain *brain, const guid_128_t guid,
+ struct mailbox **box_r, const char **errstr_r,
+ enum mail_error *error_r)
+{
+ struct mail_namespace *ns;
+ int ret;
+
+ *box_r = NULL;
+
+ for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) {
+ if (!dsync_brain_want_namespace(brain, ns))
+ continue;
+ if ((ret = ns_mailbox_try_alloc(brain, ns, guid, box_r,
+ errstr_r, error_r)) != 0)
+ return ret;
+ }
+ return 0;
+}
+
+static void
+dsync_mailbox_cache_field_dup(ARRAY_TYPE(mailbox_cache_field) *dest,
+ const ARRAY_TYPE(mailbox_cache_field) *src,
+ pool_t pool)
+{
+ const struct mailbox_cache_field *src_field;
+ struct mailbox_cache_field *dest_field;
+
+ p_array_init(dest, pool, array_count(src));
+ array_foreach(src, src_field) {
+ dest_field = array_append_space(dest);
+ dest_field->name = p_strdup(pool, src_field->name);
+ dest_field->decision = src_field->decision;
+ dest_field->last_used = src_field->last_used;
+ }
+}
+
+
+static const struct dsync_mailbox_state *
+dsync_mailbox_state_find(struct dsync_brain *brain,
+ const guid_128_t mailbox_guid)
+{
+ const uint8_t *guid_p;
+
+ guid_p = mailbox_guid;
+ return hash_table_lookup(brain->mailbox_states, guid_p);
+}
+
+static void
+dsync_mailbox_state_remove(struct dsync_brain *brain,
+ const guid_128_t mailbox_guid)
+{
+ const uint8_t *guid_p;
+
+ guid_p = mailbox_guid;
+ if (hash_table_lookup(brain->mailbox_states, guid_p) != NULL)
+ hash_table_remove(brain->mailbox_states, guid_p);
+}
+
+void dsync_brain_sync_init_box_states(struct dsync_brain *brain)
+{
+ if (brain->backup_send) {
+ /* we have an exporter, but no importer. */
+ brain->box_send_state = DSYNC_BOX_STATE_ATTRIBUTES;
+ brain->box_recv_state = brain->mail_requests ?
+ DSYNC_BOX_STATE_MAIL_REQUESTS :
+ DSYNC_BOX_STATE_RECV_LAST_COMMON;
+ } else if (brain->backup_recv) {
+ /* we have an importer, but no exporter */
+ brain->box_send_state = brain->mail_requests ?
+ DSYNC_BOX_STATE_MAIL_REQUESTS :
+ DSYNC_BOX_STATE_DONE;
+ brain->box_recv_state = DSYNC_BOX_STATE_ATTRIBUTES;
+ } else {
+ brain->box_send_state = DSYNC_BOX_STATE_ATTRIBUTES;
+ brain->box_recv_state = DSYNC_BOX_STATE_ATTRIBUTES;
+ }
+}
+
+static void
+dsync_brain_sync_mailbox_init(struct dsync_brain *brain,
+ struct mailbox *box,
+ struct file_lock *lock,
+ const struct dsync_mailbox *local_dsync_box,
+ bool wait_for_remote_box)
+{
+ const struct dsync_mailbox_state *state;
+
+ i_assert(brain->box_importer == NULL);
+ i_assert(brain->box_exporter == NULL);
+ i_assert(box->synced);
+
+ brain->box = box;
+ brain->box_lock = lock;
+ brain->pre_box_state = brain->state;
+ if (wait_for_remote_box) {
+ brain->box_send_state = DSYNC_BOX_STATE_MAILBOX;
+ brain->box_recv_state = DSYNC_BOX_STATE_MAILBOX;
+ } else {
+ dsync_brain_sync_init_box_states(brain);
+ }
+ brain->local_dsync_box = *local_dsync_box;
+ if (brain->dsync_box_pool != NULL)
+ p_clear(brain->dsync_box_pool);
+ else {
+ brain->dsync_box_pool =
+ pool_alloconly_create(MEMPOOL_GROWING"dsync brain box pool", 2048);
+ }
+ dsync_mailbox_cache_field_dup(&brain->local_dsync_box.cache_fields,
+ &local_dsync_box->cache_fields,
+ brain->dsync_box_pool);
+ i_zero(&brain->remote_dsync_box);
+
+ state = dsync_mailbox_state_find(brain, local_dsync_box->mailbox_guid);
+ if (state != NULL)
+ brain->mailbox_state = *state;
+ else {
+ i_zero(&brain->mailbox_state);
+ memcpy(brain->mailbox_state.mailbox_guid,
+ local_dsync_box->mailbox_guid,
+ sizeof(brain->mailbox_state.mailbox_guid));
+ brain->mailbox_state.last_uidvalidity =
+ local_dsync_box->uid_validity;
+ }
+}
+
+static void
+dsync_brain_sync_mailbox_init_remote(struct dsync_brain *brain,
+ const struct dsync_mailbox *remote_dsync_box)
+{
+ enum dsync_mailbox_import_flags import_flags = 0;
+ const struct dsync_mailbox_state *state;
+ uint32_t last_common_uid;
+ uint64_t last_common_modseq, last_common_pvt_modseq;
+
+ i_assert(brain->box_importer == NULL);
+ i_assert(brain->log_scan != NULL);
+
+ i_assert(memcmp(brain->local_dsync_box.mailbox_guid,
+ remote_dsync_box->mailbox_guid,
+ sizeof(remote_dsync_box->mailbox_guid)) == 0);
+
+ brain->remote_dsync_box = *remote_dsync_box;
+ dsync_mailbox_cache_field_dup(&brain->remote_dsync_box.cache_fields,
+ &remote_dsync_box->cache_fields,
+ brain->dsync_box_pool);
+
+ state = dsync_mailbox_state_find(brain, remote_dsync_box->mailbox_guid);
+ if (state != NULL) {
+ last_common_uid = state->last_common_uid;
+ last_common_modseq = state->last_common_modseq;
+ last_common_pvt_modseq = state->last_common_pvt_modseq;
+ } else {
+ last_common_uid = 0;
+ last_common_modseq = 0;
+ last_common_pvt_modseq = 0;
+ }
+
+ if (brain->mail_requests)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_WANT_MAIL_REQUESTS;
+ if (brain->master_brain)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_MASTER_BRAIN;
+ if (brain->backup_recv && !brain->no_backup_overwrite)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_REVERT_LOCAL_CHANGES;
+ if (brain->debug)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_DEBUG;
+ if (brain->local_dsync_box.have_save_guids &&
+ (remote_dsync_box->have_save_guids ||
+ (brain->backup_recv && remote_dsync_box->have_guids)))
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_MAILS_HAVE_GUIDS;
+ if (brain->local_dsync_box.have_only_guid128 ||
+ remote_dsync_box->have_only_guid128)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_MAILS_USE_GUID128;
+ if (brain->no_notify)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_NO_NOTIFY;
+ if (brain->empty_hdr_workaround)
+ import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_EMPTY_HDR_WORKAROUND;
+
+ brain->box_importer = brain->backup_send ? NULL :
+ dsync_mailbox_import_init(brain->box, brain->virtual_all_box,
+ brain->log_scan,
+ last_common_uid, last_common_modseq,
+ last_common_pvt_modseq,
+ remote_dsync_box->uid_next,
+ remote_dsync_box->first_recent_uid,
+ remote_dsync_box->highest_modseq,
+ remote_dsync_box->highest_pvt_modseq,
+ brain->sync_since_timestamp,
+ brain->sync_until_timestamp,
+ brain->sync_max_size,
+ brain->sync_flag,
+ brain->import_commit_msgs_interval,
+ import_flags, brain->hdr_hash_version,
+ brain->hashed_headers);
+}
+
+int dsync_brain_sync_mailbox_open(struct dsync_brain *brain,
+ const struct dsync_mailbox *remote_dsync_box)
+{
+ struct mailbox_status status;
+ enum dsync_mailbox_exporter_flags exporter_flags = 0;
+ uint32_t last_common_uid, highest_wanted_uid;
+ uint64_t last_common_modseq, last_common_pvt_modseq;
+ const char *desync_reason = "";
+ bool pvt_too_old;
+ int ret;
+
+ i_assert(brain->log_scan == NULL);
+ i_assert(brain->box_exporter == NULL);
+
+ last_common_uid = brain->mailbox_state.last_common_uid;
+ last_common_modseq = brain->mailbox_state.last_common_modseq;
+ last_common_pvt_modseq = brain->mailbox_state.last_common_pvt_modseq;
+ highest_wanted_uid = last_common_uid == 0 ?
+ (uint32_t)-1 : last_common_uid;
+ ret = dsync_transaction_log_scan_init(brain->box->view,
+ brain->box->view_pvt,
+ highest_wanted_uid,
+ last_common_modseq,
+ last_common_pvt_modseq,
+ &brain->log_scan, &pvt_too_old);
+ if (ret < 0) {
+ i_error("Failed to read transaction log for mailbox %s",
+ mailbox_get_vname(brain->box));
+ brain->failed = TRUE;
+ return -1;
+ }
+
+ mailbox_get_open_status(brain->box, STATUS_UIDNEXT |
+ STATUS_HIGHESTMODSEQ |
+ STATUS_HIGHESTPVTMODSEQ, &status);
+ if (status.nonpermanent_modseqs)
+ status.highest_modseq = 0;
+ if (ret == 0) {
+ if (pvt_too_old) {
+ desync_reason = t_strdup_printf(
+ "Private modseq %"PRIu64" no longer in transaction log "
+ "(highest=%"PRIu64", last_common_uid=%u, nextuid=%u)",
+ last_common_pvt_modseq,
+ status.highest_pvt_modseq, last_common_uid,
+ status.uidnext);
+ } else {
+ desync_reason = t_strdup_printf(
+ "Modseq %"PRIu64" no longer in transaction log "
+ "(highest=%"PRIu64", last_common_uid=%u, nextuid=%u)",
+ last_common_modseq,
+ status.highest_modseq, last_common_uid,
+ status.uidnext);
+ }
+ }
+
+ if (last_common_uid != 0) {
+ /* if last_common_* is higher than our current ones it means
+ that the incremental sync state is stale and we need to do
+ a full resync */
+ if (status.uidnext < last_common_uid) {
+ desync_reason = t_strdup_printf("uidnext %u < %u",
+ status.uidnext, last_common_uid);
+ ret = 0;
+ } else if (status.highest_modseq < last_common_modseq) {
+ desync_reason = t_strdup_printf("highest_modseq %"PRIu64" < %"PRIu64,
+ status.highest_modseq, last_common_modseq);
+ ret = 0;
+ } else if (status.highest_pvt_modseq < last_common_pvt_modseq) {
+ desync_reason = t_strdup_printf("highest_pvt_modseq %"PRIu64" < %"PRIu64,
+ status.highest_pvt_modseq, last_common_pvt_modseq);
+ ret = 0;
+ }
+ }
+ if (ret == 0) {
+ i_warning("Failed to do incremental sync for mailbox %s, "
+ "retry with a full sync (%s)",
+ mailbox_get_vname(brain->box), desync_reason);
+ dsync_brain_set_changes_during_sync(brain, t_strdup_printf(
+ "Incremental sync failed: %s", desync_reason));
+ brain->require_full_resync = TRUE;
+ return 0;
+ }
+
+ if (!brain->mail_requests)
+ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_AUTO_EXPORT_MAILS;
+ if (remote_dsync_box->have_save_guids &&
+ (brain->local_dsync_box.have_save_guids ||
+ (brain->backup_send && brain->local_dsync_box.have_guids)))
+ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_MAILS_HAVE_GUIDS;
+ if (brain->no_mail_prefetch)
+ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_MINIMAL_DMAIL_FILL;
+ if (brain->sync_since_timestamp > 0 ||
+ brain->sync_until_timestamp > 0)
+ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_TIMESTAMPS;
+ if (brain->sync_max_size > 0)
+ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_VSIZES;
+ if (remote_dsync_box->messages_count == 0) {
+ /* remote mailbox is empty - we don't really need to export
+ header hashes since they're not going to match anything
+ anyway. */
+ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_NO_HDR_HASHES;
+ }
+
+ brain->box_exporter = brain->backup_recv ? NULL :
+ dsync_mailbox_export_init(brain->box, brain->log_scan,
+ last_common_uid,
+ exporter_flags,
+ brain->hdr_hash_version,
+ brain->hashed_headers);
+ dsync_brain_sync_mailbox_init_remote(brain, remote_dsync_box);
+ return 1;
+}
+
+void dsync_brain_sync_mailbox_deinit(struct dsync_brain *brain)
+{
+ enum mail_error error;
+
+ i_assert(brain->box != NULL);
+
+ array_push_back(&brain->remote_mailbox_states, &brain->mailbox_state);
+ if (brain->box_exporter != NULL) {
+ const char *errstr;
+
+ i_assert(brain->failed || brain->require_full_resync ||
+ brain->sync_type == DSYNC_BRAIN_SYNC_TYPE_CHANGED);
+ if (dsync_mailbox_export_deinit(&brain->box_exporter,
+ &errstr, &error) < 0)
+ i_error("Mailbox export failed: %s", errstr);
+ }
+ if (brain->box_importer != NULL) {
+ uint32_t last_common_uid, last_messages_count;
+ uint64_t last_common_modseq, last_common_pvt_modseq;
+ const char *changes_during_sync;
+ bool require_full_resync;
+
+ i_assert(brain->failed);
+ (void)dsync_mailbox_import_deinit(&brain->box_importer,
+ FALSE,
+ &last_common_uid,
+ &last_common_modseq,
+ &last_common_pvt_modseq,
+ &last_messages_count,
+ &changes_during_sync,
+ &require_full_resync,
+ &brain->mail_error);
+ if (require_full_resync)
+ brain->require_full_resync = TRUE;
+ }
+ if (brain->log_scan != NULL)
+ dsync_transaction_log_scan_deinit(&brain->log_scan);
+ file_lock_free(&brain->box_lock);
+ mailbox_free(&brain->box);
+
+ brain->state = brain->pre_box_state;
+}
+
+static int dsync_box_get(struct mailbox *box, struct dsync_mailbox *dsync_box_r,
+ enum mail_error *error_r)
+{
+ const enum mailbox_status_items status_items =
+ STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES |
+ STATUS_FIRST_RECENT_UID | STATUS_HIGHESTMODSEQ |
+ STATUS_HIGHESTPVTMODSEQ;
+ const enum mailbox_metadata_items metadata_items =
+ MAILBOX_METADATA_CACHE_FIELDS | MAILBOX_METADATA_GUID;
+ struct mailbox_status status;
+ struct mailbox_metadata metadata;
+ const char *errstr;
+ enum mail_error error;
+
+ /* get metadata first, since it may autocreate the mailbox */
+ if (mailbox_get_metadata(box, metadata_items, &metadata) < 0 ||
+ mailbox_get_status(box, status_items, &status) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ 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 0;
+ }
+ i_error("Failed to access mailbox %s: %s",
+ mailbox_get_vname(box), errstr);
+ *error_r = error;
+ return -1;
+ }
+ if (status.nonpermanent_modseqs)
+ status.highest_modseq = 0;
+
+ i_assert(status.uidvalidity != 0 || status.messages == 0);
+
+ i_zero(dsync_box_r);
+ memcpy(dsync_box_r->mailbox_guid, metadata.guid,
+ sizeof(dsync_box_r->mailbox_guid));
+ dsync_box_r->uid_validity = status.uidvalidity;
+ dsync_box_r->uid_next = status.uidnext;
+ dsync_box_r->messages_count = status.messages;
+ dsync_box_r->first_recent_uid = status.first_recent_uid;
+ dsync_box_r->highest_modseq = status.highest_modseq;
+ dsync_box_r->highest_pvt_modseq = status.highest_pvt_modseq;
+ dsync_mailbox_cache_field_dup(&dsync_box_r->cache_fields,
+ metadata.cache_fields,
+ pool_datastack_create());
+ dsync_box_r->have_guids = status.have_guids;
+ dsync_box_r->have_save_guids = status.have_save_guids;
+ dsync_box_r->have_only_guid128 = status.have_only_guid128;
+ return 1;
+}
+
+static bool
+dsync_brain_has_mailbox_state_changed(struct dsync_brain *brain,
+ const struct dsync_mailbox *dsync_box)
+{
+ const struct dsync_mailbox_state *state;
+
+ if (brain->sync_type != DSYNC_BRAIN_SYNC_TYPE_STATE)
+ return TRUE;
+
+ state = dsync_mailbox_state_find(brain, dsync_box->mailbox_guid);
+ return state == NULL ||
+ state->last_uidvalidity != dsync_box->uid_validity ||
+ state->last_common_uid+1 != dsync_box->uid_next ||
+ state->last_common_modseq != dsync_box->highest_modseq ||
+ state->last_common_pvt_modseq != dsync_box->highest_pvt_modseq ||
+ state->last_messages_count != dsync_box->messages_count;
+}
+
+static int
+dsync_brain_try_next_mailbox(struct dsync_brain *brain, struct mailbox **box_r,
+ struct file_lock **lock_r,
+ struct dsync_mailbox *dsync_box_r)
+{
+ enum mailbox_flags flags = 0;
+ struct dsync_mailbox dsync_box;
+ struct mailbox *box;
+ struct file_lock *lock = NULL;
+ struct dsync_mailbox_node *node;
+ const char *vname = NULL;
+ enum mail_error error;
+ bool synced = FALSE;
+ int ret;
+
+ *box_r = NULL;
+
+ while (dsync_mailbox_tree_iter_next(brain->local_tree_iter, &vname, &node)) {
+ if (node->existence == DSYNC_MAILBOX_NODE_EXISTS &&
+ !guid_128_is_empty(node->mailbox_guid))
+ break;
+ vname = NULL;
+ }
+ if (vname == NULL) {
+ /* no more mailboxes */
+ dsync_mailbox_tree_iter_deinit(&brain->local_tree_iter);
+ return -1;
+ }
+
+ if (brain->backup_send) {
+ /* make sure mailbox isn't modified */
+ flags |= MAILBOX_FLAG_READONLY;
+ }
+ box = mailbox_alloc(node->ns->list, vname, flags);
+ for (;;) {
+ if ((ret = dsync_box_get(box, &dsync_box, &error)) <= 0) {
+ if (ret < 0) {
+ brain->mail_error = error;
+ brain->failed = TRUE;
+ }
+ mailbox_free(&box);
+ file_lock_free(&lock);
+ return ret;
+ }
+
+ /* if mailbox's last_common_* state equals the current state,
+ we can skip the mailbox */
+ if (!dsync_brain_has_mailbox_state_changed(brain, &dsync_box)) {
+ if (brain->debug) {
+ i_debug("brain %c: Skipping mailbox %s with unchanged state "
+ "uidvalidity=%u uidnext=%u highestmodseq=%"PRIu64" highestpvtmodseq=%"PRIu64" messages=%u",
+ brain->master_brain ? 'M' : 'S',
+ guid_128_to_string(dsync_box.mailbox_guid),
+ dsync_box.uid_validity,
+ dsync_box.uid_next,
+ dsync_box.highest_modseq,
+ dsync_box.highest_pvt_modseq,
+ dsync_box.messages_count);
+ }
+ mailbox_free(&box);
+ file_lock_free(&lock);
+ return 0;
+ }
+ if (synced) {
+ /* ok, the mailbox really changed */
+ break;
+ }
+
+ /* mailbox appears to have changed. do a full sync here and get the
+ state again. Lock before syncing. */
+ if (dsync_mailbox_lock(brain, box, &lock) < 0) {
+ brain->failed = TRUE;
+ mailbox_free(&box);
+ return -1;
+ }
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
+ i_error("Can't sync mailbox %s: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, &brain->mail_error));
+ brain->failed = TRUE;
+ mailbox_free(&box);
+ file_lock_free(&lock);
+ return -1;
+ }
+ synced = TRUE;
+ }
+
+ *box_r = box;
+ *lock_r = lock;
+ *dsync_box_r = dsync_box;
+ return 1;
+}
+
+static bool
+dsync_brain_next_mailbox(struct dsync_brain *brain, struct mailbox **box_r,
+ struct file_lock **lock_r,
+ struct dsync_mailbox *dsync_box_r)
+{
+ int ret;
+
+ if (brain->no_mail_sync)
+ return FALSE;
+
+ while ((ret = dsync_brain_try_next_mailbox(brain, box_r, lock_r, dsync_box_r)) == 0)
+ ;
+ return ret > 0;
+}
+
+void dsync_brain_master_send_mailbox(struct dsync_brain *brain)
+{
+ struct dsync_mailbox dsync_box;
+ struct mailbox *box;
+ struct file_lock *lock;
+
+ i_assert(brain->master_brain);
+ i_assert(brain->box == NULL);
+
+ if (!dsync_brain_next_mailbox(brain, &box, &lock, &dsync_box)) {
+ brain->state = DSYNC_STATE_FINISH;
+ dsync_ibc_send_end_of_list(brain->ibc, DSYNC_IBC_EOL_MAILBOX);
+ return;
+ }
+
+ /* start exporting this mailbox (wait for remote to start importing) */
+ dsync_ibc_send_mailbox(brain->ibc, &dsync_box);
+ dsync_brain_sync_mailbox_init(brain, box, lock, &dsync_box, TRUE);
+ brain->state = DSYNC_STATE_SYNC_MAILS;
+}
+
+bool dsync_boxes_need_sync(struct dsync_brain *brain,
+ const struct dsync_mailbox *box1,
+ const struct dsync_mailbox *box2,
+ const char **reason_r)
+{
+ if (brain->no_mail_sync)
+ return FALSE;
+ if (brain->sync_type != DSYNC_BRAIN_SYNC_TYPE_CHANGED) {
+ *reason_r = "Full sync";
+ return TRUE;
+ }
+ if (box1->uid_validity != box2->uid_validity)
+ *reason_r = t_strdup_printf("UIDVALIDITY changed: %u -> %u",
+ box1->uid_validity, box2->uid_validity);
+ else if (box1->uid_next != box2->uid_next)
+ *reason_r = t_strdup_printf("UIDNEXT changed: %u -> %u",
+ box1->uid_next, box2->uid_next);
+ else if (box1->messages_count != box2->messages_count)
+ *reason_r = t_strdup_printf("Message count changed: %u -> %u",
+ box1->messages_count, box2->messages_count);
+ else if (box1->highest_modseq != box2->highest_modseq) {
+ *reason_r = t_strdup_printf("HIGHESTMODSEQ changed %"
+ PRIu64" -> %"PRIu64,
+ box1->highest_modseq,
+ box2->highest_modseq);
+ if (box1->highest_modseq == 0 ||
+ box2->highest_modseq == 0) {
+ *reason_r = t_strdup_printf(
+ "%s (Permanent MODSEQs aren't supported)",
+ *reason_r);
+ }
+ } else if (box1->highest_pvt_modseq != box2->highest_pvt_modseq)
+ *reason_r = t_strdup_printf("Private HIGHESTMODSEQ changed %"
+ PRIu64" -> %"PRIu64,
+ box1->highest_pvt_modseq,
+ box2->highest_pvt_modseq);
+ else if (box1->first_recent_uid != box2->first_recent_uid)
+ *reason_r = t_strdup_printf("First RECENT UID changed: %u -> %u",
+ box1->first_recent_uid, box2->first_recent_uid);
+ else
+ return FALSE;
+ return TRUE;
+}
+
+static int
+mailbox_cache_field_name_cmp(const struct mailbox_cache_field *f1,
+ const struct mailbox_cache_field *f2)
+{
+ return strcmp(f1->name, f2->name);
+}
+
+static void
+dsync_cache_fields_update(const struct dsync_mailbox *local_box,
+ const struct dsync_mailbox *remote_box,
+ struct mailbox *box,
+ struct mailbox_update *update)
+{
+ ARRAY_TYPE(mailbox_cache_field) local_sorted, remote_sorted, changes;
+ const struct mailbox_cache_field *local_fields, *remote_fields;
+ unsigned int li, ri, local_count, remote_count;
+ time_t drop_older_timestamp;
+ int ret;
+
+ if (array_count(&remote_box->cache_fields) == 0) {
+ /* remote has no cached fields. there's nothing to update. */
+ return;
+ }
+
+ t_array_init(&local_sorted, array_count(&local_box->cache_fields));
+ t_array_init(&remote_sorted, array_count(&remote_box->cache_fields));
+ array_append_array(&local_sorted, &local_box->cache_fields);
+ array_append_array(&remote_sorted, &remote_box->cache_fields);
+ array_sort(&local_sorted, mailbox_cache_field_name_cmp);
+ array_sort(&remote_sorted, mailbox_cache_field_name_cmp);
+
+ if (array_count(&local_sorted) == 0) {
+ /* local has no cached fields. set them to same as remote. */
+ array_append_zero(&remote_sorted);
+ update->cache_updates = array_front(&remote_sorted);
+ return;
+ }
+
+ /* figure out what to change */
+ local_fields = array_get(&local_sorted, &local_count);
+ remote_fields = array_get(&remote_sorted, &remote_count);
+ t_array_init(&changes, local_count + remote_count);
+ drop_older_timestamp = ioloop_time -
+ box->index->optimization_set.cache.unaccessed_field_drop_secs;
+
+ for (li = ri = 0; li < local_count || ri < remote_count; ) {
+ ret = li == local_count ? 1 :
+ ri == remote_count ? -1 :
+ strcmp(local_fields[li].name, remote_fields[ri].name);
+ if (ret == 0) {
+ /* field exists in both local and remote */
+ const struct mailbox_cache_field *lf = &local_fields[li];
+ const struct mailbox_cache_field *rf = &remote_fields[ri];
+
+ if (lf->last_used > rf->last_used ||
+ (lf->last_used == rf->last_used &&
+ lf->decision > rf->decision)) {
+ /* use local decision and timestamp */
+ } else {
+ array_push_back(&changes, rf);
+ }
+ li++; ri++;
+ } else if (ret < 0) {
+ /* remote field doesn't exist */
+ li++;
+ } else {
+ /* local field doesn't exist */
+ if (remote_fields[ri].last_used < drop_older_timestamp) {
+ /* field hasn't be used for a long time, remote
+ will probably drop this soon as well */
+ } else {
+ array_push_back(&changes, &remote_fields[ri]);
+ }
+ ri++;
+ }
+ }
+ i_assert(li == local_count && ri == remote_count);
+ if (array_count(&changes) > 0) {
+ array_append_zero(&changes);
+ update->cache_updates = array_front(&changes);
+ }
+}
+
+bool dsync_brain_mailbox_update_pre(struct dsync_brain *brain,
+ struct mailbox *box,
+ const struct dsync_mailbox *local_box,
+ const struct dsync_mailbox *remote_box,
+ const char **reason_r)
+{
+ struct mailbox_update update;
+ const struct dsync_mailbox_state *state;
+ bool ret = TRUE;
+
+ *reason_r = NULL;
+ i_zero(&update);
+
+ if (local_box->uid_validity != remote_box->uid_validity) {
+ /* Keep the UIDVALIDITY for the mailbox that has more
+ messages. If they equal, use the higher UIDVALIDITY. */
+ if (remote_box->messages_count > local_box->messages_count ||
+ (remote_box->messages_count == local_box->messages_count &&
+ remote_box->uid_validity > local_box->uid_validity))
+ update.uid_validity = remote_box->uid_validity;
+
+ state = dsync_mailbox_state_find(brain, local_box->mailbox_guid);
+ if (state != NULL && state->last_common_uid > 0) {
+ /* we can't continue syncing this mailbox in this
+ session, because the other side already started
+ sending mailbox changes, but not for all mails. */
+ dsync_mailbox_state_remove(brain, local_box->mailbox_guid);
+ *reason_r = "UIDVALIDITY changed during a stateful sync, need to restart";
+ brain->failed = TRUE;
+ ret = FALSE;
+ }
+ }
+
+ dsync_cache_fields_update(local_box, remote_box, box, &update);
+
+ if (update.uid_validity == 0 &&
+ update.cache_updates == NULL) {
+ /* no changes */
+ return ret;
+ }
+
+ if (mailbox_update(box, &update) < 0) {
+ i_error("Couldn't update mailbox %s metadata: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, &brain->mail_error));
+ brain->failed = TRUE;
+ }
+ return ret;
+}
+
+static void
+dsync_brain_slave_send_mailbox_lost(struct dsync_brain *brain,
+ const struct dsync_mailbox *dsync_box,
+ bool ignore)
+{
+ struct dsync_mailbox delete_box;
+
+ if (brain->debug) {
+ i_debug("brain %c: We don't have mailbox %s",
+ brain->master_brain ? 'M' : 'S',
+ guid_128_to_string(dsync_box->mailbox_guid));
+ }
+ i_zero(&delete_box);
+ memcpy(delete_box.mailbox_guid, dsync_box->mailbox_guid,
+ sizeof(delete_box.mailbox_guid));
+ t_array_init(&delete_box.cache_fields, 0);
+ if (ignore)
+ delete_box.mailbox_ignore = TRUE;
+ else
+ delete_box.mailbox_lost = TRUE;
+ dsync_ibc_send_mailbox(brain->ibc, &delete_box);
+}
+
+bool dsync_brain_slave_recv_mailbox(struct dsync_brain *brain)
+{
+ const struct dsync_mailbox *dsync_box;
+ struct dsync_mailbox local_dsync_box;
+ struct mailbox *box;
+ struct file_lock *lock;
+ const char *errstr, *resync_reason, *reason;
+ enum mail_error error;
+ int ret;
+ bool resync;
+
+ i_assert(!brain->master_brain);
+ i_assert(brain->box == NULL);
+
+ if ((ret = dsync_ibc_recv_mailbox(brain->ibc, &dsync_box)) == 0)
+ return FALSE;
+ if (ret < 0) {
+ brain->state = DSYNC_STATE_FINISH;
+ return TRUE;
+ }
+
+ if (dsync_brain_mailbox_alloc(brain, dsync_box->mailbox_guid,
+ &box, &errstr, &error) < 0) {
+ i_error("Couldn't allocate mailbox GUID %s: %s",
+ guid_128_to_string(dsync_box->mailbox_guid), errstr);
+ brain->mail_error = error;
+ brain->failed = TRUE;
+ return TRUE;
+ }
+ if (box == NULL) {
+ /* mailbox was probably deleted/renamed during sync */
+ if (brain->backup_send && brain->no_backup_overwrite) {
+ if (brain->debug) {
+ i_debug("brain %c: Ignore nonexistent "
+ "mailbox GUID %s with -1 sync",
+ brain->master_brain ? 'M' : 'S',
+ guid_128_to_string(dsync_box->mailbox_guid));
+ }
+ dsync_brain_slave_send_mailbox_lost(brain, dsync_box, TRUE);
+ return TRUE;
+ }
+ //FIXME: verify this from log, and if not log an error.
+ dsync_brain_set_changes_during_sync(brain, t_strdup_printf(
+ "Mailbox GUID %s was lost",
+ guid_128_to_string(dsync_box->mailbox_guid)));
+ dsync_brain_slave_send_mailbox_lost(brain, dsync_box, FALSE);
+ return TRUE;
+ }
+ /* Lock before syncing */
+ if (dsync_mailbox_lock(brain, box, &lock) < 0) {
+ mailbox_free(&box);
+ brain->failed = TRUE;
+ return TRUE;
+ }
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
+ i_error("Can't sync mailbox %s: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, &brain->mail_error));
+ file_lock_free(&lock);
+ mailbox_free(&box);
+ brain->failed = TRUE;
+ return TRUE;
+ }
+
+ if ((ret = dsync_box_get(box, &local_dsync_box, &error)) <= 0) {
+ file_lock_free(&lock);
+ mailbox_free(&box);
+ if (ret < 0) {
+ brain->mail_error = error;
+ brain->failed = TRUE;
+ return TRUE;
+ }
+ /* another process just deleted this mailbox? */
+ if (brain->debug) {
+ i_debug("brain %c: Skipping lost mailbox %s",
+ brain->master_brain ? 'M' : 'S',
+ guid_128_to_string(dsync_box->mailbox_guid));
+ }
+ dsync_brain_slave_send_mailbox_lost(brain, dsync_box, FALSE);
+ return TRUE;
+ }
+ i_assert(local_dsync_box.uid_validity != 0);
+ i_assert(memcmp(dsync_box->mailbox_guid, local_dsync_box.mailbox_guid,
+ sizeof(dsync_box->mailbox_guid)) == 0);
+
+ resync = !dsync_brain_mailbox_update_pre(brain, box, &local_dsync_box,
+ dsync_box, &resync_reason);
+
+ if (!dsync_boxes_need_sync(brain, &local_dsync_box, dsync_box, &reason)) {
+ /* no fields appear to have changed, skip this mailbox */
+ if (brain->debug) {
+ i_debug("brain %c: Skipping unchanged mailbox %s",
+ brain->master_brain ? 'M' : 'S',
+ guid_128_to_string(dsync_box->mailbox_guid));
+ }
+ dsync_ibc_send_mailbox(brain->ibc, &local_dsync_box);
+ file_lock_free(&lock);
+ mailbox_free(&box);
+ return TRUE;
+ }
+ if (brain->debug) {
+ i_debug("brain %c: Syncing mailbox %s: %s",
+ brain->master_brain ? 'M' : 'S',
+ guid_128_to_string(dsync_box->mailbox_guid), reason);
+ }
+
+ /* start export/import */
+ dsync_brain_sync_mailbox_init(brain, box, lock, &local_dsync_box, FALSE);
+ if ((ret = dsync_brain_sync_mailbox_open(brain, dsync_box)) < 0)
+ return TRUE;
+ if (resync)
+ dsync_brain_set_changes_during_sync(brain, resync_reason);
+ if (ret == 0 || resync) {
+ brain->require_full_resync = TRUE;
+ dsync_brain_sync_mailbox_deinit(brain);
+ dsync_brain_slave_send_mailbox_lost(brain, dsync_box, FALSE);
+ return TRUE;
+ }
+
+ dsync_ibc_send_mailbox(brain->ibc, &local_dsync_box);
+ brain->state = DSYNC_STATE_SYNC_MAILS;
+ return TRUE;
+}