maildir-save.c revision c7a50b2c29780eaa3668bbac738e3fa3e4e171e8
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2002-2011 Dovecot authors, see the included COPYING file */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "lib.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "ioloop.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "array.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "buffer.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "istream.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "istream-crlf.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "ostream.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "fdatasync-path.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "eacces-error.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "str.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "index-mail.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "maildir-storage.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "maildir-uidlist.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "maildir-keywords.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "maildir-filename.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include "maildir-sync.h"
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include <stdio.h>
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include <stdlib.h>
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include <unistd.h>
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include <fcntl.h>
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include <utime.h>
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#include <sys/stat.h>
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi#define MAILDIR_FILENAME_FLAG_MOVED 0x10000000
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistruct maildir_filename {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *next;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *tmp_name, *dest_basename;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *pop3_uidl, *guid;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi uoff_t size, vsize;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi enum mail_flags flags;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int preserve_filename:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int keywords_count;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* unsigned int keywords[]; */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi};
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistruct maildir_save_context {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mail_save_context ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi pool_t pool;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_mailbox *mbox;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mail_index_transaction *trans;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_keywords_sync_ctx *keywords_sync_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_index_sync_context *sync_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mail *mail, *cur_dest_mail;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *tmpdir, *newdir, *curdir;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *files, **files_tail, *file_last;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int files_count;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi buffer_t keywords_buffer;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ARRAY_TYPE(keyword_indexes) keywords_array;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct istream *input;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi int fd;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi uint32_t first_seq, seq, last_nonrecent_uid;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int have_keywords:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int have_preserved_filenames:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int locked:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int failed:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int last_save_finished:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int locked_uidlist_refresh:1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi};
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistatic int maildir_file_move(struct maildir_save_context *ctx,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf, const char *destname,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi bool newdir)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mail_storage *storage = &ctx->mbox->storage->storage;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *tmp_path, *new_path;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(*destname != '\0');
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(*mf->tmp_name != '\0');
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* if we have flags, we'll move it to cur/ directly, because files in
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi new/ directory can't have flags. alternative would be to write it
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi in new/ and set the flags dirty in index file, but in that case
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi external MUAs would see wrong flags. */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi tmp_path = t_strconcat(ctx->tmpdir, "/", mf->tmp_name, NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi new_path = newdir ?
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi t_strconcat(ctx->newdir, "/", destname, NULL) :
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi t_strconcat(ctx->curdir, "/", destname, NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* maildir spec says we should use link() + unlink() here. however
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi since our filename is guaranteed to be unique, rename() works just
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi as well, except faster. even if the filename wasn't unique, the
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi problem could still happen if the file was already moved from
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi new/ to cur/, so link() doesn't really provide any safety anyway.
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi Besides the small temporary performance benefits, this rename() is
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi almost required with OSX's HFS+ filesystem, since it implements
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi hard links in a pretty ugly way, which makes the performance crawl
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi when a lot of hard links are used. */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (rename(tmp_path, new_path) == 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->flags |= MAILDIR_FILENAME_FLAG_MOVED;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return 0;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } else if (ENOSPACE(errno)) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_storage_set_error(storage, MAIL_ERROR_NOSPACE,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi MAIL_ERRSTR_NO_SPACE);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return -1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } else {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi tmp_path, new_path);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return -1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistatic struct mail_save_context *
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomimaildir_save_transaction_init(struct mailbox_transaction_context *t)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->box;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_save_context *ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi pool_t pool;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi pool = pool_alloconly_create("maildir_save_context", 4096);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx = p_new(pool, struct maildir_save_context, 1);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->ctx.transaction = t;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->pool = pool;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->mbox = mbox;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->trans = t->itrans;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->files_tail = &ctx->files;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->fd = -1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->tmpdir = p_strconcat(pool, mbox->box.path, "/tmp", NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->newdir = p_strconcat(pool, mbox->box.path, "/new", NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->curdir = p_strconcat(pool, mbox->box.path, "/cur", NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi buffer_create_const_data(&ctx->keywords_buffer, NULL, 0);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi array_create_from_buffer(&ctx->keywords_array, &ctx->keywords_buffer,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi sizeof(unsigned int));
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->last_save_finished = TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return &ctx->ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistruct maildir_filename *
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomimaildir_save_add(struct mail_save_context *_ctx, const char *tmp_fname,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mail *src_mail)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct istream *input;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int keyword_count;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(*tmp_fname != '\0');
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* allow caller to specify recent flag only when uid is specified
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi (we're replicating, converting, etc.). */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->uid == 0)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi _ctx->flags |= MAIL_RECENT;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi else if ((_ctx->flags & MAIL_RECENT) == 0 &&
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->last_nonrecent_uid < _ctx->uid)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->last_nonrecent_uid = _ctx->uid;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
efe78d3ba24fc866af1c79b9223dc0809ba26cadStephan Bosch /* now, we want to be able to rollback the whole append session,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi so we'll just store the name of this temp file and move it later
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi into new/ or cur/. */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* @UNSAFE */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi keyword_count = _ctx->keywords == NULL ? 0 : _ctx->keywords->count;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf = p_malloc(ctx->pool, sizeof(*mf) +
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi sizeof(unsigned int) * keyword_count);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->tmp_name = mf->dest_basename = p_strdup(ctx->pool, tmp_fname);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->flags = _ctx->flags;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->size = (uoff_t)-1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->vsize = (uoff_t)-1;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->file_last = mf;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(*ctx->files_tail == NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi *ctx->files_tail = mf;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->files_tail = &mf->next;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->files_count++;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->keywords != NULL) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* @UNSAFE */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->keywords_count = keyword_count;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi memcpy(mf + 1, _ctx->keywords->idx,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi sizeof(unsigned int) * keyword_count);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->have_keywords = TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->pop3_uidl != NULL)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->pop3_uidl = p_strdup(ctx->pool, _ctx->pop3_uidl);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* insert into index */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_index_append(ctx->trans, _ctx->uid, &ctx->seq);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_index_update_flags(ctx->trans, ctx->seq,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi MODIFY_REPLACE, _ctx->flags & ~MAIL_RECENT);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->keywords != NULL) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_index_update_keywords(ctx->trans, ctx->seq,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi MODIFY_REPLACE, _ctx->keywords);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->min_modseq != 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_index_update_modseq(ctx->trans, ctx->seq,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi _ctx->min_modseq);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (ctx->first_seq == 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->first_seq = ctx->seq;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(ctx->files->next == NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->dest_mail == NULL) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (ctx->mail == NULL)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->mail = mail_alloc(_ctx->transaction, 0, NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi _ctx->dest_mail = ctx->mail;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_set_seq(_ctx->dest_mail, ctx->seq);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi _ctx->dest_mail->saving = TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (ctx->input == NULL) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* copying with hardlinking. */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(src_mail != NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi index_copy_cache_fields(_ctx, src_mail, ctx->seq);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->cur_dest_mail = NULL;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } else {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi input = index_mail_cache_parse_init(_ctx->dest_mail,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->input);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_stream_unref(&ctx->input);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->input = input;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->cur_dest_mail = _ctx->dest_mail;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return mf;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomivoid maildir_save_set_dest_basename(struct mail_save_context *_ctx,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *basename)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->preserve_filename = TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->dest_basename = p_strdup(ctx->pool, basename);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->have_preserved_filenames = TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomivoid maildir_save_set_sizes(struct maildir_filename *mf,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi uoff_t size, uoff_t vsize)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->size = size;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->vsize = vsize;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistatic bool
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomimaildir_get_dest_filename(struct maildir_save_context *ctx,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char **fname_r)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *basename = mf->dest_basename;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (mf->size != (uoff_t)-1 && !mf->preserve_filename) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi MAILDIR_EXTRA_FILE_SIZE, mf->size);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (mf->vsize != (uoff_t)-1 && !mf->preserve_filename) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi MAILDIR_EXTRA_VIRTUAL_SIZE,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->vsize);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (mf->keywords_count == 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if ((mf->flags & MAIL_FLAGS_MASK) == MAIL_RECENT) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi *fname_r = basename;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi *fname_r = maildir_filename_set_flags(NULL, basename,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->flags & MAIL_FLAGS_MASK, NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return FALSE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(ctx->keywords_sync_ctx != NULL || mf->keywords_count == 0);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi buffer_create_const_data(&ctx->keywords_buffer, mf + 1,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->keywords_count * sizeof(unsigned int));
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi *fname_r = maildir_filename_set_flags(ctx->keywords_sync_ctx, basename,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf->flags & MAIL_FLAGS_MASK,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi &ctx->keywords_array);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return FALSE;
c1ab825edf003f5cfc6c31730442f36a17209101Timo Sirainen}
c1ab825edf003f5cfc6c31730442f36a17209101Timo Sirainen
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistatic const char *maildir_mf_get_path(struct maildir_save_context *ctx,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *fname, *dir;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if ((mf->flags & MAILDIR_FILENAME_FLAG_MOVED) == 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* file is still in tmp/ */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return t_strdup_printf("%s/%s", ctx->tmpdir, mf->tmp_name);
c1ab825edf003f5cfc6c31730442f36a17209101Timo Sirainen }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* already moved to new/ or cur/ */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi dir = maildir_get_dest_filename(ctx, mf, &fname) ?
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->newdir : ctx->curdir;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return t_strdup_printf("%s/%s", dir, fname);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomiconst char *maildir_save_file_get_path(struct mailbox_transaction_context *t,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi uint32_t seq)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_save_context *save_ctx =
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi (struct maildir_save_context *)t->save_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(seq >= save_ctx->first_seq);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi seq -= save_ctx->first_seq;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf = save_ctx->files;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi while (seq > 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf = mf->next;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert(mf != NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi seq--;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return maildir_mf_get_path(save_ctx, mf);
c1ab825edf003f5cfc6c31730442f36a17209101Timo Sirainen}
eb318ea05532d2e54ed3bfc89bc15dcf1adae838Timo Sirainen
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistatic int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char **fname_r)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mailbox *box = &mbox->box;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi unsigned int prefix_len;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *tmp_fname;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi string_t *path;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mode_t old_mask;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi int fd;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi path = t_str_new(256);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi str_append(path, dir);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi str_append_c(path, '/');
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi prefix_len = str_len(path);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi do {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi tmp_fname = maildir_filename_generate();
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi str_truncate(path, prefix_len);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi str_append(path, tmp_fname);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* the generated filename is unique. the only reason why it
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi might return an existing filename is if the time moved
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi backwards. so we'll use O_EXCL anyway, although it's mostly
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi useless. */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi old_mask = umask(0777 & ~box->file_create_mode);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi fd = open(str_c(path),
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi umask(old_mask);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } while (fd == -1 && errno == EEXIST);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi *fname_r = tmp_fname;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (fd == -1) {
a027af9acef6d8a6b367c98d70a218922cea649eAki Tuomi if (ENOSPACE(errno)) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_storage_set_error(box->storage,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi MAIL_ERROR_NOSPACE, MAIL_ERRSTR_NO_SPACE);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } else {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_storage_set_critical(box->storage,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi "open(%s) failed: %m", str_c(path));
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } else if (box->file_create_gid != (gid_t)-1) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (errno == EPERM) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_storage_set_critical(box->storage, "%s",
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi eperm_error_get_chgrp("fchown",
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi str_c(path),
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi box->file_create_gid,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi box->file_create_gid_origin));
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } else {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mail_storage_set_critical(box->storage,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi "fchown(%s) failed: %m", str_c(path));
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return fd;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomistruct mail_save_context *
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomimaildir_save_alloc(struct mailbox_transaction_context *t)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (t->save_ctx == NULL)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi t->save_ctx = maildir_save_transaction_init(t);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return t->save_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomiint maildir_save_begin(struct mail_save_context *_ctx, struct istream *input)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_filename *mf;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* new mail, new failure state */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->failed = FALSE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi T_BEGIN {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi /* create a new file in tmp/ directory */
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi const char *fname;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->fd = maildir_create_tmp(ctx->mbox, ctx->tmpdir, &fname);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (ctx->fd == -1)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->failed = TRUE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi else {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (ctx->mbox->storage->storage.set->mail_save_crlf)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->input = i_stream_create_crlf(input);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi else
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->input = i_stream_create_lf(input);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi mf = maildir_save_add(_ctx, fname, NULL);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (_ctx->guid != NULL) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi maildir_save_set_dest_basename(_ctx, mf,
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi _ctx->guid);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi } T_END;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (!ctx->failed) {
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi _ctx->output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi o_stream_cork(_ctx->output);
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi ctx->last_save_finished = FALSE;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi }
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi return ctx->failed ? -1 : 0;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi}
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomiint maildir_save_continue(struct mail_save_context *_ctx)
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi{
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi struct mail_storage *storage = &ctx->mbox->storage->storage;
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi
4c78d9e646c4a1158d7167806937c02d86cdfc25Aki Tuomi if (ctx->failed)
return -1;
do {
if (o_stream_send_istream(_ctx->output, ctx->input) < 0) {
if (!mail_storage_set_error_from_errno(storage)) {
mail_storage_set_critical(storage,
"o_stream_send_istream(%s/%s) "
"failed: %m",
ctx->tmpdir, ctx->file_last->tmp_name);
}
ctx->failed = TRUE;
return -1;
}
if (ctx->cur_dest_mail != NULL)
index_mail_cache_parse_continue(ctx->cur_dest_mail);
/* both tee input readers may consume data from our primary
input stream. we'll have to make sure we don't return with
one of the streams still having data in them. */
} while (i_stream_read(ctx->input) > 0);
return 0;
}
static int maildir_save_finish_received_date(struct maildir_save_context *ctx,
const char *path)
{
struct mail_storage *storage = &ctx->mbox->storage->storage;
struct utimbuf buf;
struct stat st;
if (ctx->ctx.received_date != (time_t)-1) {
/* set the received_date by modifying mtime */
buf.actime = ioloop_time;
buf.modtime = ctx->ctx.received_date;
if (utime(path, &buf) < 0) {
mail_storage_set_critical(storage,
"utime(%s) failed: %m", path);
return -1;
}
} else if (ctx->fd != -1) {
if (fstat(ctx->fd, &st) == 0)
ctx->ctx.received_date = st.st_mtime;
else {
mail_storage_set_critical(storage,
"fstat(%s) failed: %m", path);
return -1;
}
} else {
/* hardlinked */
if (stat(path, &st) == 0)
ctx->ctx.received_date = st.st_mtime;
else {
mail_storage_set_critical(storage,
"stat(%s) failed: %m", path);
return -1;
}
}
return 0;
}
static void maildir_save_remove_last_filename(struct maildir_save_context *ctx)
{
struct index_transaction_context *t =
(struct index_transaction_context *)ctx->ctx.transaction;
struct maildir_filename **fm;
mail_index_expunge(ctx->trans, ctx->seq);
/* currently we can't just drop pending cache updates for this one
specific record, so we'll reset the whole cache transaction. */
mail_cache_transaction_reset(t->cache_trans);
ctx->seq--;
for (fm = &ctx->files; (*fm)->next != NULL; fm = &(*fm)->next) ;
i_assert(*fm == ctx->file_last);
*fm = NULL;
ctx->files_tail = fm;
ctx->file_last = NULL;
ctx->files_count--;
}
static int maildir_save_finish_real(struct mail_save_context *_ctx)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
struct mail_storage *storage = &ctx->mbox->storage->storage;
const char *path;
int output_errno;
ctx->last_save_finished = TRUE;
if (ctx->failed && ctx->fd == -1) {
/* tmp file creation failed */
return -1;
}
path = t_strconcat(ctx->tmpdir, "/", ctx->file_last->tmp_name, NULL);
if (o_stream_flush(_ctx->output) < 0) {
if (!mail_storage_set_error_from_errno(storage)) {
mail_storage_set_critical(storage,
"o_stream_flush(%s) failed: %m", path);
}
ctx->failed = TRUE;
}
if (_ctx->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->save_date;
index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t));
}
if (maildir_save_finish_received_date(ctx, path) < 0)
ctx->failed = TRUE;
if (ctx->cur_dest_mail != NULL) {
index_mail_cache_parse_deinit(ctx->cur_dest_mail,
ctx->ctx.received_date,
!ctx->failed);
}
i_stream_unref(&ctx->input);
/* remember the size in case we want to add it to filename */
ctx->file_last->size = _ctx->output->offset;
if (ctx->cur_dest_mail == NULL ||
mail_get_virtual_size(ctx->cur_dest_mail,
&ctx->file_last->vsize) < 0)
ctx->file_last->vsize = (uoff_t)-1;
output_errno = _ctx->output->stream_errno;
o_stream_destroy(&_ctx->output);
if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER &&
!ctx->failed) {
if (fsync(ctx->fd) < 0) {
if (!mail_storage_set_error_from_errno(storage)) {
mail_storage_set_critical(storage,
"fsync(%s) failed: %m", path);
}
ctx->failed = TRUE;
}
}
if (close(ctx->fd) < 0) {
if (!mail_storage_set_error_from_errno(storage)) {
mail_storage_set_critical(storage,
"close(%s) failed: %m", path);
}
ctx->failed = TRUE;
}
ctx->fd = -1;
if (ctx->failed) {
/* delete the tmp file */
if (unlink(path) < 0 && errno != ENOENT) {
mail_storage_set_critical(storage,
"unlink(%s) failed: %m", path);
}
errno = output_errno;
if (ENOSPACE(errno)) {
mail_storage_set_error(storage,
MAIL_ERROR_NOSPACE, MAIL_ERRSTR_NO_SPACE);
} else if (errno != 0) {
mail_storage_set_critical(storage,
"write(%s) failed: %m", path);
}
maildir_save_remove_last_filename(ctx);
return -1;
}
ctx->file_last = NULL;
return 0;
}
int maildir_save_finish(struct mail_save_context *ctx)
{
int ret;
T_BEGIN {
ret = maildir_save_finish_real(ctx);
} T_END;
index_save_context_free(ctx);
return ret;
}
void maildir_save_cancel(struct mail_save_context *_ctx)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
ctx->failed = TRUE;
(void)maildir_save_finish(_ctx);
}
static void
maildir_save_unlink_files(struct maildir_save_context *ctx)
{
struct maildir_filename *mf;
for (mf = ctx->files; mf != NULL; mf = mf->next) T_BEGIN {
(void)unlink(maildir_mf_get_path(ctx, mf));
} T_END;
ctx->files = NULL;
}
static int maildir_transaction_fsync_dirs(struct maildir_save_context *ctx,
bool new_changed, bool cur_changed)
{
struct mail_storage *storage = &ctx->mbox->storage->storage;
if (storage->set->parsed_fsync_mode == FSYNC_MODE_NEVER)
return 0;
if (new_changed) {
if (fdatasync_path(ctx->newdir) < 0) {
mail_storage_set_critical(storage,
"fdatasync_path(%s) failed: %m", ctx->newdir);
return -1;
}
}
if (cur_changed) {
if (fdatasync_path(ctx->curdir) < 0) {
mail_storage_set_critical(storage,
"fdatasync_path(%s) failed: %m", ctx->curdir);
return -1;
}
}
return 0;
}
static int seq_range_cmp(const struct seq_range *r1, const struct seq_range *r2)
{
if (r1->seq1 < r2->seq2)
return -1;
else if (r1->seq1 > r2->seq2)
return 1;
else
return 0;
}
static uint32_t
maildir_save_set_recent_flags(struct maildir_save_context *ctx)
{
struct maildir_mailbox *mbox = ctx->mbox;
ARRAY_TYPE(seq_range) saved_sorted_uids;
const struct seq_range *uids;
unsigned int i, count;
uint32_t uid;
count = array_count(&ctx->ctx.transaction->changes->saved_uids);
if (count == 0)
return 0;
t_array_init(&saved_sorted_uids, count);
array_append_array(&saved_sorted_uids,
&ctx->ctx.transaction->changes->saved_uids);
array_sort(&saved_sorted_uids, seq_range_cmp);
uids = array_get(&saved_sorted_uids, &count);
for (i = 0; i < count; i++) {
for (uid = uids[i].seq1; uid <= uids[i].seq2; uid++)
index_mailbox_set_recent_uid(&mbox->box, uid);
}
return uids[count-1].seq2 + 1;
}
static int
maildir_save_sync_index(struct maildir_save_context *ctx)
{
struct mailbox_transaction_context *_t = ctx->ctx.transaction;
struct maildir_mailbox *mbox = ctx->mbox;
uint32_t first_uid, next_uid, first_recent_uid;
int ret;
/* we'll need to keep the lock past the sync deinit */
ret = maildir_uidlist_lock(mbox->uidlist);
i_assert(ret > 0);
if (maildir_sync_header_refresh(mbox) < 0)
return -1;
if ((ret = maildir_uidlist_refresh_fast_init(mbox->uidlist)) < 0)
return -1;
if (ret == 0) {
/* uidlist doesn't exist. make sure all existing message
are added to uidlist first. */
(void)maildir_storage_sync_force(mbox, 0);
}
if (maildir_sync_index_begin(mbox, NULL, &ctx->sync_ctx) < 0)
return -1;
ctx->keywords_sync_ctx =
maildir_sync_get_keywords_sync_ctx(ctx->sync_ctx);
/* now that uidlist is locked, make sure all the existing mails
have been added to index. we don't really look into the
maildir, just add all the new mails listed in
dovecot-uidlist to index. */
if (maildir_sync_index(ctx->sync_ctx, TRUE) < 0)
return -1;
/* if messages were added to index, assign them UIDs */
first_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
i_assert(first_uid != 0);
mail_index_append_finish_uids(ctx->trans, first_uid,
&_t->changes->saved_uids);
i_assert(ctx->files_count == seq_range_count(&_t->changes->saved_uids));
/* these mails are all recent in our session */
T_BEGIN {
next_uid = maildir_save_set_recent_flags(ctx);
} T_END;
if ((mbox->box.flags & MAILBOX_FLAG_KEEP_RECENT) == 0)
first_recent_uid = next_uid;
else if (ctx->last_nonrecent_uid != 0)
first_recent_uid = ctx->last_nonrecent_uid + 1;
else
first_recent_uid = 0;
if (first_recent_uid != 0) {
/* maildir_sync_index() dropped recent flags from
existing messages. we'll still need to drop recent
flags from these newly added messages. */
mail_index_update_header(ctx->trans,
offsetof(struct mail_index_header,
first_recent_uid),
&first_recent_uid,
sizeof(first_recent_uid), FALSE);
}
return 0;
}
static void
maildir_save_rollback_index_changes(struct maildir_save_context *ctx)
{
struct index_transaction_context *t =
(struct index_transaction_context *)ctx->ctx.transaction;
uint32_t seq;
if (ctx->seq == 0)
return;
for (seq = ctx->seq; seq >= ctx->first_seq; seq--)
mail_index_expunge(ctx->trans, seq);
mail_cache_transaction_reset(t->cache_trans);
}
static bool maildir_filename_has_conflict(struct maildir_filename *mf,
struct maildir_filename *prev_mf)
{
if (strcmp(mf->dest_basename, prev_mf->dest_basename) == 0) {
/* already used this */
return TRUE;
}
if (prev_mf->guid != NULL &&
strcmp(mf->dest_basename, prev_mf->guid) == 0) {
/* previous filename also had a conflict */
return TRUE;
}
return FALSE;
}
static void
maildir_filename_check_conflicts(struct maildir_save_context *ctx,
struct maildir_filename *mf,
struct maildir_filename *prev_mf)
{
uoff_t size;
if (!ctx->locked_uidlist_refresh) {
(void)maildir_uidlist_refresh(ctx->mbox->uidlist);
ctx->locked_uidlist_refresh = TRUE;
}
if ((prev_mf != NULL && maildir_filename_has_conflict(mf, prev_mf)) ||
maildir_uidlist_get_full_filename(ctx->mbox->uidlist,
mf->dest_basename) != NULL) {
/* file already exists. give it another name.
but preserve the size/vsize in the filename if possible */
if (maildir_filename_get_size(mf->dest_basename,
MAILDIR_EXTRA_FILE_SIZE, &size))
mf->size = size;
if (maildir_filename_get_size(mf->dest_basename,
MAILDIR_EXTRA_VIRTUAL_SIZE,
&size))
mf->vsize = size;
mf->guid = mf->dest_basename;
mf->dest_basename = p_strdup(ctx->pool,
maildir_filename_generate());
mf->preserve_filename = FALSE;
}
}
static int
maildir_filename_dest_basename_cmp(struct maildir_filename *const *f1,
struct maildir_filename *const *f2)
{
return strcmp((*f1)->dest_basename, (*f2)->dest_basename);
}
static int
maildir_save_move_files_to_newcur(struct maildir_save_context *ctx)
{
ARRAY_DEFINE(files, struct maildir_filename *);
struct maildir_filename *mf, *const *mfp, *prev_mf;
bool newdir, new_changed, cur_changed;
int ret;
/* put files into an array sorted by the destination filename.
this way we can easily check if there are duplicate destination
filenames within this transaction. */
t_array_init(&files, ctx->files_count);
for (mf = ctx->files; mf != NULL; mf = mf->next)
array_append(&files, &mf, 1);
array_sort(&files, maildir_filename_dest_basename_cmp);
new_changed = cur_changed = FALSE; prev_mf = FALSE;
array_foreach(&files, mfp) {
mf = *mfp;
T_BEGIN {
const char *dest;
if (mf->preserve_filename) {
maildir_filename_check_conflicts(ctx, mf,
prev_mf);
}
newdir = maildir_get_dest_filename(ctx, mf, &dest);
if (newdir)
new_changed = TRUE;
else
cur_changed = TRUE;
ret = maildir_file_move(ctx, mf, dest, newdir);
} T_END;
if (ret < 0)
return -1;
prev_mf = mf;
}
if (ctx->locked) {
i_assert(ctx->sync_ctx != NULL);
maildir_sync_set_new_msgs_count(ctx->sync_ctx,
array_count(&files));
}
return maildir_transaction_fsync_dirs(ctx, new_changed, cur_changed);
}
static void maildir_save_sync_uidlist(struct maildir_save_context *ctx)
{
struct mailbox_transaction_context *t = ctx->ctx.transaction;
struct maildir_filename *mf;
struct seq_range_iter iter;
enum maildir_uidlist_rec_flag flags;
struct maildir_uidlist_rec *rec;
unsigned int n = 0;
uint32_t uid;
bool newdir, bret;
int ret;
seq_range_array_iter_init(&iter, &t->changes->saved_uids);
for (mf = ctx->files; mf != NULL; mf = mf->next) T_BEGIN {
const char *dest;
bret = seq_range_array_iter_nth(&iter, n++, &uid);
i_assert(bret);
newdir = maildir_get_dest_filename(ctx, mf, &dest);
flags = MAILDIR_UIDLIST_REC_FLAG_RECENT;
if (newdir)
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR;
ret = maildir_uidlist_sync_next_uid(ctx->uidlist_sync_ctx,
dest, uid, flags, &rec);
i_assert(ret > 0);
i_assert(rec != NULL);
if (mf->guid != NULL) {
maildir_uidlist_sync_set_ext(ctx->uidlist_sync_ctx, rec,
MAILDIR_UIDLIST_REC_EXT_GUID, mf->guid);
}
if (mf->pop3_uidl != NULL) {
maildir_uidlist_sync_set_ext(ctx->uidlist_sync_ctx, rec,
MAILDIR_UIDLIST_REC_EXT_POP3_UIDL,
mf->pop3_uidl);
}
} T_END;
i_assert(!seq_range_array_iter_nth(&iter, n, &uid));
}
int maildir_transaction_save_commit_pre(struct mail_save_context *_ctx)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
struct mailbox_transaction_context *_t = _ctx->transaction;
enum maildir_uidlist_sync_flags sync_flags;
int ret;
i_assert(_ctx->output == NULL);
i_assert(ctx->last_save_finished);
if (ctx->files_count == 0) {
/* the mail must be freed in the commit_pre() */
if (ctx->mail != NULL)
mail_free(&ctx->mail);
return 0;
}
sync_flags = MAILDIR_UIDLIST_SYNC_PARTIAL |
MAILDIR_UIDLIST_SYNC_NOREFRESH;
if ((_t->flags & MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS) != 0) {
/* we want to assign UIDs, we must lock uidlist */
} else if (ctx->have_keywords) {
/* keywords file updating relies on uidlist lock. */
} else if (ctx->have_preserved_filenames) {
/* we're trying to use some potentially existing filenames.
we must lock to avoid race conditions where two sessions
try to save the same filename. */
} else {
/* no requirement to lock uidlist. if we happen to get a lock,
assign uids. */
sync_flags |= MAILDIR_UIDLIST_SYNC_TRYLOCK;
}
ret = maildir_uidlist_sync_init(ctx->mbox->uidlist, sync_flags,
&ctx->uidlist_sync_ctx);
if (ret > 0) {
ctx->locked = TRUE;
if (maildir_save_sync_index(ctx) < 0) {
maildir_transaction_save_rollback(_ctx);
return -1;
}
} else if (ret == 0 &&
(sync_flags & MAILDIR_UIDLIST_SYNC_TRYLOCK) != 0) {
ctx->locked = FALSE;
i_assert(ctx->uidlist_sync_ctx == NULL);
/* since we couldn't lock uidlist, we'll have to drop the
appends to index. */
maildir_save_rollback_index_changes(ctx);
} else {
maildir_transaction_save_rollback(_ctx);
return -1;
}
T_BEGIN {
ret = maildir_save_move_files_to_newcur(ctx);
} T_END;
if (ctx->locked) {
if (ret == 0) {
/* update dovecot-uidlist file. */
maildir_save_sync_uidlist(ctx);
}
if (maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx,
ret == 0) < 0)
ret = -1;
}
_t->changes->uid_validity =
maildir_uidlist_get_uid_validity(ctx->mbox->uidlist);
if (ctx->mail != NULL) {
/* Mail freeing may trigger cache updates and a call to
maildir_save_file_get_path(). Do this before finishing index
sync so we still have keywords_sync_ctx. */
mail_free(&ctx->mail);
}
if (ctx->locked) {
/* It doesn't matter if index syncing fails */
ctx->keywords_sync_ctx = NULL;
if (ret < 0)
maildir_sync_index_rollback(&ctx->sync_ctx);
else
(void)maildir_sync_index_commit(&ctx->sync_ctx);
}
if (ret < 0) {
ctx->keywords_sync_ctx = !ctx->have_keywords ? NULL :
maildir_keywords_sync_init(ctx->mbox->keywords,
ctx->mbox->box.index);
/* unlink the files we just moved in an attempt to rollback
the transaction. uidlist is still locked, so at least other
Dovecot instances haven't yet seen the files. we need
to have the keywords sync context to be able to generate
the destination filenames if keywords were used. */
maildir_save_unlink_files(ctx);
if (ctx->keywords_sync_ctx != NULL)
maildir_keywords_sync_deinit(&ctx->keywords_sync_ctx);
/* returning failure finishes the save_context */
maildir_transaction_save_rollback(_ctx);
return -1;
}
return 0;
}
void maildir_transaction_save_commit_post(struct mail_save_context *_ctx,
struct mail_index_transaction_commit_result *result ATTR_UNUSED)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
_ctx->transaction = NULL; /* transaction is already freed */
if (ctx->locked)
maildir_uidlist_unlock(ctx->mbox->uidlist);
pool_unref(&ctx->pool);
}
void maildir_transaction_save_rollback(struct mail_save_context *_ctx)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
i_assert(_ctx->output == NULL);
if (!ctx->last_save_finished)
maildir_save_cancel(&ctx->ctx);
/* delete files in tmp/ */
maildir_save_unlink_files(ctx);
if (ctx->uidlist_sync_ctx != NULL)
(void)maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx, FALSE);
if (ctx->sync_ctx != NULL)
maildir_sync_index_rollback(&ctx->sync_ctx);
if (ctx->locked)
maildir_uidlist_unlock(ctx->mbox->uidlist);
if (ctx->mail != NULL)
mail_free(&ctx->mail);
pool_unref(&ctx->pool);
}