summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/mbox/mbox-sync.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/lib-storage/index/mbox/mbox-sync.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/lib-storage/index/mbox/mbox-sync.c')
-rw-r--r--src/lib-storage/index/mbox/mbox-sync.c2066
1 files changed, 2066 insertions, 0 deletions
diff --git a/src/lib-storage/index/mbox/mbox-sync.c b/src/lib-storage/index/mbox/mbox-sync.c
new file mode 100644
index 0000000..0d2aa7f
--- /dev/null
+++ b/src/lib-storage/index/mbox/mbox-sync.c
@@ -0,0 +1,2066 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+/*
+ Modifying mbox can be slow, so we try to do it all at once minimizing the
+ required disk I/O. We may need to:
+
+ - Update message flags in Status, X-Status and X-Keywords headers
+ - Write missing X-UID and X-IMAPbase headers
+ - Write missing or broken Content-Length header if there's space
+ - Expunge specified messages
+
+ Here's how we do it:
+
+ - Start reading the mails from the beginning
+ - X-Keywords, X-UID and X-IMAPbase headers may contain padding at the end
+ of them, remember how much each message has and offset to beginning of the
+ padding
+ - If header needs to be rewritten and there's enough space, do it
+ - If we didn't have enough space, remember how much was missing
+ - Continue reading and counting the padding in each message. If available
+ padding is enough to rewrite all the previous messages needing it, do it
+ - When we encounter expunged message, treat all of it as padding and
+ rewrite previous messages if needed (and there's enough space).
+ Afterwards keep moving messages backwards to fill the expunged space.
+ Moving is done by rewriting each message's headers, with possibly adding
+ missing Content-Length header and padding. Message bodies are moved
+ without modifications.
+ - If we encounter end of file, grow the file and rewrite needed messages
+ - Rewriting is done by moving message body forward, rewriting message's
+ header and doing the same for previous message, until all of them are
+ rewritten.
+*/
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "buffer.h"
+#include "hostpid.h"
+#include "istream.h"
+#include "file-set-size.h"
+#include "str.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "sleep.h"
+#include "message-date.h"
+#include "istream-raw-mbox.h"
+#include "mbox-storage.h"
+#include "index-sync-changes.h"
+#include "mailbox-uidvalidity.h"
+#include "mailbox-recent-flags.h"
+#include "mbox-from.h"
+#include "mbox-file.h"
+#include "mbox-lock.h"
+#include "mbox-sync-private.h"
+
+#include <stddef.h>
+#include <utime.h>
+#include <sys/stat.h>
+
+/* The text below was taken exactly as c-client wrote it to my mailbox,
+ so it's probably copyrighted by University of Washington. */
+#define PSEUDO_MESSAGE_BODY \
+"This text is part of the internal format of your mail folder, and is not\n" \
+"a real message. It is created automatically by the mail system software.\n" \
+"If deleted, important folder data will be lost, and it will be re-created\n" \
+"with the data reset to initial values.\n"
+
+void mbox_sync_set_critical(struct mbox_sync_context *sync_ctx,
+ const char *fmt, ...)
+{
+ va_list va;
+
+ sync_ctx->errors = TRUE;
+ if (sync_ctx->ext_modified) {
+ mailbox_set_critical(&sync_ctx->mbox->box,
+ "mbox was modified while we were syncing, "
+ "check your locking settings");
+ }
+
+ va_start(va, fmt);
+ mailbox_set_critical(&sync_ctx->mbox->box,
+ "Sync failed for mbox: %s",
+ t_strdup_vprintf(fmt, va));
+ va_end(va);
+}
+
+int mbox_sync_seek(struct mbox_sync_context *sync_ctx, uoff_t from_offset)
+{
+ if (istream_raw_mbox_seek(sync_ctx->input, from_offset) < 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "Unexpectedly lost From-line at offset %"PRIuUOFF_T,
+ from_offset);
+ return -1;
+ }
+ return 0;
+}
+
+void mbox_sync_file_update_ext_modified(struct mbox_sync_context *sync_ctx)
+{
+ struct stat st;
+
+ /* Do this even if ext_modified is already set. Expunging code relies
+ on last_stat being updated. */
+ if (fstat(sync_ctx->write_fd, &st) < 0) {
+ mbox_set_syscall_error(sync_ctx->mbox, "fstat()");
+ return;
+ }
+
+ if (st.st_size != sync_ctx->last_stat.st_size ||
+ (sync_ctx->last_stat.st_mtime != 0 &&
+ !CMP_ST_MTIME(&st, &sync_ctx->last_stat)))
+ sync_ctx->ext_modified = TRUE;
+
+ sync_ctx->last_stat = st;
+}
+
+void mbox_sync_file_updated(struct mbox_sync_context *sync_ctx, bool dirty)
+{
+ if (dirty) {
+ /* just mark the stat as dirty. */
+ sync_ctx->last_stat.st_mtime = 0;
+ return;
+ }
+ if (fstat(sync_ctx->write_fd, &sync_ctx->last_stat) < 0)
+ mbox_set_syscall_error(sync_ctx->mbox, "fstat()");
+ i_stream_sync(sync_ctx->input);
+}
+
+static int
+mbox_sync_read_next_mail(struct mbox_sync_context *sync_ctx,
+ struct mbox_sync_mail_context *mail_ctx)
+{
+ uoff_t offset;
+
+ /* get EOF */
+ (void)istream_raw_mbox_get_header_offset(sync_ctx->input, &offset);
+ if (istream_raw_mbox_is_eof(sync_ctx->input))
+ return 0;
+
+ p_clear(sync_ctx->mail_keyword_pool);
+ i_zero(mail_ctx);
+ mail_ctx->sync_ctx = sync_ctx;
+ mail_ctx->seq = ++sync_ctx->seq;
+ mail_ctx->header = sync_ctx->header;
+
+ mail_ctx->mail.from_offset =
+ istream_raw_mbox_get_start_offset(sync_ctx->input);
+ if (istream_raw_mbox_get_header_offset(sync_ctx->input, &mail_ctx->mail.offset) < 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "Couldn't get header offset for seq=%u", mail_ctx->seq);
+ return -1;
+ }
+
+ if (mbox_sync_parse_next_mail(sync_ctx->input, mail_ctx) < 0)
+ return -1;
+ if (istream_raw_mbox_is_corrupted(sync_ctx->input))
+ return -1;
+
+ i_assert(sync_ctx->input->v_offset != mail_ctx->mail.from_offset ||
+ sync_ctx->input->eof);
+
+ if (istream_raw_mbox_get_body_size(sync_ctx->input,
+ mail_ctx->content_length,
+ &mail_ctx->mail.body_size) < 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "Couldn't get body size for seq=%u", mail_ctx->seq);
+ return -1;
+ }
+ i_assert(mail_ctx->mail.body_size < OFF_T_MAX);
+
+ if ((mail_ctx->mail.flags & MAIL_RECENT) != 0 &&
+ !mail_ctx->mail.pseudo) {
+ if (!sync_ctx->keep_recent) {
+ /* need to add 'O' flag to Status-header */
+ mail_ctx->need_rewrite = TRUE;
+ }
+ mail_ctx->recent = TRUE;
+ }
+ return 1;
+}
+
+static void mbox_sync_read_index_syncs(struct mbox_sync_context *sync_ctx,
+ uint32_t uid, bool *sync_expunge_r)
+{
+ guid_128_t expunged_guid_128;
+
+ if (uid == 0 || sync_ctx->index_reset) {
+ /* nothing for this or the future ones */
+ uid = (uint32_t)-1;
+ }
+
+ index_sync_changes_read(sync_ctx->sync_changes, uid, sync_expunge_r,
+ expunged_guid_128);
+ if (sync_ctx->readonly) {
+ /* we can't expunge anything from read-only mboxes */
+ *sync_expunge_r = FALSE;
+ }
+}
+
+static bool
+mbox_sync_read_index_rec(struct mbox_sync_context *sync_ctx,
+ uint32_t uid, const struct mail_index_record **rec_r)
+{
+ const struct mail_index_record *rec = NULL;
+ uint32_t messages_count;
+ bool ret = FALSE;
+
+ if (sync_ctx->index_reset) {
+ *rec_r = NULL;
+ return TRUE;
+ }
+
+ messages_count =
+ mail_index_view_get_messages_count(sync_ctx->sync_view);
+ while (sync_ctx->idx_seq <= messages_count) {
+ rec = mail_index_lookup(sync_ctx->sync_view, sync_ctx->idx_seq);
+ if (uid <= rec->uid)
+ break;
+
+ /* externally expunged message, remove from index */
+ mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq);
+ sync_ctx->idx_seq++;
+ rec = NULL;
+ }
+
+ if (rec == NULL && uid < sync_ctx->idx_next_uid) {
+ /* this UID was already in index and it was expunged */
+ mbox_sync_set_critical(sync_ctx,
+ "Expunged message reappeared to mailbox "
+ "(UID %u < %u, seq=%u, idx_msgs=%u)",
+ uid, sync_ctx->idx_next_uid,
+ sync_ctx->seq, messages_count);
+ ret = FALSE; rec = NULL;
+ } else if (rec != NULL && rec->uid != uid) {
+ /* new UID in the middle of the mailbox - shouldn't happen */
+ mbox_sync_set_critical(sync_ctx,
+ "UID inserted in the middle of mailbox "
+ "(%u > %u, seq=%u, idx_msgs=%u)",
+ rec->uid, uid, sync_ctx->seq, messages_count);
+ ret = FALSE; rec = NULL;
+ } else {
+ ret = TRUE;
+ }
+
+ *rec_r = rec;
+ return ret;
+}
+
+static void mbox_sync_find_index_md5(struct mbox_sync_context *sync_ctx,
+ unsigned char hdr_md5_sum[],
+ const struct mail_index_record **rec_r)
+{
+ const struct mail_index_record *rec = NULL;
+ uint32_t messages_count;
+ const void *data;
+
+ if (sync_ctx->index_reset) {
+ *rec_r = NULL;
+ return;
+ }
+
+ messages_count =
+ mail_index_view_get_messages_count(sync_ctx->sync_view);
+ while (sync_ctx->idx_seq <= messages_count) {
+ rec = mail_index_lookup(sync_ctx->sync_view, sync_ctx->idx_seq);
+ mail_index_lookup_ext(sync_ctx->sync_view,
+ sync_ctx->idx_seq,
+ sync_ctx->mbox->md5hdr_ext_idx,
+ &data, NULL);
+ if (data != NULL && memcmp(data, hdr_md5_sum, 16) == 0)
+ break;
+
+ /* externally expunged message, remove from index */
+ mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq);
+ sync_ctx->idx_seq++;
+ rec = NULL;
+ }
+
+ *rec_r = rec;
+}
+
+static void
+mbox_sync_update_from_offset(struct mbox_sync_context *sync_ctx,
+ struct mbox_sync_mail *mail,
+ bool nocheck)
+{
+ const void *data;
+ uint64_t offset;
+
+ if (!nocheck) {
+ /* see if from_offset needs updating */
+ mail_index_lookup_ext(sync_ctx->sync_view, sync_ctx->idx_seq,
+ sync_ctx->mbox->mbox_ext_idx,
+ &data, NULL);
+ if (data != NULL &&
+ *((const uint64_t *)data) == mail->from_offset)
+ return;
+ }
+
+ offset = mail->from_offset;
+ mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
+ sync_ctx->mbox->mbox_ext_idx, &offset, NULL);
+}
+
+static void
+mbox_sync_update_index_keywords(struct mbox_sync_mail_context *mail_ctx)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ struct mail_index *index = sync_ctx->mbox->box.index;
+ struct mail_keywords *keywords;
+
+ keywords = !array_is_created(&mail_ctx->mail.keywords) ?
+ mail_index_keywords_create(index, NULL) :
+ mail_index_keywords_create_from_indexes(index,
+ &mail_ctx->mail.keywords);
+ mail_index_update_keywords(sync_ctx->t, sync_ctx->idx_seq,
+ MODIFY_REPLACE, keywords);
+ mail_index_keywords_unref(&keywords);
+}
+
+static void
+mbox_sync_update_md5_if_changed(struct mbox_sync_mail_context *mail_ctx)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ const void *ext_data;
+
+ mail_index_lookup_ext(sync_ctx->sync_view, sync_ctx->idx_seq,
+ sync_ctx->mbox->md5hdr_ext_idx, &ext_data, NULL);
+ if (ext_data == NULL ||
+ memcmp(mail_ctx->hdr_md5_sum, ext_data, 16) != 0) {
+ mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
+ sync_ctx->mbox->md5hdr_ext_idx,
+ mail_ctx->hdr_md5_sum, NULL);
+ }
+}
+
+static void mbox_sync_get_dirty_flags(struct mbox_sync_mail_context *mail_ctx,
+ const struct mail_index_record *rec)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ ARRAY_TYPE(keyword_indexes) idx_keywords;
+ uint8_t idx_flags, mbox_flags;
+
+ /* default to undirtying the message. it gets added back if
+ flags/keywords don't match what is in the index. */
+ mail_ctx->mail.flags &= ENUM_NEGATE(MAIL_INDEX_MAIL_FLAG_DIRTY);
+
+ /* replace flags */
+ idx_flags = rec->flags & MAIL_FLAGS_NONRECENT;
+ mbox_flags = mail_ctx->mail.flags & MAIL_FLAGS_NONRECENT;
+ if (idx_flags != mbox_flags) {
+ mail_ctx->need_rewrite = TRUE;
+ mail_ctx->mail.flags = (mail_ctx->mail.flags & MAIL_RECENT) |
+ idx_flags | MAIL_INDEX_MAIL_FLAG_DIRTY;
+ }
+
+ /* replace keywords */
+ t_array_init(&idx_keywords, 32);
+ mail_index_lookup_keywords(sync_ctx->sync_view, sync_ctx->idx_seq,
+ &idx_keywords);
+ if (!index_keyword_array_cmp(&idx_keywords, &mail_ctx->mail.keywords)) {
+ mail_ctx->need_rewrite = TRUE;
+ mail_ctx->mail.flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
+
+ if (!array_is_created(&mail_ctx->mail.keywords)) {
+ p_array_init(&mail_ctx->mail.keywords,
+ sync_ctx->mail_keyword_pool,
+ array_count(&idx_keywords));
+ }
+ array_clear(&mail_ctx->mail.keywords);
+ array_append_array(&mail_ctx->mail.keywords, &idx_keywords);
+ }
+}
+
+static void mbox_sync_update_flags(struct mbox_sync_mail_context *mail_ctx,
+ const struct mail_index_record *rec)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ struct mailbox *box = &sync_ctx->mbox->box;
+ struct mbox_sync_mail *mail = &mail_ctx->mail;
+ enum mail_index_sync_type sync_type;
+ ARRAY_TYPE(keyword_indexes) orig_keywords = ARRAY_INIT;
+ uint8_t flags, orig_flags;
+
+ if (rec != NULL) {
+ if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
+ /* flags and keywords are dirty. replace the current
+ ones from the flags in index file. */
+ mbox_sync_get_dirty_flags(mail_ctx, rec);
+ }
+ }
+
+ flags = orig_flags = mail->flags & MAIL_FLAGS_NONRECENT;
+ if (array_is_created(&mail->keywords)) {
+ t_array_init(&orig_keywords, 32);
+ array_append_array(&orig_keywords, &mail->keywords);
+ }
+
+ /* apply new changes */
+ index_sync_changes_apply(sync_ctx->sync_changes,
+ sync_ctx->mail_keyword_pool,
+ &flags, &mail->keywords, &sync_type);
+ if (flags != orig_flags ||
+ !index_keyword_array_cmp(&mail->keywords, &orig_keywords)) {
+ mail_ctx->need_rewrite = TRUE;
+ mail->flags = flags | (mail->flags & MAIL_RECENT) |
+ MAIL_INDEX_MAIL_FLAG_DIRTY;
+ }
+ if (sync_type != 0) {
+ mailbox_sync_notify(box, mail_ctx->mail.uid,
+ index_sync_type_convert(sync_type));
+ }
+}
+
+static void mbox_sync_update_index(struct mbox_sync_mail_context *mail_ctx,
+ const struct mail_index_record *rec)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ struct mbox_sync_mail *mail = &mail_ctx->mail;
+ ARRAY_TYPE(keyword_indexes) idx_keywords;
+ uint8_t mbox_flags;
+
+ mbox_flags = mail->flags & ENUM_NEGATE(MAIL_RECENT);
+ if (!sync_ctx->delay_writes) {
+ /* changes are written to the mbox file */
+ mbox_flags &= ENUM_NEGATE(MAIL_INDEX_MAIL_FLAG_DIRTY);
+ } else if (mail_ctx->need_rewrite) {
+ /* make sure this message gets written later */
+ mbox_flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
+ }
+
+ if (rec == NULL) {
+ /* new message */
+ mail_index_append(sync_ctx->t, mail->uid, &sync_ctx->idx_seq);
+ mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq,
+ MODIFY_REPLACE, mbox_flags);
+ mbox_sync_update_index_keywords(mail_ctx);
+
+ if (sync_ctx->mbox->mbox_save_md5) {
+ mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
+ sync_ctx->mbox->md5hdr_ext_idx,
+ mail_ctx->hdr_md5_sum, NULL);
+ }
+ } else {
+ if ((rec->flags & MAIL_FLAGS_NONRECENT) !=
+ (mbox_flags & MAIL_FLAGS_NONRECENT)) {
+ /* flags other than recent/dirty have changed */
+ mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq,
+ MODIFY_REPLACE, mbox_flags);
+ } else if (((rec->flags ^ mbox_flags) &
+ MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
+ /* only dirty flag state changed */
+ bool dirty;
+
+ dirty = (mbox_flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0;
+ mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq,
+ dirty ? MODIFY_ADD : MODIFY_REMOVE,
+ (enum mail_flags)MAIL_INDEX_MAIL_FLAG_DIRTY);
+ }
+
+ /* see if keywords changed */
+ t_array_init(&idx_keywords, 32);
+ mail_index_lookup_keywords(sync_ctx->sync_view,
+ sync_ctx->idx_seq, &idx_keywords);
+ if (!index_keyword_array_cmp(&idx_keywords, &mail->keywords))
+ mbox_sync_update_index_keywords(mail_ctx);
+
+ /* see if we need to update md5 sum. */
+ if (sync_ctx->mbox->mbox_save_md5)
+ mbox_sync_update_md5_if_changed(mail_ctx);
+ }
+
+ if (!mail_ctx->recent) {
+ /* Mail has "Status: O" header. No messages before this
+ can be recent. */
+ sync_ctx->last_nonrecent_uid = mail->uid;
+ }
+
+ /* update from_offsets, but not if we're going to rewrite this message.
+ rewriting would just move it anyway. */
+ if (sync_ctx->need_space_seq == 0) {
+ bool nocheck = rec == NULL || sync_ctx->expunged_space > 0;
+ mbox_sync_update_from_offset(sync_ctx, mail, nocheck);
+ }
+}
+
+static int mbox_read_from_line(struct mbox_sync_mail_context *ctx)
+{
+ struct istream *input = ctx->sync_ctx->file_input;
+ const unsigned char *data;
+ size_t size, from_line_size;
+
+ buffer_set_used_size(ctx->sync_ctx->from_line, 0);
+ from_line_size = ctx->hdr_offset - ctx->mail.from_offset;
+
+ i_stream_seek(input, ctx->mail.from_offset);
+ for (;;) {
+ data = i_stream_get_data(input, &size);
+ if (size >= from_line_size)
+ size = from_line_size;
+
+ buffer_append(ctx->sync_ctx->from_line, data, size);
+ i_stream_skip(input, size);
+ from_line_size -= size;
+
+ if (from_line_size == 0)
+ break;
+
+ if (i_stream_read(input) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mbox_rewrite_base_uid_last(struct mbox_sync_context *sync_ctx)
+{
+ unsigned char buf[10];
+ const char *str;
+ uint32_t uid_last;
+ unsigned int i;
+ int ret;
+
+ i_assert(sync_ctx->base_uid_last_offset != 0);
+
+ /* first check that the 10 bytes are there and they're exactly as
+ expected. just an extra safety check to make sure we never write
+ to wrong location in the mbox file. */
+ ret = pread_full(sync_ctx->write_fd, buf, sizeof(buf),
+ sync_ctx->base_uid_last_offset);
+ if (ret < 0) {
+ mbox_set_syscall_error(sync_ctx->mbox, "pread_full()");
+ return -1;
+ }
+ if (ret == 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "X-IMAPbase uid-last offset unexpectedly outside mbox");
+ return -1;
+ }
+
+ for (i = 0, uid_last = 0; i < sizeof(buf); i++) {
+ if (buf[i] < '0' || buf[i] > '9') {
+ uid_last = (uint32_t)-1;
+ break;
+ }
+ uid_last = uid_last * 10 + (buf[i] - '0');
+ }
+
+ if (uid_last != sync_ctx->base_uid_last) {
+ mbox_sync_set_critical(sync_ctx,
+ "X-IMAPbase uid-last unexpectedly lost");
+ return -1;
+ }
+
+ /* and write it */
+ str = t_strdup_printf("%010u", sync_ctx->next_uid - 1);
+ if (pwrite_full(sync_ctx->write_fd, str, 10,
+ sync_ctx->base_uid_last_offset) < 0) {
+ mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
+ return -1;
+ }
+ mbox_sync_file_updated(sync_ctx, FALSE);
+
+ sync_ctx->base_uid_last = sync_ctx->next_uid - 1;
+ return 0;
+}
+
+static int
+mbox_write_from_line(struct mbox_sync_mail_context *ctx)
+{
+ string_t *str = ctx->sync_ctx->from_line;
+
+ if (pwrite_full(ctx->sync_ctx->write_fd, str_data(str), str_len(str),
+ ctx->mail.from_offset) < 0) {
+ mbox_set_syscall_error(ctx->sync_ctx->mbox, "pwrite_full()");
+ return -1;
+ }
+
+ mbox_sync_file_updated(ctx->sync_ctx, FALSE);
+ return 0;
+}
+
+static void update_from_offsets(struct mbox_sync_context *sync_ctx)
+{
+ const struct mbox_sync_mail *mails;
+ unsigned int i, count;
+ uint32_t ext_idx;
+ uint64_t offset;
+
+ ext_idx = sync_ctx->mbox->mbox_ext_idx;
+
+ mails = array_get(&sync_ctx->mails, &count);
+ for (i = 0; i < count; i++) {
+ if (mails[i].idx_seq == 0 || mails[i].expunged)
+ continue;
+
+ sync_ctx->moved_offsets = TRUE;
+ offset = mails[i].from_offset;
+ mail_index_update_ext(sync_ctx->t, mails[i].idx_seq,
+ ext_idx, &offset, NULL);
+ }
+}
+
+static void mbox_sync_handle_expunge(struct mbox_sync_mail_context *mail_ctx)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ struct mailbox *box = &sync_ctx->mbox->box;
+
+ mailbox_sync_notify(box, mail_ctx->mail.uid,
+ MAILBOX_SYNC_TYPE_EXPUNGE);
+ mail_index_expunge(sync_ctx->t, mail_ctx->mail.idx_seq);
+
+ mail_ctx->mail.expunged = TRUE;
+ mail_ctx->mail.offset = mail_ctx->mail.from_offset;
+ mail_ctx->mail.space =
+ mail_ctx->body_offset - mail_ctx->mail.from_offset +
+ mail_ctx->mail.body_size;
+ mail_ctx->mail.body_size = 0;
+ mail_ctx->mail.uid = 0;
+
+ if (sync_ctx->seq == 1) {
+ /* expunging first message, fix space to contain next
+ message's \n header too since it will be removed. */
+ mail_ctx->mail.space++;
+ if (istream_raw_mbox_has_crlf_ending(sync_ctx->input)) {
+ mail_ctx->mail.space++;
+ sync_ctx->first_mail_crlf_expunged = TRUE;
+ }
+
+ /* uid-last offset is invalid now */
+ sync_ctx->base_uid_last_offset = 0;
+ }
+
+ sync_ctx->expunged_space += mail_ctx->mail.space;
+}
+
+static int mbox_sync_handle_header(struct mbox_sync_mail_context *mail_ctx)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ uoff_t orig_from_offset, postlf_from_offset = UOFF_T_MAX;
+ off_t move_diff;
+ int ret;
+
+ if (sync_ctx->expunged_space > 0 && sync_ctx->need_space_seq == 0) {
+ /* move the header backwards to fill expunged space */
+ move_diff = -sync_ctx->expunged_space;
+
+ orig_from_offset = mail_ctx->mail.from_offset;
+ if (sync_ctx->dest_first_mail) {
+ /* we're moving this mail to beginning of file.
+ skip the initial \n (it's already counted in
+ expunged_space) */
+ mail_ctx->mail.from_offset++;
+ if (sync_ctx->first_mail_crlf_expunged)
+ mail_ctx->mail.from_offset++;
+ }
+ postlf_from_offset = mail_ctx->mail.from_offset;
+
+ /* read the From-line before rewriting overwrites it */
+ if (mbox_read_from_line(mail_ctx) < 0)
+ return -1;
+ i_assert((off_t)mail_ctx->mail.from_offset + move_diff != 1 &&
+ (off_t)mail_ctx->mail.from_offset + move_diff != 2);
+
+ mbox_sync_update_header(mail_ctx);
+ ret = mbox_sync_try_rewrite(mail_ctx, move_diff);
+ if (ret < 0)
+ return -1;
+
+ if (ret > 0) {
+ /* rewrite successful, write From-line to
+ new location */
+ i_assert((off_t)mail_ctx->mail.from_offset >=
+ -move_diff);
+ mail_ctx->mail.from_offset = (off_t)mail_ctx->mail.from_offset + move_diff;
+ mail_ctx->mail.offset = (off_t)mail_ctx->mail.offset + move_diff;
+ if (mbox_write_from_line(mail_ctx) < 0)
+ return -1;
+ } else {
+ if (sync_ctx->dest_first_mail) {
+ /* didn't have enough space, move the offset
+ back so seeking into it doesn't fail */
+ mail_ctx->mail.from_offset = orig_from_offset;
+ }
+ }
+ } else if (mail_ctx->need_rewrite) {
+ mbox_sync_update_header(mail_ctx);
+ if (sync_ctx->delay_writes && sync_ctx->need_space_seq == 0) {
+ /* mark it dirty and do it later. we can't do this
+ if we're in the middle of rewriting acquiring more
+ space. */
+ mail_ctx->dirty = TRUE;
+ return 0;
+ }
+
+ if ((ret = mbox_sync_try_rewrite(mail_ctx, 0)) < 0)
+ return -1;
+ } else {
+ /* nothing to do */
+ return 0;
+ }
+
+ if (ret == 0 && sync_ctx->need_space_seq == 0) {
+ /* first mail with no space to write it */
+ sync_ctx->need_space_seq = sync_ctx->seq;
+ sync_ctx->space_diff = 0;
+
+ if (sync_ctx->expunged_space > 0) {
+ /* create dummy message to describe the expunged data */
+ struct mbox_sync_mail mail;
+
+ /* if this is going to be the first mail, increase the
+ from_offset to point to the beginning of the
+ From-line, because the previous [CR]LF is already
+ covered by expunged_space. */
+ i_assert(postlf_from_offset != UOFF_T_MAX);
+ mail_ctx->mail.from_offset = postlf_from_offset;
+
+ i_zero(&mail);
+ mail.expunged = TRUE;
+ mail.offset = mail.from_offset =
+ mail_ctx->mail.from_offset -
+ sync_ctx->expunged_space;
+ mail.space = sync_ctx->expunged_space;
+
+ sync_ctx->space_diff = sync_ctx->expunged_space;
+ sync_ctx->expunged_space = 0;
+ i_assert(sync_ctx->space_diff < -mail_ctx->mail.space);
+
+ sync_ctx->need_space_seq--;
+ array_push_back(&sync_ctx->mails, &mail);
+ }
+ }
+ return 0;
+}
+
+static int
+mbox_sync_handle_missing_space(struct mbox_sync_mail_context *mail_ctx)
+{
+ struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
+ uoff_t end_offset, move_diff, extra_space, needed_space;
+ uint32_t last_seq;
+ ARRAY_TYPE(keyword_indexes) keywords_copy;
+
+ i_assert(mail_ctx->mail.uid == 0 || mail_ctx->mail.space > 0 ||
+ mail_ctx->mail.offset == mail_ctx->hdr_offset);
+
+ if (array_is_created(&mail_ctx->mail.keywords)) {
+ /* mail's keywords are allocated from a pool that's cleared
+ for each mail. we'll need to copy it to something more
+ permanent. */
+ p_array_init(&keywords_copy, sync_ctx->saved_keywords_pool,
+ array_count(&mail_ctx->mail.keywords));
+ array_append_array(&keywords_copy, &mail_ctx->mail.keywords);
+ mail_ctx->mail.keywords = keywords_copy;
+ }
+ array_push_back(&sync_ctx->mails, &mail_ctx->mail);
+
+ sync_ctx->space_diff += mail_ctx->mail.space;
+ if (sync_ctx->space_diff < 0) {
+ if (sync_ctx->expunged_space > 0) {
+ i_assert(sync_ctx->expunged_space ==
+ mail_ctx->mail.space);
+ sync_ctx->expunged_space = 0;
+ }
+ return 0;
+ }
+
+ /* we have enough space now */
+ if (mail_ctx->mail.uid == 0) {
+ /* this message was expunged. fill more or less of the space.
+ space_diff now consists of a negative "bytes needed" sum,
+ plus the expunged space of this message. so it contains how
+ many bytes of _extra_ space we have. */
+ i_assert(mail_ctx->mail.space >= sync_ctx->space_diff);
+ extra_space = MBOX_HEADER_PADDING *
+ (sync_ctx->seq - sync_ctx->need_space_seq + 1);
+ needed_space = mail_ctx->mail.space - sync_ctx->space_diff;
+ if ((uoff_t)sync_ctx->space_diff > needed_space + extra_space) {
+ /* don't waste too much on padding */
+ move_diff = needed_space + extra_space;
+ sync_ctx->expunged_space =
+ mail_ctx->mail.space - move_diff;
+ } else {
+ move_diff = mail_ctx->mail.space;
+ extra_space = sync_ctx->space_diff;
+ sync_ctx->expunged_space = 0;
+ }
+ last_seq = sync_ctx->seq - 1;
+ array_pop_back(&sync_ctx->mails);
+ end_offset = mail_ctx->mail.from_offset;
+ } else {
+ /* this message gave enough space from headers. rewriting stops
+ at the end of this message's headers. */
+ sync_ctx->expunged_space = 0;
+ last_seq = sync_ctx->seq;
+ end_offset = mail_ctx->body_offset;
+
+ move_diff = 0;
+ extra_space = sync_ctx->space_diff;
+ }
+
+ mbox_sync_file_update_ext_modified(sync_ctx);
+ if (mbox_sync_rewrite(sync_ctx,
+ last_seq == sync_ctx->seq ? mail_ctx : NULL,
+ end_offset, move_diff, extra_space,
+ sync_ctx->need_space_seq, last_seq) < 0)
+ return -1;
+
+ update_from_offsets(sync_ctx);
+
+ /* mail_ctx may contain wrong data after rewrite, so make sure we
+ don't try to access it */
+ i_zero(mail_ctx);
+
+ sync_ctx->need_space_seq = 0;
+ sync_ctx->space_diff = 0;
+ array_clear(&sync_ctx->mails);
+ p_clear(sync_ctx->saved_keywords_pool);
+ return 0;
+}
+
+static int
+mbox_sync_seek_to_seq(struct mbox_sync_context *sync_ctx, uint32_t seq)
+{
+ struct mbox_mailbox *mbox = sync_ctx->mbox;
+ uoff_t old_offset, offset;
+ uint32_t uid;
+ int ret;
+ bool deleted;
+
+ if (seq == 0) {
+ if (istream_raw_mbox_seek(mbox->mbox_stream, 0) < 0) {
+ mbox->invalid_mbox_file = TRUE;
+ mail_storage_set_error(&mbox->storage->storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "Mailbox isn't a valid mbox file");
+ return -1;
+ }
+ seq++;
+ } else {
+ old_offset = istream_raw_mbox_get_start_offset(sync_ctx->input);
+
+ ret = mbox_file_seek(mbox, sync_ctx->sync_view, seq, &deleted);
+ if (ret < 0) {
+ if (deleted) {
+ mbox_sync_set_critical(sync_ctx,
+ "Message was expunged unexpectedly");
+ }
+ return -1;
+ }
+ if (ret == 0) {
+ if (istream_raw_mbox_seek(mbox->mbox_stream,
+ old_offset) < 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "Error seeking back to original "
+ "offset %s", dec2str(old_offset));
+ return -1;
+ }
+ return 0;
+ }
+ }
+
+ if (seq <= 1)
+ uid = 0;
+ else
+ mail_index_lookup_uid(sync_ctx->sync_view, seq-1, &uid);
+
+ sync_ctx->prev_msg_uid = uid;
+
+ /* set to -1, since it's always increased later */
+ sync_ctx->seq = seq-1;
+ if (sync_ctx->seq == 0 &&
+ istream_raw_mbox_get_start_offset(sync_ctx->input) != 0) {
+ /* this mbox has pseudo mail which contains the X-IMAP header */
+ sync_ctx->seq++;
+ }
+
+ sync_ctx->idx_seq = seq;
+ sync_ctx->dest_first_mail = sync_ctx->seq == 0;
+ if (istream_raw_mbox_get_body_offset(sync_ctx->input, &offset) < 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "Message body offset lookup failed");
+ return -1;
+ }
+ return 1;
+}
+
+static int
+mbox_sync_seek_to_uid(struct mbox_sync_context *sync_ctx, uint32_t uid)
+{
+ struct mail_index_view *sync_view = sync_ctx->sync_view;
+ uint32_t seq1, seq2;
+ uoff_t size;
+ int ret;
+
+ i_assert(!sync_ctx->index_reset);
+
+ if (!mail_index_lookup_seq_range(sync_view, uid, (uint32_t)-1,
+ &seq1, &seq2)) {
+ /* doesn't exist anymore, seek to end of file */
+ ret = i_stream_get_size(sync_ctx->file_input, TRUE, &size);
+ if (ret < 0) {
+ mbox_istream_set_syscall_error(sync_ctx->mbox,
+ sync_ctx->file_input, "i_stream_get_size()");
+ return -1;
+ }
+ i_assert(ret != 0);
+
+ if (istream_raw_mbox_seek(sync_ctx->mbox->mbox_stream,
+ size) < 0) {
+ mbox_sync_set_critical(sync_ctx,
+ "Error seeking to end of mbox");
+ return -1;
+ }
+ sync_ctx->idx_seq =
+ mail_index_view_get_messages_count(sync_view) + 1;
+ return 1;
+ }
+
+ return mbox_sync_seek_to_seq(sync_ctx, seq1);
+}
+
+static int mbox_sync_partial_seek_next(struct mbox_sync_context *sync_ctx,
+ uint32_t next_uid, bool *partial,
+ bool *skipped_mails)
+{
+ uint32_t messages_count, uid;
+ int ret;
+
+ i_assert(!sync_ctx->index_reset);
+
+ /* delete sync records up to next message. so if there's still
+ something left in array, it means the next message needs modifying */
+ index_sync_changes_delete_to(sync_ctx->sync_changes, next_uid);
+ if (index_sync_changes_have(sync_ctx->sync_changes))
+ return 1;
+
+ if (sync_ctx->hdr->first_recent_uid <= next_uid &&
+ !sync_ctx->keep_recent) {
+ /* we'll need to rewrite Status: O headers */
+ return 1;
+ }
+
+ uid = index_sync_changes_get_next_uid(sync_ctx->sync_changes);
+
+ if (sync_ctx->hdr->first_recent_uid < sync_ctx->hdr->next_uid &&
+ (uid > sync_ctx->hdr->first_recent_uid || uid == 0) &&
+ !sync_ctx->keep_recent) {
+ /* we'll need to rewrite Status: O headers */
+ uid = sync_ctx->hdr->first_recent_uid;
+ }
+
+ if (uid != 0) {
+ /* we can skip forward to next record which needs updating. */
+ if (uid != next_uid) {
+ *skipped_mails = TRUE;
+ next_uid = uid;
+ }
+ ret = mbox_sync_seek_to_uid(sync_ctx, next_uid);
+ } else {
+ /* if there's no sync records left, we can stop. except if
+ this is a dirty sync, check if there are new messages. */
+ if (sync_ctx->mbox->mbox_hdr.dirty_flag == 0)
+ return 0;
+
+ messages_count =
+ mail_index_view_get_messages_count(sync_ctx->sync_view);
+ if (sync_ctx->seq + 1 != messages_count) {
+ ret = mbox_sync_seek_to_seq(sync_ctx, messages_count);
+ *skipped_mails = TRUE;
+ } else {
+ ret = 1;
+ }
+ *partial = FALSE;
+ }
+
+ if (ret == 0) {
+ /* seek failed because the offset is dirty. just ignore and
+ continue from where we are now. */
+ *partial = FALSE;
+ ret = 1;
+ }
+ return ret;
+}
+
+static void mbox_sync_hdr_update(struct mbox_sync_context *sync_ctx,
+ struct mbox_sync_mail_context *mail_ctx)
+{
+ const struct mailbox_update *update = sync_ctx->mbox->sync_hdr_update;
+
+ if (update->uid_validity != 0) {
+ sync_ctx->base_uid_validity = update->uid_validity;
+ mail_ctx->imapbase_rewrite = TRUE;
+ mail_ctx->need_rewrite = TRUE;
+ }
+ if (update->min_next_uid != 0 &&
+ sync_ctx->base_uid_last+1 < update->min_next_uid) {
+ i_assert(sync_ctx->next_uid <= update->min_next_uid);
+ sync_ctx->base_uid_last = update->min_next_uid-1;
+ sync_ctx->next_uid = update->min_next_uid;
+ mail_ctx->imapbase_rewrite = TRUE;
+ mail_ctx->need_rewrite = TRUE;
+ }
+}
+
+static bool mbox_sync_imapbase(struct mbox_sync_context *sync_ctx,
+ struct mbox_sync_mail_context *mail_ctx)
+{
+ if (sync_ctx->base_uid_validity != 0 &&
+ sync_ctx->hdr->uid_validity != 0 &&
+ sync_ctx->base_uid_validity != sync_ctx->hdr->uid_validity) {
+ i_warning("UIDVALIDITY changed (%u -> %u) in mbox file %s",
+ sync_ctx->hdr->uid_validity,
+ sync_ctx->base_uid_validity,
+ mailbox_get_path(&sync_ctx->mbox->box));
+ sync_ctx->index_reset = TRUE;
+ return TRUE;
+ }
+ if (sync_ctx->mbox->sync_hdr_update != NULL)
+ mbox_sync_hdr_update(sync_ctx, mail_ctx);
+ return FALSE;
+}
+
+static int mbox_sync_loop(struct mbox_sync_context *sync_ctx,
+ struct mbox_sync_mail_context *mail_ctx,
+ bool partial)
+{
+ const struct mail_index_record *rec;
+ uint32_t uid, messages_count;
+ uoff_t offset;
+ int ret;
+ bool expunged, skipped_mails, uids_broken;
+
+ messages_count =
+ mail_index_view_get_messages_count(sync_ctx->sync_view);
+
+ /* always start from first message so we can read X-IMAP or
+ X-IMAPbase header */
+ ret = mbox_sync_seek_to_seq(sync_ctx, 0);
+ if (ret <= 0)
+ return ret;
+
+ if (sync_ctx->renumber_uids) {
+ /* expunge everything */
+ while (sync_ctx->idx_seq <= messages_count) {
+ mail_index_expunge(sync_ctx->t,
+ sync_ctx->idx_seq++);
+ }
+ }
+
+ skipped_mails = uids_broken = FALSE;
+ while ((ret = mbox_sync_read_next_mail(sync_ctx, mail_ctx)) > 0) {
+ uid = mail_ctx->mail.uid;
+
+ if (mail_ctx->seq == 1) {
+ if (mbox_sync_imapbase(sync_ctx, mail_ctx)) {
+ sync_ctx->mbox->mbox_hdr.dirty_flag = 1;
+ return 0;
+ }
+ }
+
+ if (mail_ctx->mail.uid_broken && partial) {
+ /* UID ordering problems, resync everything to make
+ sure we get everything right */
+ if (sync_ctx->mbox->mbox_hdr.dirty_flag != 0)
+ return 0;
+
+ mbox_sync_set_critical(sync_ctx,
+ "UIDs broken with partial sync");
+
+ sync_ctx->mbox->mbox_hdr.dirty_flag = 1;
+ return 0;
+ }
+ if (mail_ctx->mail.uid_broken)
+ uids_broken = TRUE;
+
+ if (mail_ctx->mail.pseudo)
+ uid = 0;
+
+ rec = NULL; ret = 1;
+ if (uid != 0) {
+ if (!mbox_sync_read_index_rec(sync_ctx, uid, &rec))
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ /* UID found but it's broken */
+ uid = 0;
+ } else if (uid == 0 &&
+ !mail_ctx->mail.pseudo &&
+ (sync_ctx->delay_writes ||
+ sync_ctx->idx_seq <= messages_count)) {
+ /* If we can't use/store X-UID header, use MD5 sum.
+ Also check for existing MD5 sums when we're actually
+ able to write X-UIDs. */
+ sync_ctx->mbox->mbox_save_md5 = TRUE;
+
+ mbox_sync_find_index_md5(sync_ctx,
+ mail_ctx->hdr_md5_sum, &rec);
+ if (rec != NULL)
+ uid = mail_ctx->mail.uid = rec->uid;
+ }
+
+ /* get all sync records related to this message. with pseudo
+ message just get the first sync record so we can jump to
+ it with partial seeking. */
+ mbox_sync_read_index_syncs(sync_ctx,
+ mail_ctx->mail.pseudo ? 1 : uid,
+ &expunged);
+
+ if (mail_ctx->mail.pseudo) {
+ /* if it was set, it was for the next message */
+ expunged = FALSE;
+ } else {
+ if (rec == NULL) {
+ /* message wasn't found from index. we have to
+ read everything from now on, no skipping */
+ partial = FALSE;
+ }
+ }
+
+ if (uid == 0 && !mail_ctx->mail.pseudo) {
+ /* missing/broken X-UID. all the rest of the mails
+ need new UIDs. */
+ while (sync_ctx->idx_seq <= messages_count) {
+ mail_index_expunge(sync_ctx->t,
+ sync_ctx->idx_seq++);
+ }
+
+ if (sync_ctx->next_uid == (uint32_t)-1) {
+ /* oh no, we're out of UIDs. this shouldn't
+ happen normally, so just try to get it fixed
+ without crashing. */
+ mailbox_set_critical(&sync_ctx->mbox->box,
+ "Out of UIDs, renumbering them in mbox");
+ sync_ctx->renumber_uids = TRUE;
+ return 0;
+ }
+
+ mail_ctx->need_rewrite = TRUE;
+ mail_ctx->mail.uid = sync_ctx->next_uid++;
+ }
+ sync_ctx->prev_msg_uid = mail_ctx->mail.uid;
+
+ if (!mail_ctx->mail.pseudo)
+ mail_ctx->mail.idx_seq = sync_ctx->idx_seq;
+
+ if (!expunged) {
+ if (!mail_ctx->mail.pseudo) T_BEGIN {
+ mbox_sync_update_flags(mail_ctx, rec);
+ } T_END;
+ if (mbox_sync_handle_header(mail_ctx) < 0)
+ return -1;
+ sync_ctx->dest_first_mail = FALSE;
+ } else {
+ mbox_sync_handle_expunge(mail_ctx);
+ }
+
+ if (!mail_ctx->mail.pseudo) {
+ if (!expunged) T_BEGIN {
+ mbox_sync_update_index(mail_ctx, rec);
+ } T_END;
+ sync_ctx->idx_seq++;
+ }
+
+ if (istream_raw_mbox_next(sync_ctx->input,
+ mail_ctx->mail.body_size) < 0)
+ return -1;
+ offset = istream_raw_mbox_get_start_offset(sync_ctx->input);
+
+ if (sync_ctx->need_space_seq != 0) {
+ if (mbox_sync_handle_missing_space(mail_ctx) < 0)
+ return -1;
+ if (mbox_sync_seek(sync_ctx, offset) < 0)
+ return -1;
+ } else if (sync_ctx->expunged_space > 0) {
+ if (!expunged) {
+ /* move the body */
+ mbox_sync_file_update_ext_modified(sync_ctx);
+ if (mbox_move(sync_ctx,
+ mail_ctx->body_offset -
+ sync_ctx->expunged_space,
+ mail_ctx->body_offset,
+ mail_ctx->mail.body_size) < 0)
+ return -1;
+ if (mbox_sync_seek(sync_ctx, offset) < 0)
+ return -1;
+ }
+ } else if (partial) {
+ ret = mbox_sync_partial_seek_next(sync_ctx, uid + 1,
+ &partial,
+ &skipped_mails);
+ if (ret <= 0)
+ break;
+ }
+ }
+ if (ret < 0)
+ return -1;
+
+ if (istream_raw_mbox_is_eof(sync_ctx->input)) {
+ /* rest of the messages in index don't exist -> expunge them */
+ while (sync_ctx->idx_seq <= messages_count)
+ mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq++);
+ }
+
+ if (!skipped_mails)
+ sync_ctx->mbox->mbox_hdr.dirty_flag = 0;
+ sync_ctx->mbox->mbox_broken_offsets = FALSE;
+
+ if (uids_broken && sync_ctx->delay_writes) {
+ /* once we get around to writing the changes, we'll need to do
+ a full sync to avoid the "UIDs broken in partial sync"
+ error */
+ sync_ctx->mbox->mbox_hdr.dirty_flag = 1;
+ }
+ return 1;
+}
+
+static int mbox_write_pseudo(struct mbox_sync_context *sync_ctx, bool force)
+{
+ string_t *str;
+ unsigned int uid_validity;
+
+ i_assert(sync_ctx->write_fd != -1);
+
+ if (sync_ctx->mbox->sync_hdr_update != NULL) {
+ const struct mailbox_update *update =
+ sync_ctx->mbox->sync_hdr_update;
+ bool change = FALSE;
+
+ if (update->uid_validity != 0) {
+ sync_ctx->base_uid_validity = update->uid_validity;
+ change = TRUE;
+ }
+ if (update->min_next_uid != 0) {
+ sync_ctx->base_uid_last = update->min_next_uid-1;
+ change = TRUE;
+ }
+ if (!change && !force)
+ return 0;
+ }
+
+ uid_validity = sync_ctx->base_uid_validity != 0 ?
+ sync_ctx->base_uid_validity : sync_ctx->hdr->uid_validity;
+ i_assert(uid_validity != 0);
+
+ str = t_str_new(1024);
+ str_printfa(str, "%sDate: %s\n"
+ "From: Mail System Internal Data <MAILER-DAEMON@%s>\n"
+ "Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA"
+ "\nMessage-ID: <%s@%s>\n"
+ "X-IMAP: %u %010u\n"
+ "Status: RO\n"
+ "\n"
+ PSEUDO_MESSAGE_BODY
+ "\n",
+ mbox_from_create("MAILER_DAEMON", ioloop_time),
+ message_date_create(ioloop_time),
+ my_hostname, dec2str(ioloop_time), my_hostname,
+ uid_validity, sync_ctx->next_uid-1);
+
+ if (pwrite_full(sync_ctx->write_fd,
+ str_data(str), str_len(str), 0) < 0) {
+ if (!ENOSPACE(errno)) {
+ mbox_set_syscall_error(sync_ctx->mbox,
+ "pwrite_full()");
+ return -1;
+ }
+
+ /* out of disk space, truncate to empty */
+ if (ftruncate(sync_ctx->write_fd, 0) < 0)
+ mbox_set_syscall_error(sync_ctx->mbox, "ftruncate()");
+ }
+
+ sync_ctx->base_uid_validity = uid_validity;
+ sync_ctx->base_uid_last_offset = 0; /* don't bother calculating */
+ sync_ctx->base_uid_last = sync_ctx->next_uid-1;
+ return 0;
+}
+
+static int mbox_append_zero(struct mbox_sync_context *sync_ctx,
+ uoff_t orig_file_size, uoff_t count)
+{
+ char block[IO_BLOCK_SIZE];
+ uoff_t offset = orig_file_size;
+ ssize_t ret = 0;
+
+ memset(block, 0, I_MIN(sizeof(block), count));
+ while (count > 0) {
+ ret = pwrite(sync_ctx->write_fd, block,
+ I_MIN(sizeof(block), count), offset);
+ if (ret < 0)
+ break;
+ offset += ret;
+ count -= ret;
+ }
+
+ if (ret < 0) {
+ mbox_set_syscall_error(sync_ctx->mbox, "pwrite()");
+ if (ftruncate(sync_ctx->write_fd, orig_file_size) < 0)
+ mbox_set_syscall_error(sync_ctx->mbox, "ftruncate()");
+ return -1;
+ }
+ return 0;
+}
+
+static int mbox_sync_handle_eof_updates(struct mbox_sync_context *sync_ctx,
+ struct mbox_sync_mail_context *mail_ctx)
+{
+ uoff_t file_size, offset, padding, trailer_size;
+ int ret;
+
+ if (!istream_raw_mbox_is_eof(sync_ctx->input)) {
+ i_assert(sync_ctx->need_space_seq == 0);
+ i_assert(sync_ctx->expunged_space == 0);
+ return 0;
+ }
+
+ ret = i_stream_get_size(sync_ctx->file_input, TRUE, &file_size);
+ if (ret < 0) {
+ mbox_istream_set_syscall_error(sync_ctx->mbox,
+ sync_ctx->file_input, "i_stream_get_size()");
+ return -1;
+ }
+ if (ret == 0) {
+ /* Not a file - allow anyway */
+ return 0;
+ }
+
+ if (file_size < sync_ctx->file_input->v_offset) {
+ mbox_sync_set_critical(sync_ctx,
+ "file size unexpectedly shrank "
+ "(%"PRIuUOFF_T" vs %"PRIuUOFF_T")", file_size,
+ sync_ctx->file_input->v_offset);
+ return -1;
+ }
+ trailer_size = file_size - sync_ctx->file_input->v_offset;
+ i_assert(trailer_size <= 2);
+
+ if (sync_ctx->need_space_seq != 0) {
+ i_assert(sync_ctx->write_fd != -1);
+
+ i_assert(sync_ctx->space_diff < 0);
+ padding = MBOX_HEADER_PADDING *
+ (sync_ctx->seq - sync_ctx->need_space_seq + 1);
+ sync_ctx->space_diff -= padding;
+
+ i_assert(sync_ctx->expunged_space <= -sync_ctx->space_diff);
+ sync_ctx->space_diff += sync_ctx->expunged_space;
+ sync_ctx->expunged_space = 0;
+
+ if (mail_ctx->have_eoh && !mail_ctx->updated)
+ str_append_c(mail_ctx->header, '\n');
+
+ i_assert(sync_ctx->space_diff < 0);
+
+ if (mbox_append_zero(sync_ctx, file_size,
+ -sync_ctx->space_diff) < 0)
+ return -1;
+ mbox_sync_file_updated(sync_ctx, FALSE);
+
+ if (mbox_sync_rewrite(sync_ctx, mail_ctx, file_size,
+ -sync_ctx->space_diff, padding,
+ sync_ctx->need_space_seq,
+ sync_ctx->seq) < 0)
+ return -1;
+
+ update_from_offsets(sync_ctx);
+
+ sync_ctx->need_space_seq = 0;
+ array_clear(&sync_ctx->mails);
+ p_clear(sync_ctx->saved_keywords_pool);
+ }
+
+ if (sync_ctx->expunged_space > 0) {
+ i_assert(sync_ctx->write_fd != -1);
+
+ mbox_sync_file_update_ext_modified(sync_ctx);
+
+ /* copy trailer, then truncate the file */
+ file_size = sync_ctx->last_stat.st_size;
+ if (file_size == (uoff_t)sync_ctx->expunged_space) {
+ /* everything deleted, the trailer_size still contains
+ the \n trailer though */
+ trailer_size = 0;
+ } else if (sync_ctx->expunged_space == (off_t)file_size + 1 ||
+ sync_ctx->expunged_space == (off_t)file_size + 2) {
+ /* everything deleted and we didn't have a proper
+ trailer. */
+ trailer_size = 0;
+ sync_ctx->expunged_space = file_size;
+ }
+
+ i_assert(file_size >= sync_ctx->expunged_space + trailer_size);
+ offset = file_size - sync_ctx->expunged_space - trailer_size;
+ i_assert(offset == 0 || offset > 31);
+
+ if (mbox_move(sync_ctx, offset,
+ offset + sync_ctx->expunged_space,
+ trailer_size) < 0)
+ return -1;
+ if (ftruncate(sync_ctx->write_fd,
+ offset + trailer_size) < 0) {
+ mbox_set_syscall_error(sync_ctx->mbox, "ftruncate()");
+ return -1;
+ }
+
+ if (offset == 0) {
+ if (mbox_write_pseudo(sync_ctx, TRUE) < 0)
+ return -1;
+ }
+
+ sync_ctx->expunged_space = 0;
+ mbox_sync_file_updated(sync_ctx, FALSE);
+ } else {
+ if (file_size == 0 && sync_ctx->mbox->sync_hdr_update != NULL) {
+ if (mbox_write_pseudo(sync_ctx, FALSE) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+mbox_sync_index_update_ext_header(struct mbox_mailbox *mbox,
+ struct mail_index_transaction *trans)
+{
+ const struct mailbox_update *update = mbox->sync_hdr_update;
+ const void *data;
+ size_t data_size;
+
+ if (update != NULL && !guid_128_is_empty(update->mailbox_guid)) {
+ memcpy(mbox->mbox_hdr.mailbox_guid, update->mailbox_guid,
+ sizeof(mbox->mbox_hdr.mailbox_guid));
+ } else if (guid_128_is_empty(mbox->mbox_hdr.mailbox_guid)) {
+ guid_128_generate(mbox->mbox_hdr.mailbox_guid);
+ }
+
+ mail_index_get_header_ext(mbox->box.view, mbox->mbox_ext_idx,
+ &data, &data_size);
+ if (data_size != sizeof(mbox->mbox_hdr) ||
+ memcmp(data, &mbox->mbox_hdr, data_size) != 0) {
+ if (data_size != sizeof(mbox->mbox_hdr)) {
+ /* upgrading from v1.x */
+ mail_index_ext_resize(trans, mbox->mbox_ext_idx,
+ sizeof(mbox->mbox_hdr),
+ sizeof(uint64_t),
+ sizeof(uint64_t));
+ }
+ mail_index_update_header_ext(trans, mbox->mbox_ext_idx,
+ 0, &mbox->mbox_hdr,
+ sizeof(mbox->mbox_hdr));
+ }
+}
+
+static uint32_t mbox_get_uidvalidity_next(struct mailbox_list *list)
+{
+ const char *path;
+
+ path = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_CONTROL);
+ path = t_strconcat(path, "/"MBOX_UIDVALIDITY_FNAME, NULL);
+ return mailbox_uidvalidity_next(list, path);
+}
+
+static int mbox_sync_update_index_header(struct mbox_sync_context *sync_ctx)
+{
+ struct mail_index_view *view;
+ const struct stat *st;
+ uint32_t first_recent_uid, seq, seq2;
+
+ if (i_stream_stat(sync_ctx->file_input, FALSE, &st) < 0) {
+ mbox_istream_set_syscall_error(sync_ctx->mbox,
+ sync_ctx->file_input, "i_stream_stat()");
+ return -1;
+ }
+
+ if (sync_ctx->moved_offsets &&
+ ((uint64_t)st->st_size == sync_ctx->mbox->mbox_hdr.sync_size ||
+ (uint64_t)st->st_size == sync_ctx->orig_size)) {
+ /* We moved messages inside the mbox file without changing
+ the file's size. If mtime doesn't change, another process
+ not using the same index file as us can't know that the file
+ was changed. So make sure the mtime changes. This should
+ happen rarely enough that the sleeping doesn't become a
+ performance problem.
+
+ Note that to do this perfectly safe we should do this wait
+ whenever mails are moved or expunged, regardless of whether
+ the file's size changed. That however could become a
+ performance problem and the consequences of being wrong are
+ quite minimal (an extra logged error message). */
+ while (sync_ctx->orig_mtime == st->st_mtime) {
+ i_sleep_msecs(500);
+ if (utime(mailbox_get_path(&sync_ctx->mbox->box), NULL) < 0) {
+ mbox_set_syscall_error(sync_ctx->mbox,
+ "utime()");
+ return -1;
+ }
+
+ if (i_stream_stat(sync_ctx->file_input, FALSE, &st) < 0) {
+ mbox_istream_set_syscall_error(sync_ctx->mbox,
+ sync_ctx->file_input, "i_stream_stat()");
+ return -1;
+ }
+ }
+ }
+
+ sync_ctx->mbox->mbox_hdr.sync_mtime = st->st_mtime;
+ sync_ctx->mbox->mbox_hdr.sync_size = st->st_size;
+ mbox_sync_index_update_ext_header(sync_ctx->mbox, sync_ctx->t);
+
+ /* only reason not to have UID validity at this point is if the file
+ is entirely empty. In that case just make up a new one if needed. */
+ i_assert(sync_ctx->base_uid_validity != 0 || st->st_size <= 0);
+
+ if (sync_ctx->base_uid_validity == 0) {
+ sync_ctx->base_uid_validity = sync_ctx->hdr->uid_validity != 0 ?
+ sync_ctx->hdr->uid_validity :
+ mbox_get_uidvalidity_next(sync_ctx->mbox->box.list);
+ }
+ if (sync_ctx->base_uid_validity != sync_ctx->hdr->uid_validity) {
+ mail_index_update_header(sync_ctx->t,
+ offsetof(struct mail_index_header, uid_validity),
+ &sync_ctx->base_uid_validity,
+ sizeof(sync_ctx->base_uid_validity), TRUE);
+ }
+
+ if (istream_raw_mbox_is_eof(sync_ctx->input) &&
+ sync_ctx->next_uid != sync_ctx->hdr->next_uid) {
+ i_assert(sync_ctx->next_uid != 0);
+ mail_index_update_header(sync_ctx->t,
+ offsetof(struct mail_index_header, next_uid),
+ &sync_ctx->next_uid, sizeof(sync_ctx->next_uid), FALSE);
+ }
+
+ if (sync_ctx->last_nonrecent_uid < sync_ctx->hdr->first_recent_uid) {
+ /* other sessions have already marked more messages as
+ recent. */
+ sync_ctx->last_nonrecent_uid =
+ sync_ctx->hdr->first_recent_uid - 1;
+ }
+
+ /* mark recent messages */
+ view = mail_index_transaction_open_updated_view(sync_ctx->t);
+ if (mail_index_lookup_seq_range(view, sync_ctx->last_nonrecent_uid + 1,
+ (uint32_t)-1, &seq, &seq2)) {
+ mailbox_recent_flags_set_seqs(&sync_ctx->mbox->box,
+ view, seq, seq2);
+ }
+ mail_index_view_close(&view);
+
+ first_recent_uid = !sync_ctx->keep_recent ?
+ sync_ctx->next_uid : sync_ctx->last_nonrecent_uid + 1;
+ if (sync_ctx->hdr->first_recent_uid < first_recent_uid) {
+ mail_index_update_header(sync_ctx->t,
+ offsetof(struct mail_index_header, first_recent_uid),
+ &first_recent_uid, sizeof(first_recent_uid), FALSE);
+ }
+ return 0;
+}
+
+static void mbox_sync_restart(struct mbox_sync_context *sync_ctx)
+{
+ sync_ctx->base_uid_validity = 0;
+ sync_ctx->base_uid_last = 0;
+ sync_ctx->base_uid_last_offset = 0;
+
+ array_clear(&sync_ctx->mails);
+ p_clear(sync_ctx->saved_keywords_pool);
+
+ index_sync_changes_reset(sync_ctx->sync_changes);
+ mail_index_sync_reset(sync_ctx->index_sync_ctx);
+ mail_index_transaction_reset(sync_ctx->t);
+
+ if (sync_ctx->index_reset) {
+ mail_index_reset(sync_ctx->t);
+ sync_ctx->reset_hdr.next_uid = 1;
+ sync_ctx->hdr = &sync_ctx->reset_hdr;
+ mailbox_recent_flags_reset(&sync_ctx->mbox->box);
+ }
+
+ sync_ctx->prev_msg_uid = 0;
+ sync_ctx->next_uid = sync_ctx->hdr->next_uid;
+ sync_ctx->idx_next_uid = sync_ctx->hdr->next_uid;
+ sync_ctx->seq = 0;
+ sync_ctx->idx_seq = 1;
+ sync_ctx->need_space_seq = 0;
+ sync_ctx->expunged_space = 0;
+ sync_ctx->space_diff = 0;
+
+ sync_ctx->dest_first_mail = TRUE;
+ sync_ctx->ext_modified = FALSE;
+ sync_ctx->errors = FALSE;
+}
+
+static int mbox_sync_do(struct mbox_sync_context *sync_ctx,
+ enum mbox_sync_flags flags)
+{
+ struct mbox_index_header *mbox_hdr = &sync_ctx->mbox->mbox_hdr;
+ struct mbox_sync_mail_context mail_ctx;
+ const struct stat *st;
+ unsigned int i;
+ bool partial;
+ int ret;
+
+ if (i_stream_stat(sync_ctx->file_input, FALSE, &st) < 0) {
+ mbox_istream_set_syscall_error(sync_ctx->mbox,
+ sync_ctx->file_input, "i_stream_stat()");
+ return -1;
+ }
+ sync_ctx->last_stat = *st;
+ sync_ctx->orig_size = st->st_size;
+ sync_ctx->orig_atime = st->st_atime;
+ sync_ctx->orig_mtime = st->st_mtime;
+
+ if ((flags & MBOX_SYNC_FORCE_SYNC) != 0) {
+ /* forcing a full sync. assume file has changed. */
+ partial = FALSE;
+ mbox_hdr->dirty_flag = 1;
+ } else if ((uint32_t)st->st_mtime == mbox_hdr->sync_mtime &&
+ (uint64_t)st->st_size == mbox_hdr->sync_size) {
+ /* file is fully synced */
+ if (mbox_hdr->dirty_flag != 0 && (flags & MBOX_SYNC_UNDIRTY) != 0)
+ partial = FALSE;
+ else
+ partial = TRUE;
+ } else if ((flags & MBOX_SYNC_UNDIRTY) != 0 ||
+ (uint64_t)st->st_size == mbox_hdr->sync_size) {
+ /* we want to do full syncing. always do this if
+ file size hasn't changed but timestamp has. it most
+ likely means that someone had modified some header
+ and we probably want to know about it */
+ partial = FALSE;
+ sync_ctx->mbox->mbox_hdr.dirty_flag = 1;
+ } else {
+ /* see if we can delay syncing the whole file.
+ normally we only notice expunges and appends
+ in partial syncing. */
+ partial = TRUE;
+ sync_ctx->mbox->mbox_hdr.dirty_flag = 1;
+ }
+
+ mbox_sync_restart(sync_ctx);
+ for (i = 0;;) {
+ ret = mbox_sync_loop(sync_ctx, &mail_ctx, partial);
+ if (ret > 0 && !sync_ctx->errors)
+ break;
+ if (ret < 0)
+ return -1;
+
+ /* a) partial sync didn't work
+ b) we ran out of UIDs
+ c) syncing had errors */
+ if (sync_ctx->delay_writes &&
+ (sync_ctx->errors || sync_ctx->renumber_uids)) {
+ /* fixing a broken mbox state, be sure to write
+ the changes (except if we're readonly). */
+ if (!sync_ctx->readonly)
+ sync_ctx->delay_writes = FALSE;
+ }
+ if (++i == 3)
+ break;
+
+ mbox_sync_restart(sync_ctx);
+ partial = FALSE;
+ }
+
+ if (mbox_sync_handle_eof_updates(sync_ctx, &mail_ctx) < 0)
+ return -1;
+
+ /* only syncs left should be just appends (and their updates)
+ which weren't synced yet for some reason (crash). we'll just
+ ignore them, as we've overwritten them above. */
+ index_sync_changes_reset(sync_ctx->sync_changes);
+
+ if (sync_ctx->base_uid_last != sync_ctx->next_uid-1 &&
+ ret > 0 && !sync_ctx->delay_writes &&
+ sync_ctx->base_uid_last_offset != 0) {
+ /* Rewrite uid_last in X-IMAPbase header if we've seen it
+ (ie. the file isn't empty) */
+ ret = mbox_rewrite_base_uid_last(sync_ctx);
+ } else {
+ ret = 0;
+ }
+
+ if (mbox_sync_update_index_header(sync_ctx) < 0)
+ return -1;
+ return ret;
+}
+
+int mbox_sync_header_refresh(struct mbox_mailbox *mbox)
+{
+ const void *data;
+ size_t data_size;
+
+ if (mail_index_refresh(mbox->box.index) < 0) {
+ mailbox_set_index_error(&mbox->box);
+ return -1;
+ }
+
+ mail_index_get_header_ext(mbox->box.view, mbox->mbox_ext_idx,
+ &data, &data_size);
+ if (data_size == 0) {
+ /* doesn't exist yet. */
+ i_zero(&mbox->mbox_hdr);
+ return 0;
+ }
+
+ memcpy(&mbox->mbox_hdr, data, I_MIN(sizeof(mbox->mbox_hdr), data_size));
+ if (mbox->mbox_broken_offsets)
+ mbox->mbox_hdr.dirty_flag = 1;
+ return 0;
+}
+
+int mbox_sync_get_guid(struct mbox_mailbox *mbox)
+{
+ struct mail_index_transaction *trans;
+ unsigned int lock_id;
+ int ret;
+
+ if (mbox_lock(mbox, F_WRLCK, &lock_id) <= 0)
+ return -1;
+
+ ret = mbox_sync_header_refresh(mbox);
+ if (ret == 0) {
+ trans = mail_index_transaction_begin(mbox->box.view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ mbox_sync_index_update_ext_header(mbox, trans);
+ ret = mail_index_transaction_commit(&trans);
+ }
+ mbox_unlock(mbox, lock_id);
+ return ret;
+}
+
+int mbox_sync_has_changed(struct mbox_mailbox *mbox, bool leave_dirty)
+{
+ const struct stat *st;
+ struct stat statbuf;
+
+ if (mbox->mbox_file_stream != NULL && mbox->mbox_fd == -1) {
+ /* read-only stream */
+ if (i_stream_stat(mbox->mbox_file_stream, FALSE, &st) < 0) {
+ if (errno == ENOENT) {
+ mailbox_set_deleted(&mbox->box);
+ return 0;
+ }
+ mbox_istream_set_syscall_error(mbox,
+ mbox->mbox_file_stream, "i_stream_stat()");
+ return -1;
+ }
+ } else {
+ if (stat(mailbox_get_path(&mbox->box), &statbuf) < 0) {
+ if (errno == ENOENT) {
+ mailbox_set_deleted(&mbox->box);
+ return 0;
+ }
+ mbox_set_syscall_error(mbox, "stat()");
+ return -1;
+ }
+ st = &statbuf;
+ }
+
+ if (mbox_sync_header_refresh(mbox) < 0)
+ return -1;
+
+ if (guid_128_is_empty(mbox->mbox_hdr.mailbox_guid)) {
+ /* need to assign mailbox GUID */
+ return 1;
+ }
+
+ if ((uint32_t)st->st_mtime == mbox->mbox_hdr.sync_mtime &&
+ (uint64_t)st->st_size == mbox->mbox_hdr.sync_size) {
+ /* fully synced */
+ if (mbox->mbox_hdr.dirty_flag != 0 || leave_dirty)
+ return 0;
+ /* flushing dirtiness */
+ }
+
+ /* file changed */
+ return 1;
+}
+
+static void mbox_sync_context_free(struct mbox_sync_context *sync_ctx)
+{
+ index_sync_changes_deinit(&sync_ctx->sync_changes);
+ index_storage_expunging_deinit(&sync_ctx->mbox->box);
+ if (sync_ctx->index_sync_ctx != NULL)
+ mail_index_sync_rollback(&sync_ctx->index_sync_ctx);
+ pool_unref(&sync_ctx->mail_keyword_pool);
+ pool_unref(&sync_ctx->saved_keywords_pool);
+ str_free(&sync_ctx->header);
+ str_free(&sync_ctx->from_line);
+ array_free(&sync_ctx->mails);
+}
+
+static int mbox_sync_int(struct mbox_mailbox *mbox, enum mbox_sync_flags flags,
+ unsigned int *lock_id)
+{
+ struct mail_index_sync_ctx *index_sync_ctx;
+ struct mail_index_view *sync_view;
+ struct mail_index_transaction *trans;
+ struct mbox_sync_context sync_ctx;
+ enum mail_index_sync_flags sync_flags;
+ int ret;
+ bool changed, delay_writes, readonly;
+
+ readonly = mbox_is_backend_readonly(mbox) ||
+ (flags & MBOX_SYNC_READONLY) != 0;
+ delay_writes = readonly ||
+ ((flags & MBOX_SYNC_REWRITE) == 0 &&
+ mbox->storage->set->mbox_lazy_writes);
+
+ if (!mbox->storage->set->mbox_dirty_syncs &&
+ !mbox->storage->set->mbox_very_dirty_syncs)
+ flags |= MBOX_SYNC_UNDIRTY;
+
+ if ((flags & MBOX_SYNC_LOCK_READING) != 0) {
+ if (mbox_lock(mbox, F_RDLCK, lock_id) <= 0)
+ return -1;
+ }
+
+ if ((flags & MBOX_SYNC_HEADER) != 0 ||
+ (flags & MBOX_SYNC_FORCE_SYNC) != 0) {
+ if (mbox_sync_header_refresh(mbox) < 0)
+ return -1;
+ changed = TRUE;
+ } else {
+ bool leave_dirty = (flags & MBOX_SYNC_UNDIRTY) == 0;
+ if ((ret = mbox_sync_has_changed(mbox, leave_dirty)) < 0)
+ return -1;
+ changed = ret > 0;
+ }
+
+ if ((flags & MBOX_SYNC_LOCK_READING) != 0) {
+ /* we just want to lock it for reading. if mbox hasn't been
+ modified don't do any syncing. */
+ if (!changed)
+ return 0;
+
+ /* have to sync to make sure offsets have stayed the same */
+ mbox_unlock(mbox, *lock_id);
+ *lock_id = 0;
+ }
+
+ /* flush input streams' buffers */
+ if (mbox->mbox_stream != NULL)
+ i_stream_sync(mbox->mbox_stream);
+ if (mbox->mbox_file_stream != NULL)
+ i_stream_sync(mbox->mbox_file_stream);
+
+again:
+ if (changed) {
+ /* we're most likely modifying the mbox while syncing, just
+ lock it for writing immediately. the mbox must be locked
+ before index syncing is started to avoid deadlocks, so we
+ don't have much choice either (well, easy ones anyway). */
+ int lock_type = readonly ? F_RDLCK : F_WRLCK;
+
+ if ((ret = mbox_lock(mbox, lock_type, lock_id)) <= 0) {
+ if (ret == 0 || lock_type == F_RDLCK)
+ return -1;
+
+ /* try as read-only */
+ if (mbox_lock(mbox, F_RDLCK, lock_id) <= 0)
+ return -1;
+ mbox->backend_readonly = readonly = TRUE;
+ mbox->backend_readonly_set = TRUE;
+ delay_writes = TRUE;
+ }
+ }
+
+ sync_flags = index_storage_get_sync_flags(&mbox->box);
+ if ((flags & MBOX_SYNC_REWRITE) != 0)
+ sync_flags |= MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY;
+
+ ret = index_storage_expunged_sync_begin(&mbox->box, &index_sync_ctx,
+ &sync_view, &trans, sync_flags);
+ if (ret <= 0)
+ return ret;
+
+ if ((mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) != 0) {
+ /* see if we need to drop recent flags */
+ sync_ctx.hdr = mail_index_get_header(sync_view);
+ if (sync_ctx.hdr->first_recent_uid < sync_ctx.hdr->next_uid)
+ changed = TRUE;
+ }
+
+ if (!changed && !mail_index_sync_have_more(index_sync_ctx)) {
+ /* nothing to do */
+ nothing_to_do:
+ /* index may need to do internal syncing though, so commit
+ instead of rolling back. */
+ index_storage_expunging_deinit(&mbox->box);
+ if (mail_index_sync_commit(&index_sync_ctx) < 0) {
+ mailbox_set_index_error(&mbox->box);
+ return -1;
+ }
+ return 0;
+ }
+
+ i_zero(&sync_ctx);
+ sync_ctx.mbox = mbox;
+ sync_ctx.keep_recent =
+ (mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) == 0;
+
+ sync_ctx.hdr = mail_index_get_header(sync_view);
+ sync_ctx.from_line = str_new(default_pool, 256);
+ sync_ctx.header = str_new(default_pool, 4096);
+
+ sync_ctx.index_sync_ctx = index_sync_ctx;
+ sync_ctx.sync_view = sync_view;
+ sync_ctx.t = trans;
+ sync_ctx.mail_keyword_pool =
+ pool_alloconly_create("mbox keywords", 512);
+ sync_ctx.saved_keywords_pool =
+ pool_alloconly_create("mbox saved keywords", 4096);
+
+ /* make sure we've read the latest keywords in index */
+ (void)mail_index_get_keywords(mbox->box.index);
+
+ i_array_init(&sync_ctx.mails, 64);
+
+ sync_ctx.flags = flags;
+ sync_ctx.readonly = readonly;
+ sync_ctx.delay_writes = delay_writes;
+
+ sync_ctx.sync_changes =
+ index_sync_changes_init(index_sync_ctx, sync_view, trans,
+ sync_ctx.delay_writes);
+
+ if (!changed && delay_writes) {
+ /* if we have only flag changes, we don't need to open the
+ mbox file */
+ bool expunged;
+ uint32_t uid;
+
+ mbox_sync_read_index_syncs(&sync_ctx, 1, &expunged);
+ uid = expunged ? 1 :
+ index_sync_changes_get_next_uid(sync_ctx.sync_changes);
+ if (uid == 0) {
+ sync_ctx.index_sync_ctx = NULL;
+ mbox_sync_context_free(&sync_ctx);
+ goto nothing_to_do;
+ }
+ }
+
+ if (*lock_id == 0) {
+ /* ok, we have something to do but no locks. we'll have to
+ restart syncing to avoid deadlocking. */
+ mbox_sync_context_free(&sync_ctx);
+ changed = TRUE;
+ goto again;
+ }
+
+ if (mbox_file_open_stream(mbox) < 0) {
+ mbox_sync_context_free(&sync_ctx);
+ return -1;
+ }
+
+ sync_ctx.file_input = sync_ctx.mbox->mbox_file_stream;
+ sync_ctx.input = sync_ctx.mbox->mbox_stream;
+ sync_ctx.write_fd = sync_ctx.mbox->mbox_lock_type != F_WRLCK ? -1 :
+ sync_ctx.mbox->mbox_fd;
+
+ ret = mbox_sync_do(&sync_ctx, flags);
+
+ if (ret < 0)
+ mail_index_sync_rollback(&index_sync_ctx);
+ else if (mail_index_sync_commit(&index_sync_ctx) < 0) {
+ mailbox_set_index_error(&mbox->box);
+ ret = -1;
+ }
+ sync_ctx.t = NULL;
+ sync_ctx.index_sync_ctx = NULL;
+
+ if (ret == 0 && mbox->mbox_fd != -1 && sync_ctx.keep_recent &&
+ !readonly) {
+ /* try to set atime back to its original value.
+ (it'll fail with EPERM for shared mailboxes where we aren't
+ the file's owner) */
+ struct utimbuf buf;
+ struct stat st;
+
+ if (fstat(mbox->mbox_fd, &st) < 0)
+ mbox_set_syscall_error(mbox, "fstat()");
+ else {
+ buf.modtime = st.st_mtime;
+ buf.actime = sync_ctx.orig_atime;
+ if (utime(mailbox_get_path(&mbox->box), &buf) < 0 &&
+ errno != EPERM)
+ mbox_set_syscall_error(mbox, "utime()");
+ }
+ }
+
+ i_assert(*lock_id != 0);
+
+ if (mbox->storage->storage.set->mail_nfs_storage &&
+ mbox->mbox_fd != -1) {
+ if (fdatasync(mbox->mbox_fd) < 0) {
+ mbox_set_syscall_error(mbox, "fdatasync()");
+ ret = -1;
+ }
+ }
+
+ mbox_sync_context_free(&sync_ctx);
+ return ret;
+}
+
+int mbox_sync(struct mbox_mailbox *mbox, enum mbox_sync_flags flags)
+{
+ unsigned int lock_id = 0;
+ int ret;
+
+ i_assert(mbox->mbox_lock_type != F_RDLCK ||
+ (flags & MBOX_SYNC_READONLY) != 0);
+
+ mbox->syncing = TRUE;
+ ret = mbox_sync_int(mbox, flags, &lock_id);
+ mbox->syncing = FALSE;
+
+ if (lock_id != 0) {
+ if (ret < 0) {
+ /* syncing failed, don't leave it locked */
+ mbox_unlock(mbox, lock_id);
+ } else if ((flags & MBOX_SYNC_LOCK_READING) == 0) {
+ if (mbox_unlock(mbox, lock_id) < 0)
+ ret = -1;
+ } else if (mbox->mbox_lock_type != F_RDLCK) {
+ /* drop to read lock */
+ unsigned int read_lock_id = 0;
+
+ if (mbox_lock(mbox, F_RDLCK, &read_lock_id) <= 0)
+ ret = -1;
+ if (mbox_unlock(mbox, lock_id) < 0)
+ ret = -1;
+ }
+ }
+
+ mailbox_sync_notify(&mbox->box, 0, 0);
+ return ret;
+}
+
+struct mailbox_sync_context *
+mbox_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+ struct mbox_mailbox *mbox = MBOX_MAILBOX(box);
+ enum mbox_sync_flags mbox_sync_flags = 0;
+ int ret = 0;
+
+ if (index_mailbox_want_full_sync(&mbox->box, flags)) {
+ if ((flags & MAILBOX_SYNC_FLAG_FULL_READ) != 0 &&
+ !mbox->storage->set->mbox_very_dirty_syncs)
+ mbox_sync_flags |= MBOX_SYNC_UNDIRTY;
+ if ((flags & MAILBOX_SYNC_FLAG_FULL_WRITE) != 0)
+ mbox_sync_flags |= MBOX_SYNC_REWRITE;
+ if ((flags & MAILBOX_SYNC_FLAG_FORCE_RESYNC) != 0) {
+ mbox_sync_flags |= MBOX_SYNC_UNDIRTY |
+ MBOX_SYNC_REWRITE | MBOX_SYNC_FORCE_SYNC;
+ }
+
+ ret = mbox_sync(mbox, mbox_sync_flags);
+ }
+
+ return index_mailbox_sync_init(box, flags, ret < 0);
+}