mbox-sync.c revision 96c253a039f102fa78a313ee05200ab3970112dc
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* Copyright (C) 2004 Timo Sirainen */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Modifying mbox can be slow, so we try to do it all at once minimizing the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen required disk I/O. We may need to:
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Update message flags in Status, X-Status and X-Keywords headers
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Write missing X-UID and X-IMAPbase headers
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Write missing or broken Content-Length header if there's space
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Expunge specified messages
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Here's how we do it:
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Start reading the mails mail headers from the beginning
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - X-Keywords and X-UID headers may contain extra spaces at the end of them,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen remember how much extra each message has and offset to beginning of the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If message flags are dirty and there's enough space to write them, do it
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If we didn't have enough space, remember how much was missing and keep
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen the total amount of them
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - When we encounter expunged message, check if the amount of empty space in
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen previous messages plus size of expunged message is enough to cover the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen missing space. If yes,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - execute the rewrite plan
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - forget all the messages before the expunged message. only remember
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen how much data we still have to move to cover the expunged message
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If we encounter end of file, grow the file and execute the rewrite plan
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Rewrite plan goes:
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Start from the first message that needs more space
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If there's expunged messages before us, we have to write over them.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Move all messages after it backwards to fill it
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - Each moved message's X-Keywords header should have n bytes extra
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen space, unless there's not enough space to do it.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If there's no expunged messages, we can move data either forward or
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen backward to get it. Calculate which requires less moving. Forward
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen counting may encounter more messages which require extra space, count
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If we decide to move forwards and we had to go through dirty
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen messages, do the moving from last to first dirty message
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - If we encounter end of file, grow the file enough to get the required
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen amount of space plus enough space to fill X-Keywords headers full of
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic int mbox_sync_grow_file(struct mbox_sync_context *sync_ctx,
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen /* put the extra space between last message's header and body */
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen file_size = i_stream_get_size(sync_ctx->file_input) + grow_size;
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if (file_set_size(sync_ctx->fd, file_size) < 0)
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if (mbox_move(sync_ctx, mail_ctx->body_offset, src_offset,
659fe5d24825b160cae512538088020d97a60239Timo Sirainenstatic void mbox_sync_buffer_delete_old(buffer_t *syncs_buf, uint32_t uid)
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen sync = buffer_get_modifyable_data(syncs_buf, &size);
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen buffer_set_used_size(syncs_buf, dest * sizeof(*sync));
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainenmbox_sync_next_mail(struct mbox_sync_context *sync_ctx,
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen struct mbox_sync_mail_context *mail_ctx, uint32_t seq)
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen istream_raw_mbox_get_start_offset(sync_ctx->input);
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen istream_raw_mbox_get_header_offset(sync_ctx->input);
96c253a039f102fa78a313ee05200ab3970112dcTimo Sirainen if (seq > 1 && sync_ctx->first_uid == mail_ctx->mail.uid) {
96c253a039f102fa78a313ee05200ab3970112dcTimo Sirainen /* First message was expunged and this is the next one.
96c253a039f102fa78a313ee05200ab3970112dcTimo Sirainen Skip \n header */
3cfff0ca01961d885bdbd6ef08d761880116af07Timo Sirainen mbox_sync_parse_next_mail(sync_ctx->input, mail_ctx, FALSE);
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen i_assert(sync_ctx->input->v_offset != mail_ctx->from_offset);
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen istream_raw_mbox_get_body_size(sync_ctx->input,
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen /* save the offset permanently with recent flag state */
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen from_offset = (mail_ctx->from_offset - sync_ctx->expunged_space) << 1;
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if ((mail_ctx->mail.flags & MBOX_NONRECENT) == 0) {
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen /* need to add 'O' flag to Status-header */
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen buffer_append(sync_ctx->ibox->mbox_data_buf, &from_offset,
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainenstatic void mbox_sync_apply_index_syncs(buffer_t *syncs_buf, uint8_t *flags,
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen for (i = 0; i < size; i++)
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen mail_index_sync_flags_apply(&sync[i], flags, keywords);
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainenstatic int mbox_read_from_line(struct mbox_sync_mail_context *ctx)
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen struct istream *input = ctx->sync_ctx->file_input;
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen const unsigned char *data;
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen buffer_set_used_size(ctx->sync_ctx->from_line, 0);
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen from_line_size = ctx->hdr_offset - ctx->from_offset;
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen buffer_append(ctx->sync_ctx->from_line, data, size);
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainenmbox_write_from_line(struct mbox_sync_mail_context *ctx, off_t move_diff)
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen if (pwrite_full(ctx->sync_ctx->fd, str_data(str), str_len(str),
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen // FIXME: error handling
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainenstatic int mbox_sync_do(struct index_mailbox *ibox,
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen buffer_t *syncs, struct mail_index_sync_rec *sync_rec)
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen uint32_t seq, need_space_seq, idx_seq, messages_count;
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen t = mail_index_transaction_begin(sync_view, FALSE);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen buffer_create_dynamic(default_pool, 512, (size_t)-1);
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen sync_ctx.from_line = str_new(default_pool, 256);
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen sync_ctx.header = str_new(default_pool, 4096);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen mails = buffer_create_dynamic(default_pool, 4096, (size_t)-1);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen messages_count = mail_index_view_get_message_count(sync_view);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen space_diff = 0; need_space_seq = 0; idx_seq = 0; rec = NULL;
3cfff0ca01961d885bdbd6ef08d761880116af07Timo Sirainen for (seq = 0;;) {
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen /* set input->eof */
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen (void)istream_raw_mbox_get_header_offset(input);
659fe5d24825b160cae512538088020d97a60239Timo Sirainen mbox_sync_next_mail(&sync_ctx, &mail_ctx, seq);
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen /* get all sync records related to this message */
659fe5d24825b160cae512538088020d97a60239Timo Sirainen mbox_sync_buffer_delete_old(syncs, mail_ctx.mail.uid);
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen while (mail_ctx.mail.uid >= sync_rec->uid1 && ret > 0) {
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen i_assert(mail_ctx.mail.uid <= sync_rec->uid2);
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen ret = mail_index_sync_next(index_sync_ctx, sync_rec);
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if (seq == 1 && sync_ctx.base_uid_validity == 0) {
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if (mail_index_get_header(sync_view, &hdr) < 0) {
fcfd317f7eb1f0216764c75c5fab3555020552d4Timo Sirainen hdr->uid_validity == 0 ? (uint32_t)ioloop_time :
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen if ((mail_ctx.need_rewrite || sync_ctx.expunged_space > 0 ||
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen buffer_get_used_size(syncs) != 0) && !ibox->readonly) {
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen sync_ctx.expunged_space += mail_ctx.mail.space;
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen /* read the From-line */
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen /* first mail with no space to write it */
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen /* create dummy message to describe
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen the expunged data */
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen /* update index */
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen if (rec != NULL && rec->uid >= mail_ctx.mail.uid)
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen ret = mail_index_lookup(sync_view, ++idx_seq, &rec);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen } while (ret == 0);
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen if (rec != NULL && rec->uid != mail_ctx.mail.uid) {
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen /* new UID in the middle of the mailbox -
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen shouldn't happen */
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen "mbox sync: UID inserted in the middle "
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen "of mailbox (%u > %u)",
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen /* see if flags changed */
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen mbox_sync_apply_index_syncs(syncs, &old_flags,
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen /* we used this record */
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen /* new message */
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen mail_index_append(t, mail_ctx.mail.uid, &idx_seq);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen mail_index_update_flags(t, idx_seq, MODIFY_REPLACE,
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen istream_raw_mbox_next(input, mail_ctx.mail.body_size);
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen offset = istream_raw_mbox_get_start_offset(input);
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen if (sync_ctx.expunged_space > 0 && !sync_expunge &&
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen /* move the body */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* we have enough space now */
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen /* don't waste too much on extra
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen /* mail_ctx may contain wrong data after
313fe89df4d91cd0cd7f3558dc6d7fd21ad39eeeTimo Sirainen rewrite, so make sure we don't try to access
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen trailer_size = i_stream_get_size(sync_ctx.file_input) - offset;
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen mbox_sync_grow_file(&sync_ctx, &mail_ctx, -space_diff) < 0)
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen else if (mbox_sync_try_rewrite(&mail_ctx, 0) < 0)
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen buffer_set_used_size(mails, (seq-need_space_seq) *
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if (mbox_sync_rewrite(&sync_ctx, mails, need_space_seq,
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen /* copy trailer, then truncate the file */
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen offset = i_stream_get_size(sync_ctx.file_input) -
e68309fcfa2eaa88217fd51e7b4900fc9c20ef5dTimo Sirainen else if (ftruncate(ibox->mbox_fd, offset + trailer_size) < 0)
fc1696e32dd732a5bbabc3c8f64810448e327043Timo Sirainen if (sync_ctx.base_uid_last+1 != sync_ctx.next_uid) {
fc1696e32dd732a5bbabc3c8f64810448e327043Timo Sirainen // FIXME: rewrite X-IMAPbase header
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen /* only syncs left should be just appends (and their updates)
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen which weren't synced yet for some reason (crash). we'll just
ab6315aa0d5c83f4f1dc98b3715826a686aebffdTimo Sirainen ignore them, as we've overwritten them above. */
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen while (mail_index_sync_next(index_sync_ctx, sync_rec) > 0)
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen if (mail_index_get_header(sync_view, &hdr) < 0)
fcfd317f7eb1f0216764c75c5fab3555020552d4Timo Sirainen if (sync_ctx.base_uid_validity != hdr->uid_validity) {
fcfd317f7eb1f0216764c75c5fab3555020552d4Timo Sirainen offsetof(struct mail_index_header, uid_validity),
fcfd317f7eb1f0216764c75c5fab3555020552d4Timo Sirainen &sync_ctx.next_uid, sizeof(sync_ctx.next_uid));
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen if ((uint32_t)st.st_mtime != hdr->sync_stamp) {
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen offsetof(struct mail_index_header, sync_stamp),
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen if ((uint64_t)st.st_mtime != hdr->sync_size) {
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen offsetof(struct mail_index_header, sync_size),
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen if (mail_index_transaction_commit(t, &seq, &offset) < 0)
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen else if (seq != 0) {
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen ibox->mbox_data = buffer_get_data(ibox->mbox_data_buf, &size);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen ibox->mbox_data_count = size / sizeof(*ibox->mbox_data);
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainenint mbox_sync(struct index_mailbox *ibox, int last_commit)
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen unsigned int lock_id;
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen ret = mail_index_sync_begin(ibox->index, &index_sync_ctx, &sync_view,
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen syncs = buffer_create_dynamic(default_pool, 256, (size_t)-1);
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen lock_type = mail_index_sync_have_more(index_sync_ctx) ?
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen if (mbox_lock(ibox, lock_type, &lock_id) > 0 &&
33ae95df45c9b5ec51332a6b39eb5322038686b9Timo Sirainen ret = mbox_sync_do(ibox, index_sync_ctx, sync_view,
9aa52288a4b53186d81b0ec9afa7d9e0a8ee8753Timo Sirainen /* read lock -> write lock. do it again. */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainenint mbox_storage_sync(struct mailbox *box, enum mailbox_sync_flags flags)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen struct index_mailbox *ibox = (struct index_mailbox *)box;