mbox-sync-rewrite.c revision 1e76a5b92f9d82d557f81f080f3dfad1c9d8f200
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch/* Copyright (C) 2004-2005 Timo Sirainen */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschint mbox_move(struct mbox_sync_context *sync_ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch output = o_stream_create_file(sync_ctx->write_fd, default_pool,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch input = i_stream_create_limit(default_pool, sync_ctx->file_input,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch else if (ret >= 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mail_storage_set_critical(STORAGE(sync_ctx->mbox->storage),
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch "mbox_move(%"PRIuUOFF_T", %"PRIuUOFF_T", %"PRIuUOFF_T
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch ") moved only %"PRIuUOFF_T" bytes in mbox file %s",
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch dest, source, size, (uoff_t)ret, sync_ctx->mbox->path);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch } else if (ret < 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch "o_stream_send_istream()");
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch return (int)ret;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschstatic int mbox_fill_space(struct mbox_sync_context *sync_ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (pwrite_full(sync_ctx->write_fd, space, size, offset) < 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschvoid mbox_sync_headers_add_space(struct mbox_sync_mail_context *ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch const unsigned char *data;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch start_pos = ctx->hdr_pos[MBOX_HDR_X_IMAPBASE];
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* update the header using the existing offset.
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch otherwise we might chose wrong header and just decrease
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch the available space */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch start_pos = ctx->mail.offset - ctx->hdr_offset;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* Append at the end of X-Keywords header,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch or X-UID if it doesn't exist */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch start_pos = ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] != (size_t)-1 ?
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch for (pos = start_pos; pos < data_size; pos++) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* possibly continues in next line */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (pos+1 == data_size || !IS_LWSP(data[pos+1]))
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch } else if (!IS_LWSP(data[pos]) && data[pos] != '\r') {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* pos points to end of header now, and start_pos to beginning
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch of whitespace. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch p = buffer_get_space_unsafe(ctx->header, pos, size);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschstatic void mbox_sync_header_remove_space(struct mbox_sync_mail_context *ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch const unsigned char *data;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* find the end of the LWSP */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch for (pos = last_line_pos = start_pos; pos < data_size; pos++) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* possibly continues in next line */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (pos+1 == data_size || !IS_LWSP(data[pos+1])) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch } else if (!IS_LWSP(data[pos]) && data[pos] != '\r') {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* and remove what we can */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* remove it all */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_move_buffer(ctx, start_pos, 0, data_size - start_pos);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* we have more space than needed. since we're removing from
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch the beginning of header instead of end, we don't have to
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch worry about multiline-headers. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_move_buffer(ctx, start_pos, 0, *size);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (ctx->mail.space < (off_t)(data_size - last_line_pos)) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschstatic void mbox_sync_headers_remove_space(struct mbox_sync_mail_context *ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch static enum header_position space_positions[] = {
55f1e5a6f22a7ea13a12b17be715e661e7f02471Stephan Bosch mbox_sync_header_remove_space(ctx, ctx->hdr_pos[pos],
55f1e5a6f22a7ea13a12b17be715e661e7f02471Stephan Bosch /* FIXME: see if we could remove X-Keywords header completely */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschstatic void mbox_sync_first_mail_written(struct mbox_sync_mail_context *ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* we wrote the first mail. update last-uid offset so we can find
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] != (size_t)-1);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch ctx->sync_ctx->base_uid_last_offset = hdr_offset +
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* update so a) we don't try to update it later unneededly,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch b) if we do actually update it, we see the correct value */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch ctx->sync_ctx->base_uid_last = ctx->last_uid_updated_value;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschint mbox_sync_try_rewrite(struct mbox_sync_mail_context *ctx, off_t move_diff)
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch struct mbox_sync_context *sync_ctx = ctx->sync_ctx;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(sync_ctx->mbox->mbox_lock_type == F_WRLCK);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch old_hdr_size = ctx->body_offset - ctx->hdr_offset;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* add space. note that we must call add_space() even if we're
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch not adding anything so mail.offset gets fixed. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_headers_add_space(ctx, old_hdr_size - new_hdr_size);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* try removing the space where we can */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* good, we removed enough. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch } else if (move_diff < 0 &&
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch new_hdr_size - old_hdr_size <= (uoff_t)-move_diff) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* moving backwards - we can use the extra space from
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch it, just update expunged_space accordingly */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch sync_ctx->expunged_space -= new_hdr_size - old_hdr_size;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* couldn't get enough space */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (ctx->header_first_change == (size_t)-1 && move_diff == 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* no changes actually. we get here if index sync record told
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch us to do something that was already there */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* forget about partial write optimizations */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch str_truncate(ctx->header, ctx->header_last_change);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch str_data(ctx->header) + ctx->header_first_change,
9a3c4a10fddf508c14d19fdc508d86b1be6d145bStephan Bosch str_len(ctx->header) - ctx->header_first_change,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch (ctx->imapbase_updated || ctx->sync_ctx->base_uid_last != 0)) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* the position might have moved as a result of moving
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch whitespace */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_first_mail_written(ctx, ctx->hdr_offset + move_diff);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschstatic void mbox_sync_read_next(struct mbox_sync_context *sync_ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch istream_raw_mbox_get_header_offset(sync_ctx->input);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mail_ctx->mail.body_size = mails[idx].body_size;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* This will force the UID to be the one that we originally assigned
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch to it, regardless of whether it's broken or not in the file. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch first_mail_expunge_extra - expunged_space != 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch sync_ctx->dest_first_mail = mails[idx].from_offset == 0;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* we need to skip over the initial \n (it's already counted in
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch expunged_space) */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mails[idx].from_offset += first_mail_expunge_extra;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_parse_next_mail(sync_ctx->input, mail_ctx);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(mail_ctx->mail.pseudo == mails[idx].pseudo);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* set next_uid back before updating the headers. this is important
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if we're updating the first message to make X-IMAP[base] header
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch have the correct value. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* remove all possible spacing before updating */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_headers_remove_space(mail_ctx, (size_t)-1);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_update_header_from(mail_ctx, &mails[idx]);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* updating might just try to add headers and mess up our
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch calculations completely. so only add the EOH here. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschstatic int mbox_sync_read_and_move(struct mbox_sync_context *sync_ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (mbox_sync_seek(sync_ctx, mails[idx].from_offset) < 0)
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_read_next(sync_ctx, &new_mail_ctx, mails, seq, idx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_stream_seek(sync_ctx->input, mail_ctx->body_offset);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (first_nonexpunged && expunged_space > 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* move From-line (after parsing headers so we don't
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch overwrite them) */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (mbox_move(sync_ctx, mails[idx].from_offset - expunged_space,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mails[idx].offset - mails[idx].from_offset) < 0)
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch need_space = str_len(mail_ctx->header) - mail_ctx->mail.space -
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch (mail_ctx->body_offset - mail_ctx->hdr_offset);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(need_space == (uoff_t)-mails[idx].space);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* don't touch spacing */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch } else if (padding < (uoff_t)mail_ctx->mail.space) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_headers_remove_space(mail_ctx, mail_ctx->mail.space -
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_headers_add_space(mail_ctx, padding -
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* move the body of this message and headers of next message forward,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch then write the headers */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (mbox_move(sync_ctx, dest_offset, offset, end_offset - offset) < 0)
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* the header may actually be moved backwards if there was expunged
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch space which we wanted to remove */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(dest_offset >= str_len(mail_ctx->header));
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(dest_offset >= mails[idx].from_offset - expunged_space);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (pwrite_full(sync_ctx->write_fd, str_data(mail_ctx->header),
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch str_len(mail_ctx->header), dest_offset) < 0) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mbox_sync_first_mail_written(mail_ctx, dest_offset);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch (mail_ctx->mail.offset - mail_ctx->hdr_offset);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Boschint mbox_sync_rewrite(struct mbox_sync_context *sync_ctx,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch uoff_t end_offset, off_t move_diff, uoff_t extra_space,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch uoff_t offset, dest_offset, next_end_offset, next_move_diff;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch uint32_t idx, first_nonexpunged_idx, padding_per_mail;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch unsigned int count;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(sync_ctx->mbox->mbox_lock_type == F_WRLCK);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mails = array_get_modifiable(&sync_ctx->mails, &count);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* if there's expunges in mails[], we would get more correct balancing
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch by counting only them here. however, that might make us overwrite
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch data which hasn't yet been copied backwards. to avoid too much
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch complexity, we just leave all the rest of the extra space to first
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* after expunge the next mail must have been missing space, or we
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch would have moved it backwards already */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch for (first_nonexpunged_idx = 0;; first_nonexpunged_idx++) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch expunged_space += mails[first_nonexpunged_idx].space;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(mails[first_nonexpunged_idx].space < 0);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* start moving backwards. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* give the rest of the extra space to first mail.
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch we might also have to move the mail backwards to
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch fill the expunged space */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch padding_per_mail = move_diff + expunged_space +
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (mails[idx].space <= 0 && !mails[idx].expunged) {
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* give space to this mail. end_offset is left to
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch contain this message's From-line (ie. below we
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch move only headers + body). */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch bool first_nonexpunged = idx == first_nonexpunged_idx;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if (mbox_sync_read_and_move(sync_ctx, mail_ctx, mails,
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch move_diff -= next_move_diff + mails[idx].space;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch /* this mail provides more space. just move it forward
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch from the extra space offset and set end_offset to
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch point to beginning of extra space. that way the
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch header will be moved along with previous mail's
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch if this is expunged mail, we're moving following
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch mail's From-line and maybe headers. */
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch offset = mails[idx].offset + mails[idx].space;
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(move_diff >= 0 || idx == first_nonexpunged_idx);
56dd928c164ec5c0d1158a1760154b58c5f1f6e7Stephan Bosch i_assert(mails[idx].from_offset == start_offset);