Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib-storage/index/mbox/mbox-save.c @ 2973:32cc9186d8eb HEAD
If we want MD5 sums in indexes, mail saving should also add them.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 15 Dec 2004 20:48:06 +0200 |
parents | 62d53b49110d |
children | 982e7432276f |
line wrap: on
line source
/* Copyright (C) 2002 Timo Sirainen */ #include "lib.h" #include "ioloop.h" #include "hostpid.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "write-full.h" #include "istream-header-filter.h" #include "ostream-crlf.h" #include "message-parser.h" #include "mbox-storage.h" #include "mbox-file.h" #include "mbox-from.h" #include "mbox-lock.h" #include "mbox-md5.h" #include "mbox-sync-private.h" #include <stddef.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <netdb.h> struct mbox_save_context { struct mail_save_context ctx; struct index_mailbox *ibox; struct mail_index_transaction *trans; uoff_t append_offset, mail_offset; string_t *headers; size_t space_end_idx; uint32_t seq, next_uid; struct istream *input; struct ostream *output, *body_output; uoff_t extra_hdr_offset, eoh_offset, eoh_input_offset; char last_char; struct index_mail mail; const struct mail_full_flags *flags; struct mbox_md5_context *mbox_md5_ctx; unsigned int synced:1; unsigned int failed:1; unsigned int save_crlf:1; }; static char my_hostdomain[256] = ""; static void write_error(struct mbox_save_context *ctx, int error) { if (ENOSPACE(error)) { mail_storage_set_error(ctx->ibox->box.storage, "Not enough disk space"); } else { errno = error; mbox_set_syscall_error(ctx->ibox, "write()"); } } static int mbox_seek_to_end(struct mbox_save_context *ctx, uoff_t *offset) { struct stat st; char ch; int fd; fd = ctx->ibox->mbox_fd; if (fstat(fd, &st) < 0) return mbox_set_syscall_error(ctx->ibox, "fstat()"); *offset = (uoff_t)st.st_size; if (st.st_size == 0) return 0; if (lseek(fd, st.st_size-1, SEEK_SET) < 0) return mbox_set_syscall_error(ctx->ibox, "lseek()"); if (read(fd, &ch, 1) != 1) return mbox_set_syscall_error(ctx->ibox, "read()"); if (ch != '\n') { if (write_full(fd, "\n", 1) < 0) { write_error(ctx, errno); return -1; } *offset += 1; } return 0; } static int mbox_append_lf(struct mbox_save_context *ctx) { if (o_stream_send(ctx->output, "\n", 1) < 0) { write_error(ctx, ctx->output->stream_errno); return -1; } return 0; } static int write_from_line(struct mbox_save_context *ctx, time_t received_date, const char *from_envelope) { const char *line, *name; int ret; if (*my_hostdomain == '\0') { struct hostent *hent; hent = gethostbyname(my_hostname); name = hent != NULL ? hent->h_name : NULL; if (name == NULL) { /* failed, use just the hostname */ name = my_hostname; } strocpy(my_hostdomain, name, sizeof(my_hostdomain)); } t_push(); if (from_envelope == NULL) { from_envelope = t_strconcat(ctx->ibox->storage->user, "@", my_hostdomain, NULL); } /* save in local timezone, no matter what it was given with */ line = mbox_from_create(from_envelope, received_date); if ((ret = o_stream_send_str(ctx->output, line)) < 0) write_error(ctx, ctx->output->stream_errno); t_pop(); return ret; } static int mbox_write_content_length(struct mbox_save_context *ctx) { uoff_t end_offset; const char *str; size_t len; int ret = 0; end_offset = ctx->output->offset; /* write Content-Length headers */ t_push(); str = t_strdup_printf("\nContent-Length: %s", dec2str(end_offset - ctx->eoh_offset)); len = strlen(str); if (o_stream_seek(ctx->output, ctx->extra_hdr_offset + ctx->space_end_idx - len) < 0) { mbox_set_syscall_error(ctx->ibox, "o_stream_seek()"); ret = -1; } else if (o_stream_send(ctx->output, str, len) < 0) { write_error(ctx, ctx->output->stream_errno); ret = -1; } else { if (o_stream_seek(ctx->output, end_offset) < 0) { mbox_set_syscall_error(ctx->ibox, "o_stream_seek()"); ret = -1; } } t_pop(); return ret; } static void mbox_save_init_sync(struct mbox_transaction_context *t) { struct mbox_save_context *ctx = t->save_ctx; const struct mail_index_header *hdr; hdr = mail_index_get_header(t->ictx.trans_view); ctx->next_uid = hdr->next_uid; ctx->synced = TRUE; t->mbox_modified = TRUE; index_mail_init(&t->ictx, &ctx->mail, 0, NULL); } static void status_flags_append(string_t *str, enum mail_flags flags, const struct mbox_flag_type *flags_list) { int i; for (i = 0; flags_list[i].chr != 0; i++) { if ((flags & flags_list[i].flag) != 0) str_append_c(str, flags_list[i].chr); } } static void mbox_save_append_flag_headers(string_t *str, enum mail_flags flags) { if ((flags & STATUS_FLAGS_MASK) != 0) { str_append(str, "Status: "); status_flags_append(str, flags, mbox_status_flags); str_append_c(str, '\n'); } if ((flags & XSTATUS_FLAGS_MASK) != 0) { str_append(str, "X-Status: "); status_flags_append(str, flags, mbox_xstatus_flags); str_append_c(str, '\n'); } } static void mbox_save_append_keyword_headers(struct mbox_save_context *ctx, const char *const *keywords, unsigned int count) { unsigned char space[MBOX_HEADER_PADDING+1 + sizeof("Content-Length: \n")-1 + MAX_INT_STRLEN]; unsigned int i; str_append(ctx->headers, "X-Keywords:"); for (i = 0; i < count; i++) { str_append_c(ctx->headers, ' '); str_append(ctx->headers, keywords[i]); } memset(space, ' ', sizeof(space)); str_append_n(ctx->headers, space, sizeof(space)); ctx->space_end_idx = str_len(ctx->headers); str_append_c(ctx->headers, '\n'); } static int mbox_save_init_file(struct mbox_save_context *ctx, struct mbox_transaction_context *t, int want_mail) { struct index_mailbox *ibox = ctx->ibox; int ret; if (ctx->append_offset == (uoff_t)-1) { /* first appended mail in this transaction */ if (ibox->mbox_lock_type != F_WRLCK) { if (mbox_lock(ibox, F_WRLCK, &t->mbox_lock_id) <= 0) return -1; } if (ibox->mbox_fd == -1) { if (mbox_file_open(ibox) < 0) return -1; } if (!want_mail) { /* assign UIDs only if mbox doesn't require syncing */ ret = mbox_sync_has_changed(ibox, TRUE); if (ret < 0) return -1; if (ret == 0) mbox_save_init_sync(t); } if (mbox_seek_to_end(ctx, &ctx->append_offset) < 0) return -1; ctx->output = o_stream_create_file(ibox->mbox_fd, default_pool, 0, FALSE); } if (!ctx->synced && want_mail) { /* we'll need to assign UID for the mail immediately. */ if (mbox_sync(ibox, 0) < 0) return -1; mbox_save_init_sync(t); } return 0; } static void save_header_callback(struct message_header_line *hdr, int *matched, void *context) { struct mbox_save_context *ctx = context; if (!*matched && ctx->ibox->mbox_save_md5 && hdr != NULL) mbox_md5_continue(ctx->mbox_md5_ctx, hdr); if ((hdr == NULL && ctx->eoh_input_offset == (uoff_t)-1) || (hdr != NULL && hdr->eoh)) ctx->eoh_input_offset = ctx->input->v_offset; } struct mail_save_context * mbox_save_init(struct mailbox_transaction_context *_t, const struct mail_full_flags *flags, time_t received_date, int timezone_offset __attr_unused__, const char *from_envelope, struct istream *input, int want_mail) { struct mbox_transaction_context *t = (struct mbox_transaction_context *)_t; struct index_mailbox *ibox = t->ictx.ibox; struct mbox_save_context *ctx = t->save_ctx; enum mail_flags save_flags; keywords_mask_t keywords; uint64_t offset; /* FIXME: we could write timezone_offset to From-line.. */ if (received_date == (time_t)-1) received_date = ioloop_time; if (ctx == NULL) { ctx = t->save_ctx = i_new(struct mbox_save_context, 1); ctx->ctx.box = &ibox->box; ctx->ibox = ibox; ctx->trans = mail_index_transaction_begin(ibox->view, FALSE, TRUE); ctx->append_offset = (uoff_t)-1; ctx->headers = str_new(default_pool, 512); ctx->save_crlf = getenv("MAIL_SAVE_CRLF") != NULL; ctx->mail_offset = (uoff_t)-1; } ctx->failed = FALSE; ctx->seq = 0; ctx->flags = flags; if (mbox_save_init_file(ctx, t, want_mail) < 0) { ctx->failed = TRUE; return &ctx->ctx; } save_flags = (flags->flags & ~MAIL_RECENT) | MAIL_RECENT; str_truncate(ctx->headers, 0); if (ctx->synced) { str_printfa(ctx->headers, "X-UID: %u\n", ctx->next_uid); if (!ibox->keep_recent) save_flags &= ~MAIL_RECENT; memset(keywords, 0, INDEX_KEYWORDS_BYTE_COUNT); // FIXME: set keywords mail_index_append(ctx->trans, ctx->next_uid, &ctx->seq); mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_REPLACE, save_flags, keywords); offset = ctx->output->offset == 0 ? 0 : ctx->output->offset - 1; mail_index_update_ext(ctx->trans, ctx->seq, ibox->mbox_ext_idx, &offset, NULL); ctx->next_uid++; } mbox_save_append_flag_headers(ctx->headers, save_flags ^ MBOX_NONRECENT); mbox_save_append_keyword_headers(ctx, flags->keywords, flags->keywords_count); str_append_c(ctx->headers, '\n'); i_assert(ibox->mbox_lock_type == F_WRLCK); ctx->mail_offset = ctx->output->offset; ctx->eoh_input_offset = (uoff_t)-1; ctx->eoh_offset = (uoff_t)-1; ctx->last_char = '\n'; if (write_from_line(ctx, received_date, from_envelope) < 0) ctx->failed = TRUE; else { ctx->input = i_stream_create_header_filter(input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, mbox_hide_headers, mbox_hide_headers_count, save_header_callback, ctx); ctx->body_output = getenv("MAIL_SAVE_CRLF") != NULL ? o_stream_create_crlf(default_pool, ctx->output) : o_stream_create_lf(default_pool, ctx->output); if (ctx->ibox->mbox_save_md5) ctx->mbox_md5_ctx = mbox_md5_init(); } return &ctx->ctx; } int mbox_save_continue(struct mail_save_context *_ctx) { struct mbox_save_context *ctx = (struct mbox_save_context *)_ctx; const unsigned char *data; size_t size; ssize_t ret; if (ctx->failed) return -1; if (ctx->eoh_offset != (uoff_t)-1) { /* writing body */ if (o_stream_send_istream(ctx->body_output, ctx->input) < 0) { ctx->failed = TRUE; return -1; } return 0; } while ((ret = i_stream_read(ctx->input)) != -1) { if (ret == 0) return 0; data = i_stream_get_data(ctx->input, &size); if (ctx->eoh_input_offset != (uoff_t)-1 && ctx->input->v_offset + size >= ctx->eoh_input_offset) { /* found end of headers. write the rest of them. */ size = ctx->eoh_input_offset - ctx->input->v_offset; if (o_stream_send(ctx->output, data, size) < 0) { ctx->failed = TRUE; return -1; } if (size > 0) ctx->last_char = data[size-1]; i_stream_skip(ctx->input, size + 1); break; } if (o_stream_send(ctx->output, data, size) < 0) { ctx->failed = TRUE; return -1; } ctx->last_char = data[size-1]; i_stream_skip(ctx->input, size); } if (ctx->last_char != '\n') { if (o_stream_send(ctx->output, "\n", 1) < 0) { ctx->failed = TRUE; return -1; } } if (ctx->ibox->mbox_save_md5) { unsigned char hdr_md5_sum[16]; mbox_md5_finish(ctx->mbox_md5_ctx, hdr_md5_sum); mail_index_update_ext(ctx->trans, ctx->seq, ctx->ibox->md5hdr_ext_idx, hdr_md5_sum, NULL); } /* append our own headers and ending empty line */ ctx->extra_hdr_offset = ctx->output->offset; if (o_stream_send(ctx->output, str_data(ctx->headers), str_len(ctx->headers)) < 0) { ctx->failed = TRUE; return -1; } ctx->eoh_offset = ctx->output->offset; /* write body */ (void)i_stream_get_data(ctx->input, &size); return ctx->input->eof && size == 0 ? 0 : mbox_save_continue(_ctx); } int mbox_save_finish(struct mail_save_context *_ctx, struct mail **mail_r) { struct mbox_save_context *ctx = (struct mbox_save_context *)_ctx; if (!ctx->failed) { if (mbox_write_content_length(ctx) < 0 || mbox_append_lf(ctx) < 0) ctx->failed = TRUE; } if (ctx->input != NULL) { i_stream_unref(ctx->input); ctx->input = NULL; } if (ctx->body_output != NULL) { o_stream_unref(ctx->body_output); ctx->body_output = NULL; } if (ctx->failed && ctx->mail_offset != (uoff_t)-1) { /* saving this mail failed - truncate back to beginning of it */ if (ftruncate(ctx->ibox->mbox_fd, (off_t)ctx->mail_offset) < 0) mbox_set_syscall_error(ctx->ibox, "ftruncate()"); ctx->mail_offset = (uoff_t)-1; } if (ctx->failed) { errno = ctx->output->stream_errno; if (ENOSPACE(errno)) { mail_storage_set_error(ctx->ibox->box.storage, "Not enough disk space"); } else if (errno != 0) { mail_storage_set_critical(ctx->ibox->box.storage, "write(%s) failed: %m", ctx->ibox->path); } return -1; } if (mail_r != NULL) { i_assert(ctx->seq != 0); if (index_mail_next(&ctx->mail, ctx->seq) < 0) return -1; *mail_r = &ctx->mail.mail; } return 0; } void mbox_save_cancel(struct mail_save_context *_ctx) { struct mbox_save_context *ctx = (struct mbox_save_context *)_ctx; ctx->failed = TRUE; (void)mbox_save_finish(_ctx, NULL); } static void mbox_transaction_save_deinit(struct mbox_save_context *ctx) { i_assert(ctx->body_output == NULL); if (ctx->mail.pool != NULL) index_mail_deinit(&ctx->mail); if (ctx->output != NULL) o_stream_unref(ctx->output); str_free(ctx->headers); i_free(ctx); } int mbox_transaction_save_commit(struct mbox_save_context *ctx) { uint32_t seq; uoff_t offset; int ret = 0; if (ctx->synced) { mail_index_update_header(ctx->trans, offsetof(struct mail_index_header, next_uid), &ctx->next_uid, sizeof(ctx->next_uid)); } if (!ctx->synced && ctx->ibox->mbox_fd != -1) { if (fdatasync(ctx->ibox->mbox_fd) < 0) { mbox_set_syscall_error(ctx->ibox, "fsync()"); ret = -1; } } if (mail_index_transaction_commit(ctx->trans, &seq, &offset) < 0) ret = -1; mbox_transaction_save_deinit(ctx); return ret; } void mbox_transaction_save_rollback(struct mbox_save_context *ctx) { struct index_mailbox *ibox = ctx->ibox; if (ctx->append_offset != (uoff_t)-1 && ibox->mbox_fd != -1) { i_assert(ibox->mbox_lock_type == F_WRLCK); /* failed, truncate file back to original size. output stream needs to be flushed before truncating so unref() won't write anything. */ o_stream_flush(ctx->output); if (ftruncate(ibox->mbox_fd, (off_t)ctx->append_offset) < 0) mbox_set_syscall_error(ibox, "ftruncate()"); } mail_index_transaction_rollback(ctx->trans); mbox_transaction_save_deinit(ctx); }