summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/dbox-single/sdbox-save.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/dbox-single/sdbox-save.c')
-rw-r--r--src/lib-storage/index/dbox-single/sdbox-save.c359
1 files changed, 359 insertions, 0 deletions
diff --git a/src/lib-storage/index/dbox-single/sdbox-save.c b/src/lib-storage/index/dbox-single/sdbox-save.c
new file mode 100644
index 0000000..03e24ab
--- /dev/null
+++ b/src/lib-storage/index/dbox-single/sdbox-save.c
@@ -0,0 +1,359 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "fdatasync-path.h"
+#include "hex-binary.h"
+#include "hex-dec.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "index-mail.h"
+#include "mail-copy.h"
+#include "index-pop3-uidl.h"
+#include "dbox-attachment.h"
+#include "dbox-save.h"
+#include "sdbox-storage.h"
+#include "sdbox-file.h"
+#include "sdbox-sync.h"
+
+
+struct sdbox_save_context {
+ struct dbox_save_context ctx;
+
+ struct sdbox_mailbox *mbox;
+ struct sdbox_sync_context *sync_ctx;
+
+ struct dbox_file *cur_file;
+ struct dbox_file_append_context *append_ctx;
+
+ uint32_t first_saved_seq;
+ ARRAY(struct dbox_file *) files;
+};
+
+#define SDBOX_SAVECTX(s) container_of(DBOX_SAVECTX(s), struct sdbox_save_context, ctx)
+
+struct dbox_file *
+sdbox_save_file_get_file(struct mailbox_transaction_context *t, uint32_t seq)
+{
+ struct sdbox_save_context *ctx = SDBOX_SAVECTX(t->save_ctx);
+ struct dbox_file *const *files, *file;
+ unsigned int count;
+
+ i_assert(seq >= ctx->first_saved_seq);
+
+ files = array_get(&ctx->files, &count);
+ i_assert(count > 0);
+ i_assert(seq - ctx->first_saved_seq < count);
+
+ file = files[seq - ctx->first_saved_seq];
+ i_assert(((struct sdbox_file *)file)->written_to_disk);
+ return file;
+}
+
+struct mail_save_context *
+sdbox_save_alloc(struct mailbox_transaction_context *t)
+{
+ struct sdbox_mailbox *mbox = SDBOX_MAILBOX(t->box);
+ struct sdbox_save_context *ctx;
+
+ i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+ if (t->save_ctx != NULL) {
+ /* use the existing allocated structure */
+ ctx = SDBOX_SAVECTX(t->save_ctx);
+ ctx->cur_file = NULL;
+ ctx->ctx.failed = FALSE;
+ ctx->ctx.finished = FALSE;
+ ctx->ctx.dbox_output = NULL;
+ return &ctx->ctx.ctx;
+ }
+
+ ctx = i_new(struct sdbox_save_context, 1);
+ ctx->ctx.ctx.transaction = t;
+ ctx->ctx.trans = t->itrans;
+ ctx->mbox = mbox;
+ i_array_init(&ctx->files, 32);
+ t->save_ctx = &ctx->ctx.ctx;
+ return t->save_ctx;
+}
+
+void sdbox_save_add_file(struct mail_save_context *_ctx, struct dbox_file *file)
+{
+ struct sdbox_save_context *ctx = SDBOX_SAVECTX(_ctx);
+ struct dbox_file *const *files;
+ unsigned int count;
+
+ if (ctx->first_saved_seq == 0)
+ ctx->first_saved_seq = ctx->ctx.seq;
+
+ files = array_get(&ctx->files, &count);
+ if (count > 0) {
+ /* a plugin may leave a previously saved file open.
+ we'll close it here to avoid eating too many fds. */
+ dbox_file_close(files[count-1]);
+ }
+ array_push_back(&ctx->files, &file);
+}
+
+int sdbox_save_begin(struct mail_save_context *_ctx, struct istream *input)
+{
+ struct sdbox_save_context *ctx = SDBOX_SAVECTX(_ctx);
+ struct dbox_file *file;
+ int ret;
+
+ file = sdbox_file_create(ctx->mbox);
+ ctx->append_ctx = dbox_file_append_init(file);
+ ret = dbox_file_get_append_stream(ctx->append_ctx,
+ &ctx->ctx.dbox_output);
+ if (ret <= 0) {
+ i_assert(ret != 0);
+ dbox_file_append_rollback(&ctx->append_ctx);
+ dbox_file_unref(&file);
+ ctx->ctx.failed = TRUE;
+ return -1;
+ }
+ ctx->cur_file = file;
+ dbox_save_begin(&ctx->ctx, input);
+
+ sdbox_save_add_file(_ctx, file);
+ return ctx->ctx.failed ? -1 : 0;
+}
+
+static int dbox_save_mail_write_metadata(struct dbox_save_context *ctx,
+ struct dbox_file *file)
+{
+ struct sdbox_file *sfile = (struct sdbox_file *)file;
+ const ARRAY_TYPE(mail_attachment_extref) *extrefs_arr;
+ const struct mail_attachment_extref *extrefs;
+ struct dbox_message_header dbox_msg_hdr;
+ uoff_t message_size;
+ guid_128_t guid_128;
+ unsigned int i, count;
+
+ i_assert(file->msg_header_size == sizeof(dbox_msg_hdr));
+
+ message_size = ctx->dbox_output->offset -
+ file->msg_header_size - file->file_header_size;
+
+ dbox_save_write_metadata(&ctx->ctx, ctx->dbox_output,
+ message_size, NULL, guid_128);
+ dbox_msg_header_fill(&dbox_msg_hdr, message_size);
+ if (o_stream_pwrite(ctx->dbox_output, &dbox_msg_hdr,
+ sizeof(dbox_msg_hdr),
+ file->file_header_size) < 0) {
+ dbox_file_set_syscall_error(file, "pwrite()");
+ return -1;
+ }
+ sfile->written_to_disk = TRUE;
+
+ /* remember the attachment paths until commit time */
+ extrefs_arr = index_attachment_save_get_extrefs(&ctx->ctx);
+ if (extrefs_arr != NULL)
+ extrefs = array_get(extrefs_arr, &count);
+ else {
+ extrefs = NULL;
+ count = 0;
+ }
+ if (count > 0) {
+ sfile->attachment_pool =
+ pool_alloconly_create("sdbox attachment paths", 512);
+ p_array_init(&sfile->attachment_paths,
+ sfile->attachment_pool, count);
+ for (i = 0; i < count; i++) {
+ const char *path = p_strdup(sfile->attachment_pool,
+ extrefs[i].path);
+ array_push_back(&sfile->attachment_paths, &path);
+ }
+ }
+ return 0;
+}
+
+static int dbox_save_finish_write(struct mail_save_context *_ctx)
+{
+ struct sdbox_save_context *ctx = (struct sdbox_save_context *)_ctx;
+ struct dbox_file **files;
+
+ ctx->ctx.finished = TRUE;
+ if (ctx->ctx.dbox_output == NULL)
+ return -1;
+
+ if (_ctx->data.save_date != (time_t)-1) {
+ /* we can't change ctime, but we can add the date to cache */
+ struct index_mail *mail = (struct index_mail *)_ctx->dest_mail;
+ uint32_t t = _ctx->data.save_date;
+
+ index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t));
+ }
+ dbox_save_end(&ctx->ctx);
+
+ files = array_back_modifiable(&ctx->files);
+ if (!ctx->ctx.failed) T_BEGIN {
+ if (dbox_save_mail_write_metadata(&ctx->ctx, *files) < 0)
+ ctx->ctx.failed = TRUE;
+ } T_END;
+
+ if (ctx->ctx.failed) {
+ index_storage_save_abort_last(&ctx->ctx.ctx, ctx->ctx.seq);
+ dbox_file_append_rollback(&ctx->append_ctx);
+ dbox_file_unlink(*files);
+ dbox_file_unref(files);
+ array_pop_back(&ctx->files);
+ } else {
+ dbox_file_append_checkpoint(ctx->append_ctx);
+ if (dbox_file_append_commit(&ctx->append_ctx) < 0)
+ ctx->ctx.failed = TRUE;
+ dbox_file_close(*files);
+ }
+
+ i_stream_unref(&ctx->ctx.input);
+ ctx->ctx.dbox_output = NULL;
+
+ return ctx->ctx.failed ? -1 : 0;
+}
+
+int sdbox_save_finish(struct mail_save_context *ctx)
+{
+ int ret;
+
+ ret = dbox_save_finish_write(ctx);
+ index_save_context_free(ctx);
+ return ret;
+}
+
+void sdbox_save_cancel(struct mail_save_context *_ctx)
+{
+ struct dbox_save_context *ctx = DBOX_SAVECTX(_ctx);
+
+ ctx->failed = TRUE;
+ (void)sdbox_save_finish(_ctx);
+}
+
+static int dbox_save_assign_uids(struct sdbox_save_context *ctx,
+ const ARRAY_TYPE(seq_range) *uids)
+{
+ struct dbox_file *const *files;
+ struct seq_range_iter iter;
+ unsigned int i, count, n = 0;
+ uint32_t uid;
+ bool ret;
+
+ seq_range_array_iter_init(&iter, uids);
+ files = array_get(&ctx->files, &count);
+ for (i = 0; i < count; i++) {
+ struct sdbox_file *sfile = (struct sdbox_file *)files[i];
+
+ ret = seq_range_array_iter_nth(&iter, n++, &uid);
+ i_assert(ret);
+ if (sdbox_file_assign_uid(sfile, uid) < 0)
+ return -1;
+ if (ctx->ctx.highest_pop3_uidl_seq == i+1) {
+ index_pop3_uidl_set_max_uid(&ctx->mbox->box,
+ ctx->ctx.trans, uid);
+ }
+ }
+ i_assert(!seq_range_array_iter_nth(&iter, n, &uid));
+ return 0;
+}
+
+static void dbox_save_unref_files(struct sdbox_save_context *ctx)
+{
+ struct dbox_file **files;
+ unsigned int i, count;
+
+ files = array_get_modifiable(&ctx->files, &count);
+ for (i = 0; i < count; i++) {
+ if (ctx->ctx.failed) {
+ struct sdbox_file *sfile =
+ (struct sdbox_file *)files[i];
+
+ (void)sdbox_file_unlink_aborted_save(sfile);
+ }
+ dbox_file_unref(&files[i]);
+ }
+ array_free(&ctx->files);
+}
+
+int sdbox_transaction_save_commit_pre(struct mail_save_context *_ctx)
+{
+ struct sdbox_save_context *ctx = SDBOX_SAVECTX(_ctx);
+ struct mailbox_transaction_context *_t = _ctx->transaction;
+ const struct mail_index_header *hdr;
+
+ i_assert(ctx->ctx.finished);
+
+ if (array_count(&ctx->files) == 0) {
+ /* the mail must be freed in the commit_pre() */
+ return 0;
+ }
+
+ if (sdbox_sync_begin(ctx->mbox, SDBOX_SYNC_FLAG_FORCE |
+ SDBOX_SYNC_FLAG_FSYNC, &ctx->sync_ctx) < 0) {
+ sdbox_transaction_save_rollback(_ctx);
+ return -1;
+ }
+
+ /* update dbox header flags */
+ dbox_save_update_header_flags(&ctx->ctx, ctx->sync_ctx->sync_view,
+ ctx->mbox->hdr_ext_id, offsetof(struct sdbox_index_header, flags));
+
+ /* assign UIDs for new messages */
+ hdr = mail_index_get_header(ctx->sync_ctx->sync_view);
+ mail_index_append_finish_uids(ctx->ctx.trans, hdr->next_uid,
+ &_t->changes->saved_uids);
+ if (dbox_save_assign_uids(ctx, &_t->changes->saved_uids) < 0) {
+ sdbox_transaction_save_rollback(_ctx);
+ return -1;
+ }
+
+ _t->changes->uid_validity = hdr->uid_validity;
+ return 0;
+}
+
+void sdbox_transaction_save_commit_post(struct mail_save_context *_ctx,
+ struct mail_index_transaction_commit_result *result)
+{
+ struct sdbox_save_context *ctx = SDBOX_SAVECTX(_ctx);
+ struct mail_storage *storage = _ctx->transaction->box->storage;
+
+ _ctx->transaction = NULL; /* transaction is already freed */
+
+ if (array_count(&ctx->files) == 0) {
+ sdbox_transaction_save_rollback(_ctx);
+ return;
+ }
+
+ mail_index_sync_set_commit_result(ctx->sync_ctx->index_sync_ctx,
+ result);
+
+ if (sdbox_sync_finish(&ctx->sync_ctx, TRUE) < 0)
+ ctx->ctx.failed = TRUE;
+
+ if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER) {
+ const char *box_path = mailbox_get_path(&ctx->mbox->box);
+
+ if (fdatasync_path(box_path) < 0) {
+ mail_set_critical(_ctx->dest_mail,
+ "fdatasync_path(%s) failed: %m", box_path);
+ }
+ }
+ i_assert(ctx->ctx.finished);
+ dbox_save_unref_files(ctx);
+ i_free(ctx);
+}
+
+void sdbox_transaction_save_rollback(struct mail_save_context *_ctx)
+{
+ struct sdbox_save_context *ctx = SDBOX_SAVECTX(_ctx);
+
+ ctx->ctx.failed = TRUE;
+ if (!ctx->ctx.finished)
+ sdbox_save_cancel(_ctx);
+ dbox_save_unref_files(ctx);
+
+ if (ctx->sync_ctx != NULL)
+ (void)sdbox_sync_finish(&ctx->sync_ctx, FALSE);
+ i_free(ctx);
+}