dsync-mailbox-import.c revision 86bde2c1838d1ce967fa2b394bb952004a4adcb7
/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "hash.h"
#include "istream.h"
#include "seq-range-array.h"
#include "mail-storage-private.h"
#include "mail-search-build.h"
#include "dsync-transaction-log-scan.h"
#include "dsync-mail.h"
#include "dsync-mailbox-import.h"
struct importer_mail {
const char *guid;
};
struct importer_new_mail {
/* linked list of mails for this GUID */
struct importer_new_mail *next;
/* if non-NULL, this mail exists in both local and remote. this link
points to the other side. */
struct importer_new_mail *link;
const char *guid;
struct dsync_mail_change *change;
unsigned int uid_in_local:1;
unsigned int uid_is_usable:1;
unsigned int skip:1;
unsigned int copy_failed:1;
};
struct dsync_mailbox_importer {
struct mail_search_context *search_ctx;
const char *cur_guid;
/* UID => struct dsync_mail_change */
const struct hash_table *local_changes;
/* GUID => struct importer_new_mail */
struct hash_table *import_guids;
/* UID => struct importer_new_mail */
struct hash_table *import_uids;
unsigned int mail_request_idx;
unsigned int failed:1;
unsigned int last_common_uid_found:1;
unsigned int cur_uid_has_change:1;
unsigned int cur_mail_saved:1;
unsigned int local_expunged_guids_set:1;
unsigned int new_uids_assigned:1;
unsigned int want_mail_requests:1;
unsigned int mails_have_guids:1;
unsigned int master_brain:1;
};
static void
{
struct mail_search_args *search_args;
struct mail_search_arg *sarg;
0, NULL);
/* this flag causes cur_guid to be looked up later */
}
struct dsync_mailbox_importer *
struct dsync_transaction_log_scan *log_scan,
{
const enum mailbox_transaction_flags ext_trans_flags =
struct dsync_mailbox_importer *importer;
struct mailbox_status status;
10240);
last_common_uid != 0 || last_common_modseq != 0;
if ((flags & DSYNC_MAILBOX_IMPORT_FLAG_WANT_MAIL_REQUESTS) != 0) {
}
(flags & DSYNC_MAILBOX_IMPORT_FLAG_MASTER_BRAIN) != 0;
&status);
return importer;
}
{
const char *errstr;
enum mail_error error;
if (error == MAIL_ERROR_EXPUNGED)
return 0;
return -1;
}
static bool
{
/* end of search */
return FALSE;
}
if (!importer->cur_uid_has_change &&
/* this message exists locally, but remote didn't send
expunge-change for it. if the message's
uid <= last-common-uid, it should be deleted */
}
return FALSE;
}
}
}
/* make sure next_local_seq gets updated in case we came here
because of min_uid */
return TRUE;
}
static int
const struct importer_mail *m2)
{
int ret;
return 1;
return -1;
if (ret != 0)
return ret;
return -1;
return 1;
return 0;
}
struct importer_new_mail *newmail)
{
struct dsync_mail_request *request;
}
}
struct importer_new_mail *newmail)
{
if (first_mail == NULL) {
/* first mail for this GUID */
return;
}
} else {
if (!newmail->uid_in_local) {
/* FIXME: ? */
return;
}
if (first_mail == NULL) {
/* first mail for this UID */
return;
}
}
/* 1) add the newmail to the end of the linked list
2) find our link */
}
}
}
struct dsync_mail_change *save_change)
{
struct importer_new_mail *newmail;
int diff;
bool remote_saved;
}
if (save_change != NULL) {
}
if (diff < 0) {
/* add a record for local mail */
} else if (diff > 0) {
remote_saved = TRUE;
} else {
/* identical */
remote_saved = TRUE;
}
if (newmail->uid_in_local) {
} else {
/* NOTE: assumes save_change is allocated from importer pool */
}
return remote_saved;
}
static bool ATTR_NULL(2)
struct dsync_mail_change *save_change)
{
if (importer->cur_mail_saved) {
return FALSE;
}
}
struct dsync_mail_change *save_change)
{
}
static bool
const struct dsync_mail_change *change)
{
const char *guid;
return FALSE;
/* GUID is unknown */
return TRUE;
}
/* backend doesn't support GUIDs. if hdr_hash is set, we could
verify it, but since this message really is supposed to
match, it's probably too much trouble. */
return TRUE;
}
/* verify that GUID matches, just in case */
return FALSE;
}
i_error("Mailbox %s: Unexpected GUID mismatch for "
return FALSE;
}
return TRUE;
}
static void
bool prefer_remote,
{
/* resolve conflicts */
if (conflict_flags != 0) {
if (prefer_remote)
local_add &= ~conflict_flags;
else
}
if (conflict_flags != 0) {
if (prefer_remote)
else
remote_add &= ~conflict_flags;
}
/* see if there are conflicting final flags */
if (conflict_flags != 0) {
if (prefer_remote)
/*else
remote_wanted = local_wanted;*/
}
}
static bool
unsigned int *idx_r)
{
const char *const *names;
unsigned int i, count;
for (i = 0; i < count; i++) {
*idx_r = i;
return TRUE;
}
}
return FALSE;
}
{
const char *const *namep;
unsigned int i;
for (i = 0; i < 32; i++) {
if ((bits & (1U << i)) == 0)
continue;
}
}
static void
bool prefer_remote)
{
/* local_changes and remote_changes are assumed to have no
duplicates names */
struct mail_keywords *kw;
/* we'll assign a common index for each keyword name and place
the changes to separate bit arrays. */
else {
count = 0;
}
if (array_size == 0) {
/* this message has no keywords */
return;
}
/* @UNSAFE: create large enough arrays to fit all keyword indexes. */
/* get remote changes */
for (i = 0; i < count; i++) {
switch (changes[i][0]) {
case KEYWORD_CHANGE_ADD:
/* fall through */
case KEYWORD_CHANGE_FINAL:
break;
case KEYWORD_CHANGE_REMOVE:
break;
}
}
/* get local changes. use existing indexes for names when they exist. */
else {
count = 0;
}
for (i = 0; i < count; i++) {
}
switch (changes[i][0]) {
case KEYWORD_CHANGE_ADD:
break;
case KEYWORD_CHANGE_REMOVE:
break;
case KEYWORD_CHANGE_FINAL:
i_unreached();
}
}
for (i = 0; local_keywords[i] != NULL; i++) {
name = local_keywords[i];
}
}
/* merge keywords */
for (i = 0; i < array_size; i++) {
if (change_add[i] != 0) {
change_add[i], i*32);
}
if (change_remove[i] != 0) {
change_add[i], i*32);
}
}
/* apply changes */
if (array_count(&add_keywords) > 0) {
(void)array_append_space(&add_keywords);
array_idx(&add_keywords, 0));
}
if (array_count(&remove_keywords) > 0) {
(void)array_append_space(&remove_keywords);
array_idx(&remove_keywords, 0));
}
}
static void
const struct dsync_mail_change *change)
{
const struct dsync_mail_change *local_change;
bool prefer_remote;
else {
return;
}
if (local_change == NULL) {
local_add = local_remove = 0;
} else {
}
else {
/* identical modseq, we'll just have to pick one.
Note that both brains need to pick the same one, otherwise
they become unsynced. */
}
/* merge flags */
if (change_add != 0)
if (change_remove != 0)
/* merge keywords */
}
static void
const struct dsync_mail_change *change)
{
struct dsync_mail_change *save;
/* we've already verified that the GUID matches.
apply flag changes if there are any. */
return;
}
if (importer->last_common_uid_found) {
/* this is a new mail. its UID may or may not conflict with
an existing local mail, we'll figure it out later. */
} else {
/* the local mail is expunged. we'll decide later if we want
to save this mail locally or expunge it form remote. */
}
}
static void
const struct dsync_mail_change *change)
{
if (importer->last_common_uid_found) {
/* expunge the message, unless its GUID unexpectedly doesn't
match */
/* already expunged locally, we can ignore this.
uid=last_common_uid if we managed to verify from
transaction log that the GUIDs match */
/* already verified that the GUID matches */
} else {
/* we don't know yet if we should expunge this
message or not. queue it until we do. */
}
}
static void
{
/* If there are local mails after last_common_uid which we skipped
while trying to match the next message, we need to now go back */
return;
importer->next_local_seq = 0;
}
static void
{
struct dsync_mail_change *const *saves;
struct seq_range_iter iter;
unsigned int n, i, count;
/* expunge the messages whose expunge-decision we delayed previously */
/* we expunge messages only up to last_common_uid,
ignore the rest */
break;
}
}
/* handle pending saves */
for (i = 0; i < count; i++) {
}
}
static int
const struct dsync_mail_change *change)
{
const char *hdr_hash;
/* we have GUIDs, verify them */
}
/* verify hdr_hash if it exists */
i_error("Mailbox %s: GUIDs not supported, "
"sync with header hashes instead",
return -1;
}
return -1;
}
}
static void
const struct dsync_mail_change *change)
{
const struct dsync_mail_change *local_change;
int ret;
/* try to find the matching local mail */
/* no more local mails. use the last message with a matching
GUID as the last common UID. */
return;
}
/* we can't know if this UID matches */
return;
}
/* we have a matching local UID. check GUID to see if it's
really the same mail or not */
/* unknown */
return;
}
if (ret == 0) {
/* mismatch - found the first non-common UID */
} else {
}
return;
}
/* remote doesn't support GUIDs, can't verify expunge */
return;
}
/* local message is expunged. see if we can find its GUID from
transaction log and check if the GUIDs match. The GUID in
log is a 128bit GUID, so we may need to convert the remote's
GUID string to 128bit GUID first. */
return;
i_unreached();
/* mismatch - found the first non-common UID */
} else {
}
return;
}
const struct dsync_mail_change *change)
{
if (!importer->last_common_uid_found)
if (importer->last_common_uid_found) {
/* a) uid <= last_common_uid for flag changes and expunges.
this happens only when last_common_uid was originally given
as parameter to importer.
when we're finding the last_common_uid ourself,
uid>last_common_uid always in here, because
last_common_uid_found=TRUE only after we find the first
mismatch.
b) uid > last_common_uid for i) new messages, ii) expunges
that were sent "just in case" */
/* ignore */
return;
} else {
}
} else {
/* a) uid < last_common_uid can never happen */
/* b) uid = last_common_uid if we've verified that the
messages' GUIDs match so far.
c) uid > last_common_uid: i) TYPE_EXPUNGE change has
GUID=NULL, so we couldn't verify yet if it matches our
local message, ii) local message is expunged and we couldn't
find its GUID */
}
}
break;
break;
break;
}
}
static void
{
struct mail_save_context *save_ctx;
return;
}
}
static void
{
/* already assigned */
if (newmail->uid_in_local) {
}
continue;
}
/* figure out what UID to use for the mail */
if (newmail->uid_is_usable) {
/* keep the UID */
else
new_uid = common_uid_next++;
/* local UID changed, reassign it by copying */
}
/* skip the linked mail */
}
}
}
{
if (!importer->last_common_uid_found) {
/* handle pending expunges and flag updates */
}
/* skip common local mails */
/* if there are any local mails left, add them to newmails list */
}
const struct dsync_mail_request *
{
const struct dsync_mail_request *requests;
unsigned int count;
return NULL;
}
static const char *const *
{
const char *const *changes;
unsigned int i, count;
return NULL;
for (i = 0; i < count; i++) {
if (changes[i][0] == KEYWORD_CHANGE_ADD ||
changes[i][0] == KEYWORD_CHANGE_FINAL) {
}
}
if (array_count(&keywords) == 0)
return NULL;
(void)array_append_space(&keywords);
}
static void
struct mail_save_context *save_ctx,
const struct dsync_mail_change *change)
{
const char *const *keyword_names;
struct mail_keywords *keywords;
}
}
static int
struct mail_save_context **save_ctx_p,
struct importer_new_mail *all_newmails)
{
struct importer_new_mail *inst;
return -1;
}
return 1;
}
}
return 0;
}
static struct mail_save_context *
const struct dsync_mail *mail,
struct importer_new_mail *newmail)
{
struct mail_save_context *save_ctx;
if (mail->pop3_order > 0)
return save_ctx;
}
const struct dsync_mail *mail,
struct importer_new_mail *newmail,
struct importer_new_mail *all_newmails)
{
struct mail_save_context *save_ctx;
bool save_failed = FALSE;
/* try to save the mail by copying an existing mail */
}
if (ret > 0) {
return;
}
/* fallback to saving from remote stream */
/* it was just expunged in remote, skip it */
return;
}
i_error("Can't save message to mailbox %s: %s",
return;
}
if (mailbox_save_continue(save_ctx) < 0) {
save_failed = TRUE;
ret = -1;
break;
}
}
i_error("read(msg input) failed: %m");
} else if (save_failed) {
} else {
if (mailbox_save_finish(&save_ctx) < 0) {
i_error("Can't save message to mailbox %s: %s",
} else {
}
}
}
const struct dsync_mail *mail)
{
if (importer->want_mail_requests) {
i_error("%s: Remote sent unwanted message body for "
"GUID=%s UID=%u",
}
return;
}
else {
}
/* save all instances of the message */
/* no need to do anything for this mail */
continue;
}
if (newmail->uid_in_local) {
/* we already handled this by copying the mail */
continue;
}
T_BEGIN {
allmails);
} T_END;
}
}
static int
{
const enum mailbox_transaction_flags trans_flags =
struct mailbox_transaction_context *trans;
struct mail_save_context *save_ctx;
int ret = 0;
ret = -1;
else
}
if (mailbox_transaction_commit(&trans) < 0) {
i_error("UID reassign commit failed to mailbox %s: %s",
ret = -1;
}
return ret;
}
static bool
const struct mail_transaction_commit_changes *changes,
bool *changes_during_sync_r)
{
struct seq_range_iter iter;
const uint32_t *wanted_uids;
unsigned int i, n, wanted_count;
int ret = 0;
/* find the highest wanted UID that doesn't match what we got */
i_assert(i < wanted_count);
if (lowest_saved_uid > saved_uid)
if (saved_uid != wanted_uids[i]) {
if (highest_unwanted_uid < wanted_uids[i])
}
i++;
}
if (highest_unwanted_uid == 0 && i > 0 &&
/* we didn't see any unwanted UIDs, but we'll still need to
verify that messages didn't just get saved locally to a gap
that we left in local_uid_next..(lowest_saved_uid-1) */
}
if (highest_unwanted_uid == 0)
else {
}
if (seq1 > 0) {
}
return ret;
}
bool *changes_during_sync_r)
{
struct mailbox_update update;
int ret = 0;
/* commit saves */
&changes) < 0) {
i_error("Save commit failed to mailbox %s: %s",
return -1;
}
/* commit flag changes and expunges */
i_error("Commit failed to mailbox %s: %s",
return -1;
}
/* update mailbox metadata. */
i_error("Mailbox update failed to mailbox %s: %s",
ret = -1;
}
/* sync mailbox to finish flag changes and expunges. */
i_error("Mailbox sync failed to mailbox %s: %s",
ret = -1;
}
changes_during_sync_r) < 0)
ret = -1;
return ret;
}
static unsigned int
{
struct hash_iterate_context *iter;
unsigned int msgs_left = 0;
if (!mail->uid_in_local) {
msgs_left++;
break;
}
}
}
return msgs_left;
}
bool *changes_during_sync_r)
{
unsigned int msgs_left;
int ret;
if (!importer->new_uids_assigned)
i_error("%s: Remote didn't send %u expected message bodies",
}
if (!*changes_during_sync_r)
else {
/* local changes occurred during dsync. we exported changes up
to local_initial_highestmodseq, so all of the changes have
happened after it. we want the next run to see those changes,
so return it as the last common modseq */
}
return ret;
}