dsync-mailbox-import.c revision 02c335c23bf5fa225a467c19f2c063fb0dc7b8c3
/* Copyright (c) 2013-2016 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "hash.h"
#include "str.h"
#include "hex-binary.h"
#include "istream.h"
#include "seq-range-array.h"
#include "imap-util.h"
#include "mail-storage-private.h"
#include "mail-search-build.h"
#include "dsync-transaction-log-scan.h"
#include "dsync-mail.h"
#include "dsync-mailbox.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;
/* the final UID for the message */
/* the original local UID, or 0 if exists only remotely */
/* the original remote UID, or 0 if exists only remotely */
/* UID for the mail in the virtual \All mailbox */
unsigned int uid_in_local:1;
unsigned int uid_is_usable:1;
unsigned int skip:1;
unsigned int expunged:1;
unsigned int copy_failed:1;
};
/* for quickly testing that two-way sync doesn't actually do any unexpected
modifications. */
struct dsync_mailbox_importer {
enum mail_flags sync_flag;
const char *sync_keyword;
bool sync_flag_dontwant;
struct mail_search_context *search_ctx;
struct mailbox *virtual_all_box;
struct mailbox_transaction_context *virtual_trans;
struct mail *virtual_mail;
const char *cur_guid;
const char *cur_hdr_hash;
/* UID => struct dsync_mail_change */
/* GUID => struct importer_new_mail */
/* UID => struct importer_new_mail */
unsigned int mail_request_idx;
unsigned int import_pos, import_count;
enum mail_error mail_error;
unsigned int failed:1;
unsigned int debug:1;
unsigned int stateful_import:1;
unsigned int last_common_uid_found:1;
unsigned int cur_uid_has_change:1;
unsigned int cur_mail_skip:1;
unsigned int local_expunged_guids_set:1;
unsigned int new_uids_assigned:1;
unsigned int want_mail_requests:1;
unsigned int master_brain:1;
unsigned int revert_local_changes:1;
unsigned int mails_have_guids:1;
unsigned int mails_use_guid128:1;
unsigned int delete_mailbox:1;
};
const struct dsync_mail *mail,
struct importer_new_mail *all_newmails,
bool remote_mail);
bool final);
{
i_debug("brain %c: Import %s: %s",
} T_END;
}
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 */
}
static void
{
const enum mailbox_transaction_flags ext_trans_flags =
}
struct dsync_mailbox_importer *
struct mailbox *virtual_all_box,
struct dsync_transaction_log_scan *log_scan,
{
struct dsync_mailbox_importer *importer;
struct mailbox_status status;
10240);
last_common_uid != 0 || last_common_modseq != 0;
if (sync_flag[0] == '-') {
sync_flag++;
}
if (sync_flag[0] == '\\')
else
}
if ((flags & DSYNC_MAILBOX_IMPORT_FLAG_NO_NOTIFY) != 0)
if ((flags & DSYNC_MAILBOX_IMPORT_FLAG_WANT_MAIL_REQUESTS) != 0) {
}
(flags & DSYNC_MAILBOX_IMPORT_FLAG_MASTER_BRAIN) != 0;
&status);
return importer;
}
static int
struct dsync_mailbox_attribute **attr_r)
{
const struct dsync_mailbox_attribute *attr_change;
struct mail_attribute_value value;
i_error("Mailbox %s: Failed to get attribute %s: %s",
return -1;
}
&lookup_attr);
if (attr_change == NULL &&
/* we have no knowledge of this attribute */
return 0;
}
if (attr_change != NULL) {
}
return 0;
}
static int
{
for (;;) {
break;
if (*cmp_r != 0)
return 0;
}
if (input1->stream_errno != 0) {
return -1;
}
if (input2->stream_errno != 0) {
return -1;
}
*cmp_r = 0;
else
return 0;
}
static int
const struct dsync_mailbox_attribute *attr2,
int *cmp_r)
{
int ret;
return 0;
}
/* at least one of them is a stream. make both of them streams. */
i_stream_seek(input1, 0);
i_stream_seek(input2, 0);
return ret;
}
static int
const struct dsync_mailbox_attribute *local_attr,
int *cmp_r)
{
if (DSYNC_ATTR_HAS_VALUE(attr) &&
/* remote has a value and local doesn't -> use it */
*cmp_r = 1;
return 0;
} else if (!DSYNC_ATTR_HAS_VALUE(attr) &&
/* remote doesn't have a value, bt local does -> skip */
*cmp_r = -1;
return 0;
}
}
static int
const struct dsync_mailbox_attribute *attr,
const struct dsync_mailbox_attribute *local_attr,
const char **result_r)
{
struct mail_attribute_value value;
int cmp;
/* attribute doesn't exist on either side -> ignore */
*result_r = "Nonexistent in both sides";
return 0;
}
if (local_attr == NULL) {
/* we haven't seen this locally -> use whatever remote has */
*result_r = "Nonexistent locally";
importer->last_common_modseq > 0) {
/* we're doing incremental syncing, and we can see that the
attribute was changed remotely, but not locally -> use it */
*result_r = "Changed remotely";
importer->last_common_modseq > 0) {
/* we're doing incremental syncing, and we can see that the
attribute was changed locally, but not remotely -> ignore */
*result_r = "Changed locally";
/* remote has a newer timestamp -> use it */
*result_r = "Remote has newer timestamp";
/* remote has an older timestamp -> ignore */
*result_r = "Local has newer timestamp";
} else {
/* the timestamps are the same. now we're down to guessing
the right answer, unless the values are actually equal,
so check that first. next try to use modseqs, but if even
they are the same, fallback to just picking one based on the
value. */
return -1;
}
if (cmp == 0) {
/* identical scripts */
*result_r = "Unchanged value";
return 0;
}
/* remote has a higher modseq -> use it */
*result_r = "Remote has newer modseq";
/* remote has an older modseq -> ignore */
*result_r = "Local has newer modseq";
} else if (cmp < 0) {
*result_r = "Value changed, but unknown which is newer - picking local";
} else {
*result_r = "Value changed, but unknown which is newer - picking remote";
}
}
if (ignore)
return 0;
i_error("Mailbox %s: Failed to set attribute %s: %s",
/* the attributes aren't vital, don't fail everything just
because of them. */
}
return 0;
}
const struct dsync_mailbox_attribute *attr)
{
struct dsync_mailbox_attribute *local_attr;
const char *result = "";
int ret;
ret = -1;
else {
local_attr, &result);
}
return ret;
}
{
const char *errstr;
enum mail_error error;
if (error == MAIL_ERROR_EXPUNGED)
return;
i_error("Mailbox %s: Can't lookup %s for UID=%u: %s",
}
static bool
const struct dsync_mail_change *change,
const char *guid, const char **cmp_guid_r)
{
i_unreached();
} else if (importer->mails_use_guid128) {
} else {
if (cmp_guid_r != NULL)
}
if (cmp_guid_r != NULL) {
}
return FALSE;
}
return TRUE;
}
static int
{
struct mail_private *pmail;
const char *hdr_hash;
/* end of search */
return -1;
}
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 -1;
}
}
if (importer->mails_have_guids) {
return 0;
}
} else {
&hdr_hash) < 0) {
"header hash");
return 0;
}
}
/* make sure next_local_seq gets updated in case we came here
because of min_uid */
return 1;
}
static bool
{
int ret;
for (;;) {
T_BEGIN {
} T_END;
break;
}
return ret > 0;
}
static int
const struct importer_mail *m2)
{
int ret;
return 1;
return -1;
if (ret != 0)
return ret;
return -1;
return 1;
return 0;
}
{
if (first_mail == NULL) {
/* first mail for this GUID */
return;
}
} else {
if (remote_uid == 0) {
/* mail exists only locally. we don't want to request
it, and we'll assume it has no duplicate
instances. */
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
FIXME: this loop is slow if the same GUID has a ton of instances.
Could it be improved in some way? */
}
}
}
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 */
if (importer->revert_local_changes) {
return FALSE;
}
} else if (diff > 0) {
remote_saved = TRUE;
} else {
/* identical */
remote_saved = TRUE;
}
if (newmail->uid_in_local) {
}
/* 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_skip) {
return FALSE;
}
}
struct dsync_mail_change *save_change)
{
}
static void
const char *error)
{
if (!importer->stateful_import) {
error);
} else {
i_warning("Mailbox %s doesn't match previous state: %s "
"(dsync must be run again without the state)",
}
}
static bool
const struct dsync_mail_change *change)
{
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;
}
"Unexpected GUID mismatch for UID=%u: %s != %s",
return FALSE;
}
return TRUE;
}
const struct dsync_mail_change *change)
{
const char *cmp_guid;
return TRUE;
"Unexpected GUID mismatch (2) for UID=%u: %s != %s",
return FALSE;
}
return TRUE;
}
static void
bool *remote_changed, bool *remote_pvt_changed)
{
/* resolve conflicts */
if (conflict_flags != 0) {
conflict_flags &= ~pvt_mask;
if (prefer_remote)
local_add &= ~conflict_flags;
else
if (prefer_pvt_remote)
else
}
if (conflict_flags != 0) {
conflict_flags &= ~pvt_mask;
if (prefer_remote)
else
remote_add &= ~conflict_flags;
if (prefer_pvt_remote)
else
}
/* don't change flags that are currently identical in both sides */
/* see if there are conflicting final flags */
if (conflict_flags != 0) {
if (prefer_remote && prefer_pvt_remote)
else if (prefer_remote && !prefer_pvt_remote) {
(remote_wanted & ~pvt_mask);
} else if (!prefer_remote && prefer_pvt_remote) {
(remote_wanted & pvt_mask);
}
}
*remote_changed = TRUE;
}
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,
bool *remote_changed, bool *remote_pvt_changed)
{
/* 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:
break;
case KEYWORD_CHANGE_REMOVE:
break;
case KEYWORD_CHANGE_FINAL:
break;
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:
break;
}
}
for (i = 0; local_keywords[i] != NULL; i++) {
name = local_keywords[i];
}
}
/* merge keywords */
for (i = 0; i < array_size; i++) {
&change_add[i], &change_remove[i],
if (change_add[i] != 0) {
change_add[i], i*32);
}
if (change_remove[i] != 0) {
change_remove[i], i*32);
}
}
/* apply changes */
if (array_count(&add_keywords) > 0) {
array_idx(&add_keywords, 0));
}
if (array_count(&remove_keywords) > 0) {
array_idx(&remove_keywords, 0));
}
}
static void
const struct dsync_mail_change *change)
{
struct mail_keywords *kw;
unsigned int i, count;
else {
count = 0;
}
for (i = 0; i < count; i++) {
switch (changes[i][0]) {
case KEYWORD_CHANGE_ADD:
case KEYWORD_CHANGE_FINAL:
break;
case KEYWORD_CHANGE_REMOVE:
break;
}
}
}
static void
const struct dsync_mail_change *change)
{
const struct dsync_mail_change *local_change;
bool prefer_remote, prefer_pvt_remote;
return;
} else {
return;
}
if (importer->revert_local_changes) {
/* dsync backup: just make the local look like remote. */
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. */
}
else
/* merge flags */
if (change_add != 0)
if (change_remove != 0)
/* merge keywords */
/* update modseqs. try to anticipate when we have to increase modseq
to get it closer to what remote has (although we can't guess it
exactly correctly) */
}
static bool
const char *keyword)
{
const char *const *strp;
return FALSE;
switch ((*strp)[0]) {
case KEYWORD_CHANGE_FINAL:
return TRUE;
break;
default:
break;
}
}
return FALSE;
}
static bool
const struct dsync_mail_change *change,
const char **result_r)
{
if (importer->sync_since_timestamp > 0) {
/* mail has too old timestamp - skip it */
*result_r = "Ignoring missing local mail with too old timestamp";
return FALSE;
}
}
*result_r = "Ignoring missing local mail that doesn't have wanted flags";
return FALSE;
}
*result_r = "Ignoring missing local mail that has unwanted flags";
return FALSE;
}
}
*result_r = "Ignoring missing local mail that doesn't have wanted keywords";
return FALSE;
}
*result_r = "Ignoring missing local mail that has unwanted keywords";
return FALSE;
}
}
return TRUE;
}
static void
const struct dsync_mail_change *change)
{
struct dsync_mail_change *save;
const char *result;
/* we've already verified that the GUID matches.
apply flag changes if there are any. */
return;
}
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;
} T_END;
/* 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++) {
} else {
}
}
}
static int
const struct dsync_mail_change *change,
const char **result_r)
{
/* we have GUIDs, verify them */
*result_r = "GUIDs match";
return 1;
} else {
return 0;
}
}
/* verify hdr_hash if it exists */
/* the message was already expunged, so we don't know
its header. return "unknown". */
*result_r = "Unknown match for expunge";
return -1;
}
i_error("Mailbox %s: GUIDs not supported, "
"sync with header hashes instead",
*result_r = "Error, invalid parameters";
return -1;
}
*result_r = "Error fetching header stream";
return -1;
}
*result_r = "Headers hashes match";
return 1;
} else {
return 0;
}
}
static bool
const struct dsync_mail_change *change,
const char **result_r)
{
const struct dsync_mail_change *local_change;
/* remote doesn't support GUIDs, can't verify expunge */
*result_r = "GUIDs not supported, can't verify expunge";
return FALSE;
}
/* 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. */
*result_r = "Expunged local mail's GUID not found";
return FALSE;
}
*result_r = "Expunged local mail's GUID matches remote";
*result_r = "Expunged local mail's GUID doesn't match remote GUID";
} else {
/* GUID mismatch for two expunged mails. dsync can't update
GUIDs for already expunged messages, so we can't immediately
determine that the rest of the messages are a mismatch. so
for now we'll just skip over this pair. */
*result_r = "Expunged mails' GUIDs don't match - delaying decision";
/* NOTE: the return value here doesn't matter, because the only
caller that checks for it never reaches this code path */
}
return TRUE;
}
static void
const struct dsync_mail_change *change)
{
/* mail exists on remote, but not locally. we'll need to
insert this mail back, which means deleting the whole
mailbox and resyncing. */
i_warning("Deleting mailbox '%s': UID=%u GUID=%s is missing locally",
}
static void
const struct dsync_mail_change *change,
const char **result_r)
{
int ret;
change->received_timestamp > 0 ||
/* try to find the matching local mail */
/* no more local mails. we can still try to match
expunged mails though. */
/* mail doesn't exist remotely either, don't bother
looking it up locally. */
*result_r = "Expunged mail not found locally";
return;
}
;
*result_r = "Mail's UID is above local UIDNEXT";
} else if (importer->revert_local_changes) {
*result_r = "Reverting local change by deleting mailbox";
/* it's unknown if this mail existed locally and was
expunged. since we don't want to lose any mails,
assume that we need to preserve the mail. use the
last message with a matching GUID as the last common
UID. */
}
return;
}
/* we can't know if this UID matches */
*result_r = "Expunged mail has no GUID, can't verify it";
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;
}
/* mail exists remotely, but doesn't exist locally. */
return;
if (importer->revert_local_changes &&
*result_r = "Reverting local change by deleting mailbox";
} else {
}
}
const struct dsync_mail_change *change)
{
const char *result;
return -1;
if (!importer->last_common_uid_found) {
} else {
result = "New mail";
}
return -1;
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 0;
} 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
{
/* already assigned */
continue;
}
/* figure out what UID to use for the mail */
if (newmail->uid_is_usable) {
/* keep the UID */
/* we can use the linked message's UID and expunge
this mail */
} else {
new_uid = common_uid_next++;
}
/* skip processing the linked mail */
}
}
}
static int
struct dsync_mail *dmail_r)
{
const char *error_field, *errstr;
enum mail_error error;
return 0;
/* NOTE: Errors are logged, but they don't cause the entire import
to fail. */
if (error == MAIL_ERROR_EXPUNGED)
return 0;
i_error("Mailbox %s: Can't lookup %s for UID=%u: %s",
return -1;
}
"Unexpected GUID mismatch (3) for UID=%u: %s != %s",
return -1;
}
return 1;
}
static void
{
}
static bool
{
struct mail_save_context *save_ctx;
return FALSE;
return FALSE;
return TRUE;
}
static bool
{
unsigned int count, n;
struct seq_range_iter iter;
/* optimize by first trying to use the latest UID */
if (count == 0)
return FALSE;
return TRUE;
}
/* now try to use any of them by iterating through them. (would be
easier&faster to just iterate backwards, but probably too much
trouble to add such API) */
return TRUE;
}
}
return FALSE;
}
static bool
struct importer_new_mail *all_newmails,
{
struct importer_new_mail *mail;
struct dsync_mail dmail;
if (array_count(local_uids) == 0)
return FALSE;
/* wanted_uids contains UIDs that need to exist at the end. those that
don't already exist in local_uids have a higher UID than any
existing local UID */
&wanted_uid)) {
if (local_uid == wanted_uid) {
/* we have exactly the UID we want. keep it. */
wanted_n++;
continue;
}
}
/* we no longer want this local UID. */
}
/* reuse as many existing messages as possible by changing their UIDs */
break;
wanted_n++;
}
/* expunge all unwanted messages */
}
/* mark mails whose UIDs we got to be skipped over later */
}
/* we've assigned all wanted UIDs */
return TRUE;
}
/* try to find one existing message that we can use to copy to the
other instances */
&dmail) > 0) {
return TRUE;
}
}
return FALSE;
}
static bool
struct importer_new_mail *all_newmails)
{
struct dsync_mail dmail;
if (all_newmails->virtual_all_uid == 0)
return FALSE;
return TRUE;
}
return FALSE;
}
static bool
struct importer_new_mail *all_newmails)
{
struct dsync_mail_request *request;
struct importer_new_mail *mail;
const char *request_guid = NULL;
uint32_t request_uid = 0;
/* get the list of the current local UIDs and the wanted UIDs.
find the first remote instance that we can request in case there are
no local instances */
if (mail->uid_in_local)
else if (request_guid == NULL) {
i_assert(request_uid != 0);
}
}
&local_uids, &wanted_uids) &&
/* no local instance. request from remote */
if (importer->want_mail_requests) {
}
return FALSE;
}
/* successfully handled all the mails locally */
importer->import_pos++;
return TRUE;
}
static void
{
struct mail_search_context *search_ctx;
struct mail_search_args *search_args;
struct importer_new_mail *newmail;
const char *guid;
i_error("Couldn't sync \\All mailbox '%s': %s",
return;
}
/* ignore errors */
continue;
}
}
if (mailbox_search_deinit(&search_ctx) < 0) {
i_error("Couldn't search \\All mailbox '%s': %s",
}
}
static void
{
struct hash_iterate_context *iter;
const char *key;
void *key2;
struct importer_new_mail *mail;
/* find UIDs in \All mailbox for all wanted GUIDs. */
}
T_BEGIN {
} T_END;
}
T_BEGIN {
} T_END;
}
}
{
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 */
i_error("Mailbox %s: Search failed: %s",
&importer->mail_error));
}
}
/* save mails from local sources where possible,
request the rest from remote */
}
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_ADD_AND_FINAL) {
}
}
if (array_count(&keywords) == 0)
return NULL;
}
static void
struct mail_save_context *save_ctx,
const struct dsync_mail_change *change)
{
const char *const *keyword_names;
struct mail_keywords *keywords;
}
/* FIXME: if there already are private flags, they get lost because
saving can't handle updating private index. they get added on the
next sync though. if this is fixed here, set min_pvt_modseq also. */
}
static int
struct mail_save_context **save_ctx_p,
struct importer_new_mail **all_newmails_forcopy)
{
struct importer_new_mail *inst;
return -1;
}
return 1;
}
}
return 0;
}
static void
const struct dsync_mail *mail)
{
if (mail->pop3_order > 0)
}
static struct mail_save_context *
const struct dsync_mail *mail,
struct importer_new_mail *newmail)
{
struct mail_save_context *save_ctx;
if (mail->saved_date != 0)
if (!mail->minimal_fields)
return save_ctx;
}
static bool
const struct dsync_mail *mail,
struct importer_new_mail *newmail,
struct importer_new_mail **all_newmails_forcopy,
bool remote_mail)
{
struct mail_save_context *save_ctx;
bool save_failed = FALSE;
/* try to save the mail by copying an existing mail */
}
/* copy using the source mail */
ret = 1;
else {
ret = -1;
}
}
if (ret > 0) {
return TRUE;
}
/* fallback to saving from remote stream */
if (!remote_mail) {
/* the mail isn't remote yet. we were just trying to copy a
local mail to avoid downloading the remote mail. */
return FALSE;
}
if (mail->minimal_fields) {
struct dsync_mail mail2;
const char *error_field;
&error_field) < 0) {
i_error("Mailbox %s: Failed to read mail %s uid=%u: %s",
&importer->mail_error));
return TRUE;
}
} else {
}
/* it was just expunged in remote, skip it */
return TRUE;
}
i_stream_seek(input, 0);
i_error("Mailbox %s: Saving failed: %s",
&importer->mail_error));
return TRUE;
}
if (mailbox_save_continue(save_ctx) < 0) {
save_failed = TRUE;
ret = -1;
break;
}
}
if (input->stream_errno != 0) {
i_error("Mailbox %s: read(msg input) failed: %s",
} else if (save_failed) {
i_error("Mailbox %s: Saving failed: %s",
&importer->mail_error));
} else {
if (mailbox_save_finish(&save_ctx) < 0) {
i_error("Mailbox %s: Saving failed: %s",
&importer->mail_error));
} else {
}
}
return TRUE;
}
const struct dsync_mail *mail,
struct importer_new_mail *all_newmails,
bool remote_mail)
{
/* if all_newmails list is large, avoid scanning through the
uninteresting ones for each newmail */
/* save all instances of the message */
} T_END;
}
return ret;
}
const struct dsync_mail *mail)
{
struct importer_new_mail *all_newmails;
return;
if (all_newmails == NULL) {
if (importer->want_mail_requests) {
i_error("Mailbox %s: Remote sent unwanted message body for "
"GUID=%s UID=%u",
} else {
}
return;
}
else {
}
importer->import_pos++;
i_unreached();
}
static int
{
const enum mailbox_transaction_flags trans_flags =
struct mailbox_transaction_context *trans;
struct mail_search_args *search_args;
struct mail_search_arg *arg;
struct mail_search_context *search_ctx;
struct mail_save_context *save_ctx;
unsigned int renumber_count = 0;
int ret = 1;
if (array_count(unwanted_uids) == 0)
return 1;
} T_END;
i_error("Mailbox %s: Couldn't move mail within mailbox: %s",
ret = -1;
} else if (ret > 0) {
ret = 0;
}
}
if (mailbox_search_deinit(&search_ctx) < 0) {
i_error("Mailbox %s: mail search failed: %s",
ret = -1;
}
if (mailbox_transaction_commit(&trans) < 0) {
i_error("Mailbox %s: UID reassign commit failed: %s",
ret = -1;
}
if (ret == 0) {
"Renumbered %u of %u unwanted UIDs",
}
return ret;
}
static int
bool *changes_during_sync_r)
{
unsigned int i, wanted_count, saved_count;
int ret = 0;
if (wanted_count == 0)
return 0;
/* wanted_uids contains the UIDs we tried to save mails with.
if nothing changed during dsync, we should have the expected UIDs
(saved_uids) and all is well.
if any new messages got inserted during dsync, we'll need to fix up
the UIDs and let the next dsync fix up the other side. for example:
remote uids = 5,7,9 = wanted_uids
remote uidnext = 12
locally added new uid=5 ->
saved_uids = 10,7,9
we'll now need to reassign UIDs 5 and 10. to be fully future-proof
we'll reassign all UIDs between [original local uidnext .. highest
UID we think we know] that aren't in saved_uids. */
/* create uidset for the list of UIDs we don't want to exist */
for (i = 0; i < wanted_count; i++) {
i_assert(i < wanted_count);
if (saved_uids[i] == wanted_uids[i])
}
if (ret == 0) {
/* conflicting changes during sync, revert our last-common-uid
back to a safe value. */
}
return ret < 0 ? -1 : 0;
}
static int
{
struct seq_range_iter iter;
unsigned int n;
/* commit saves */
&changes) < 0) {
i_error("Mailbox %s: Save commit failed: %s",
/* removed wanted_uids that weren't actually saved */
ret = -1;
} else {
/* remember the UIDs that were successfully saved */
} T_END;
/* commit flag changes and expunges */
i_error("Mailbox %s: Commit failed: %s",
&importer->mail_error));
ret = -1;
}
}
if (!final)
return ret;
}
bool *changes_during_sync_r)
{
struct mailbox_update update;
int ret;
if (ret == 0) {
/* update mailbox metadata if we successfully saved
everything. */
"min_first_recent_uid=%u min_highest_modseq=%llu "
"min_highest_pvt_modseq=%llu",
(unsigned long long)update.min_highest_modseq,
(unsigned long long)update.min_highest_pvt_modseq);
i_error("Mailbox %s: Update failed: %s",
&importer->mail_error));
ret = -1;
}
}
/* sync mailbox to finish flag changes and expunges. */
i_error("Mailbox %s: Sync failed: %s",
&importer->mail_error));
ret = -1;
}
if (ret == 0) {
/* give new UIDs to messages that got saved with unwanted UIDs.
do it only if the whole transaction succeeded. */
ret = -1;
}
return ret;
}
static void
{
struct hash_iterate_context *iter;
const char *key;
struct importer_new_mail *mail;
continue;
i_error("Mailbox %s: Remote didn't send mail GUID=%s (UID=%u)",
}
}
}
static void
{
struct hash_iterate_context *iter;
void *key;
struct importer_new_mail *mail;
continue;
i_error("Mailbox %s: Remote didn't send mail UID=%u",
mail->remote_uid);
}
}
}
bool success,
bool *changes_during_sync_r,
enum mail_error *error_r)
{
struct mailbox_status status;
int ret;
}
}
i_error("Mailbox %s: Search failed: %s",
&importer->mail_error));
}
}
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 */
}
if (importer->delete_mailbox) {
i_error("Couldn't delete mailbox %s: %s",
&importer->mail_error));
}
*last_messages_count_r = 0;
} else {
}
return ret;
}
{
return "";
}