mbox-sync.c revision 2d39dc1a453546892109b35c0d9770369011a13d
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose/* Copyright (C) 2004 Timo Sirainen */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose Modifying mbox can be slow, so we try to do it all at once minimizing the
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose required disk I/O. We may need to:
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Update message flags in Status, X-Status and X-Keywords headers
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Write missing X-UID and X-IMAPbase headers
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Write missing or broken Content-Length header if there's space
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Expunge specified messages
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose Here's how we do it:
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Start reading the mails from the beginning
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - X-Keywords, X-UID and X-IMAPbase headers may contain padding at the end
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose of them, remember how much each message has and offset to beginning of the
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - If header needs to be rewritten and there's enough space, do it
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - If we didn't have enough space, remember how much was missing
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Continue reading and counting the padding in each message. If available
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose padding is enough to rewrite all the previous messages needing it, do it
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - When we encounter expunged message, treat all of it as padding and
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose rewrite previous messages if needed (and there's enough space).
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose Afterwards keep moving messages backwards to fill the expunged space.
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose Moving is done by rewriting each message's headers, with possibly adding
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose missing Content-Length header and padding. Message bodies are moved
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose without modifications.
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - If we encounter end of file, grow the file and rewrite needed messages
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose - Rewriting is done by moving message body forward, rewriting message's
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose header and doing the same for previous message, until all of them are
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose/* The text below was taken exactly as c-client wrote it to my mailbox,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose so it's probably copyrighted by University of Washington. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose"This text is part of the internal format of your mail folder, and is not\n" \
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov"a real message. It is created automatically by the mail system software.\n" \
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose"If deleted, important folder data will be lost, and it will be re-created\n" \
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose"with the data reset to initial values.\n"
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosevoid mbox_sync_set_critical(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose const char *fmt, ...)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_critical(&sync_ctx->mbox->storage->storage,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "mbox file %s was modified while we were syncing, "
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "check your locking settings", sync_ctx->mbox->path);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_critical(&sync_ctx->mbox->storage->storage,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Boseint mbox_sync_seek(struct mbox_sync_context *sync_ctx, uoff_t from_offset)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (istream_raw_mbox_seek(sync_ctx->input, from_offset) < 0) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "Unexpectedly lost From-line at offset %"PRIuUOFF_T
401d8b0600dd2d36f6d62ee1d72f56a245cc3158Jakub Hrozekvoid mbox_sync_file_update_ext_modified(struct mbox_sync_context *sync_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* Do this even if ext_modified is already set. Expunging code relies
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose on last_stat being updated. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_set_syscall_error(sync_ctx->mbox, "fstat()");
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* nanoseconds give better precision to this check if they're
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose supported by the OS */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose || st.st_mtim.tv_nsec != sync_ctx->last_stat.st_mtim.tv_nsec
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosevoid mbox_sync_file_updated(struct mbox_sync_context *sync_ctx, bool dirty)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* just mark the stat as dirty. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (fstat(sync_ctx->write_fd, &sync_ctx->last_stat) < 0)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_set_syscall_error(sync_ctx->mbox, "fstat()");
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic void mbox_sync_array_delete_to(ARRAY_TYPE(sync_recs) *syncs_arr,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* keep it */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_sync_read_next_mail(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* get EOF */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose (void)istream_raw_mbox_get_header_offset(sync_ctx->input);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose istream_raw_mbox_get_start_offset(sync_ctx->input);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose istream_raw_mbox_get_header_offset(sync_ctx->input);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_sync_parse_next_mail(sync_ctx->input, mail_ctx);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose i_assert(sync_ctx->input->v_offset != mail_ctx->mail.from_offset ||
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* need to add 'O' flag to Status-header */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic bool mbox_sync_buf_have_expunges(ARRAY_TYPE(sync_recs) *syncs_arr)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose unsigned int i, count;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose for (i = 0; i < count; i++) {
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov if (syncs[i].type == MAIL_INDEX_SYNC_TYPE_EXPUNGE)
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashovstatic int mbox_sync_read_index_syncs(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mail_index_sync_rec *sync_rec = &sync_ctx->sync_rec;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* nothing for this or the future ones */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_sync_array_delete_to(&sync_ctx->syncs, uid);
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov (sync_rec->type != MAIL_INDEX_SYNC_TYPE_EXPUNGE ||
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_EXPUNGE)
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov ret = mail_index_sync_next(sync_ctx->index_sync_ctx, sync_rec);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov if (sync_rec->uid2 >= sync_ctx->next_uid)
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* we're not going to write these yet */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose *sync_expunge_r = mbox_sync_buf_have_expunges(&sync_ctx->syncs);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosevoid mbox_sync_apply_index_syncs(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose unsigned int i, count;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose for (i = 0; i < count; i++) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_sync_flags_apply(&syncs[i], &mail->flags);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* no existing keywords */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* adding, create the array */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_sync_read_index_rec(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose uint32_t uid, const struct mail_index_record **rec_r)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_view_get_messages_count(sync_ctx->sync_view);
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov while (sync_ctx->idx_seq <= messages_count) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose i_assert(ret != 0); /* we should be looking at head index */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* externally expunged message, remove from index */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (rec == NULL && uid < sync_ctx->idx_next_uid) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* this UID was already in index and it was expunged */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "mbox sync: Expunged message reappeared in mailbox %s "
d115f40c7a3999e3cbe705a2ff9cf0fd493f80fbMichal Zidek "(UID %u < %u, seq=%u, idx_msgs=%u)",
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose sync_ctx->mbox->path, uid, sync_ctx->idx_next_uid,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* new UID in the middle of the mailbox - shouldn't happen */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "mbox sync: UID inserted in the middle of mailbox %s "
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "(%u > %u, seq=%u, idx_msgs=%u)", sync_ctx->mbox->path,
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov rec->uid, uid, sync_ctx->seq, messages_count);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic int mbox_sync_find_index_md5(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose unsigned char hdr_md5_sum[],
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose const void *data;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_view_get_messages_count(sync_ctx->sync_view);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (data != NULL && memcmp(data, hdr_md5_sum, 16) == 0)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* externally expunged message, remove from index */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_expunge(sync_ctx->t, sync_ctx->idx_seq);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_sync_update_from_offset(struct mbox_sync_context *sync_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose const void *data;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* see if from_offset needs updating */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov sync_ctx->mbox->mbox_ext_idx, &offset, NULL);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_sync_update_index_keywords(struct mbox_sync_mail_context *mail_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose keywords = !array_is_created(&mail_ctx->mail.keywords) ?
63748c69a2c6785d949c82f94749704e0408e5a7Sumit Bose mail_index_keywords_create_from_indexes(sync_ctx->t,
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov mail_index_update_keywords(sync_ctx->t, sync_ctx->idx_seq,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_sync_update_md5_if_changed(struct mbox_sync_mail_context *mail_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (mail_index_lookup_ext(sync_ctx->sync_view, sync_ctx->idx_seq,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose memcmp(mail_ctx->hdr_md5_sum, ext_data, 16) != 0) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic int mbox_sync_update_index(struct mbox_sync_mail_context *mail_ctx,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov struct mailbox *box = &sync_ctx->mbox->ibox.box;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* new message */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_append(sync_ctx->t, mail->uid, &sync_ctx->idx_seq);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq,
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov mbox_sync_update_index_keywords(mail_ctx);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_update_ext(sync_ctx->t, sync_ctx->idx_seq,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* see if we need to update flags in index file. the flags in
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose sync records are automatically applied to rec->flags at the
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose end of index syncing, so calculate those new flags first */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* get old keywords */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (mail_index_lookup_keywords(sync_ctx->sync_view,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_storage_set_index_error(&sync_ctx->mbox->ibox);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (sync_type != 0 && box->v.sync_notify != NULL)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose#define SYNC_FLAGS (MAIL_RECENT | MAIL_INDEX_MAIL_FLAG_DIRTY)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if ((idx_mail.flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* flags are dirty. ignore whatever was in the mbox,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose but update recent/dirty flag states if needed. */
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov mbox_flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* keep index's internal flags */
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov /* flags other than recent/dirty have changed */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_update_flags(sync_ctx->t, sync_ctx->idx_seq,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* drop recent flag (it can only be dropped) */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* dirty flag state changed */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if ((idx_mail.flags & MAIL_INDEX_MAIL_FLAG_DIRTY) == 0 &&
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* see if we need to update md5 sum. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (mbox_sync_update_md5_if_changed(mail_ctx) < 0)
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov (rec == NULL || (rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) == 0 ||
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov /* update from_offsets, but not if we're going to rewrite this message.
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose rewriting would just move it anyway. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose bool nocheck = rec == NULL || sync_ctx->expunged_space > 0;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (mbox_sync_update_from_offset(sync_ctx, mail, nocheck) < 0)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic int mbox_read_from_line(struct mbox_sync_mail_context *ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct istream *input = ctx->sync_ctx->file_input;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose const unsigned char *data;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose buffer_set_used_size(ctx->sync_ctx->from_line, 0);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose from_line_size = ctx->hdr_offset - ctx->mail.from_offset;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose buffer_append(ctx->sync_ctx->from_line, data, size);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic int mbox_rewrite_base_uid_last(struct mbox_sync_context *sync_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose unsigned int i;
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov i_assert(sync_ctx->base_uid_last_offset != 0);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* first check that the 10 bytes are there and they're exactly as
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose expected. just an extra safety check to make sure we never write
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose to wrong location in the mbox file. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose ret = pread_full(sync_ctx->write_fd, buf, sizeof(buf),
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_set_syscall_error(sync_ctx->mbox, "pread_full()");
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "X-IMAPbase uid-last unexpectedly points outside "
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose for (i = 0, uid_last = 0; i < sizeof(buf); i++) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose "X-IMAPbase uid-last unexpectedly lost in mbox file %s",
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov /* and write it */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose str = t_strdup_printf("%010u", sync_ctx->next_uid - 1);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose sync_ctx->base_uid_last = sync_ctx->next_uid - 1;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_write_from_line(struct mbox_sync_mail_context *ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (pwrite_full(ctx->sync_ctx->write_fd, str_data(str), str_len(str),
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mbox_set_syscall_error(ctx->sync_ctx->mbox, "pwrite_full()");
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic void update_from_offsets(struct mbox_sync_context *sync_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose unsigned int i, count;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose for (i = 0; i < count; i++) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_update_ext(sync_ctx->t, mails[i].idx_seq,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic void mbox_sync_handle_expunge(struct mbox_sync_mail_context *mail_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_index_expunge(sync_ctx->t, mail_ctx->mail.idx_seq);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_ctx->mail.offset = mail_ctx->mail.from_offset;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose mail_ctx->body_offset - mail_ctx->mail.from_offset +
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* expunging first message, fix space to contain next
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose message's \n header too since it will be removed. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (istream_raw_mbox_has_crlf_ending(sync_ctx->input)) {
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov /* uid-last offset is invalid now */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose sync_ctx->expunged_space += mail_ctx->mail.space;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosestatic int mbox_sync_handle_header(struct mbox_sync_mail_context *mail_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (sync_ctx->expunged_space > 0 && sync_ctx->need_space_seq == 0) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* move the header backwards to fill expunged space */
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov /* we're moving this mail to beginning of file.
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov skip the initial \n (it's already counted in
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose expunged_space) */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* read the From-line before rewriting overwrites it */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose ret = mbox_sync_try_rewrite(mail_ctx, move_diff);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* rewrite successful, write From-line to
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose new location */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* didn't have enough space, move the offset
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose back so seeking into it doesn't fail */
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov /* mark it dirty and do it later */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if ((ret = mbox_sync_try_rewrite(mail_ctx, 0)) < 0)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* nothing to do */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* first mail with no space to write it */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* create dummy message to describe the expunged data */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose i_assert(sync_ctx->space_diff < -mail_ctx->mail.space);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bosembox_sync_handle_missing_space(struct mbox_sync_mail_context *mail_ctx)
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose struct mbox_sync_context *sync_ctx = mail_ctx->sync_ctx;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose uoff_t end_offset, move_diff, extra_space, needed_space;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose i_assert(mail_ctx->mail.uid == 0 || mail_ctx->mail.space > 0 ||
a3c8390d19593b1e5277d95bfb4ab206d4785150Nikolai Kondrashov mail_ctx->mail.offset == mail_ctx->hdr_offset);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if (array_is_created(&mail_ctx->mail.keywords)) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* mail's keywords are allocated from a pool that's cleared
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose for each mail. we'll need to copy it to something more
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose permanent. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose p_array_init(&keywords_copy, sync_ctx->saved_keywords_pool,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose array_append_array(&keywords_copy, &mail_ctx->mail.keywords);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose array_append(&sync_ctx->mails, &mail_ctx->mail, 1);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* we have enough space now */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* this message was expunged. fill more or less of the space.
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose space_diff now consists of a negative "bytes needed" sum,
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose plus the expunged space of this message. so it contains how
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose many bytes of _extra_ space we have. */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose i_assert(mail_ctx->mail.space >= sync_ctx->space_diff);
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose needed_space = mail_ctx->mail.space - sync_ctx->space_diff;
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose if ((uoff_t)sync_ctx->space_diff > needed_space + extra_space) {
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* don't waste too much on padding */
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose /* this message gave enough space from headers. rewriting stops
caee9828ee30609e9f433957dbb3d0163390a207Sumit Bose at the end of this message's headers. */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* mail_ctx may contain wrong data after rewrite, so make sure we
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek don't try to access it */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozekmbox_sync_seek_to_seq(struct mbox_sync_context *sync_ctx, uint32_t seq)
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek if (istream_raw_mbox_seek(mbox->mbox_stream, 0) < 0) {
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mail_storage_set_error(&mbox->storage->storage,
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "Mailbox isn't a valid mbox file");
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek old_offset = istream_raw_mbox_get_start_offset(sync_ctx->input);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek ret = mbox_file_seek(mbox, sync_ctx->sync_view, seq, &deleted);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "Message was expunged unexpectedly "
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "Error seeking back to original "
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "offset %s in mbox file %s",
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek else if (mail_index_lookup_uid(sync_ctx->sync_view, seq-1, &uid) < 0) {
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* set to -1, since it's always increased later */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek istream_raw_mbox_get_start_offset(sync_ctx->input) != 0) {
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* this mbox has pseudo mail which contains the X-IMAP header */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek sync_ctx->dest_first_mail = sync_ctx->seq == 0;
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek (void)istream_raw_mbox_get_body_offset(sync_ctx->input);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozekmbox_sync_seek_to_uid(struct mbox_sync_context *sync_ctx, uint32_t uid)
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek struct mail_index_view *sync_view = sync_ctx->sync_view;
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek if (mail_index_lookup_uid_range(sync_view, uid, (uint32_t)-1,
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mail_storage_set_index_error(&sync_ctx->mbox->ibox);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* doesn't exist anymore, seek to end of file */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek st = i_stream_stat(sync_ctx->file_input, TRUE);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "i_stream_stat()");
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek if (istream_raw_mbox_seek(sync_ctx->mbox->mbox_stream,
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "Error seeking to end of mbox file %s",
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mail_index_view_get_messages_count(sync_view) + 1;
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozekstatic int mbox_sync_partial_seek_next(struct mbox_sync_context *sync_ctx,
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* delete sync records up to next message. so if there's still
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek something left in array, it means the next message needs modifying */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mbox_sync_array_delete_to(&sync_ctx->syncs, next_uid);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* we can skip forward to next record which needs updating. */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek ret = mbox_sync_seek_to_uid(sync_ctx, next_uid);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* if there's no sync records left, we can stop. except if
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek this is a dirty sync, check if there are new messages. */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mail_index_view_get_messages_count(sync_ctx->sync_view);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek ret = mbox_sync_seek_to_seq(sync_ctx, messages_count);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* seek failed because the offset is dirty. just ignore and
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek continue from where we are now. */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozekstatic int mbox_sync_loop(struct mbox_sync_context *sync_ctx,
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mail_index_view_get_messages_count(sync_ctx->sync_view);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* always start from first message so we can read X-IMAP or
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek X-IMAPbase header */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* expunge everything */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek while ((ret = mbox_sync_read_next_mail(sync_ctx, mail_ctx)) > 0) {
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek if (mail_ctx->seq == 1 && sync_ctx->base_uid_validity != 0 &&
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "UIDVALIDITY changed (%u -> %u) "
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "in mbox file %s",
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek mail_index_mark_corrupted(sync_ctx->mbox->ibox.index);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* UID ordering problems, resync everything to make
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek sure we get everything right */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek "UIDs broken with partial sync in mbox file %s",
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek ret = mbox_sync_read_index_rec(sync_ctx, uid, &rec);
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* UID found but it's broken */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek } else if (uid == 0 &&
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* If we can't use/store X-UID header, use MD5 sum.
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek Also check for existing MD5 sums when we're actually
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek able to write X-UIDs. */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* get all sync records related to this message. with pseudo
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek message just get the first sync record so we can jump to
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek it with partial seeking. */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* if it was set, it was for the next message */
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek /* message wasn't found from index. we have to
e2d96566aeb881bd89e5c9236d663f6a9a88019aJakub Hrozek read everything from now on, no skipping */
if (!expunged) {
if (!expunged) {
if (!expunged) {
} else if (partial) {
&partial,
if (ret <= 0) {
if (ret < 0)
if (!skipped_mails)
unsigned int uid_validity;
trailer_size = 0;
trailer_size) < 0)
if (offset == 0) {
(unsigned int)ioloop_time;
if (ret > 0)
if (ret < 0)
unsigned int lock_id = 0;
bool delay_writes;
if (!changed)
lock_id = 0;
if (changed) {
sync_flags = 0;
if (ret <= 0) {
if (ret < 0)
if (lock_id != 0)
return ret;
if (lock_id != 0)
bool expunged;
goto __nothing_to_do;
if (lock_id == 0) {
goto __again;
if (ret < 0)
unsigned int read_lock_id = 0;
return ret;
int ret;
return ret;
struct mailbox_sync_context *
int ret = 0;
ioloop_time) {