maildir-save.c revision 7c95b03620a03a43dd72d39608cea5fc77393ad6
181N/A/* Copyright (C) 2002-2004 Timo Sirainen */
181N/A
181N/A#include "lib.h"
181N/A#include "ioloop.h"
181N/A#include "ostream.h"
181N/A#include "ostream-crlf.h"
181N/A#include "str.h"
181N/A#include "maildir-storage.h"
181N/A#include "maildir-uidlist.h"
181N/A
181N/A#include <stdio.h>
181N/A#include <stdlib.h>
181N/A#include <unistd.h>
181N/A#include <fcntl.h>
181N/A#include <utime.h>
181N/A#include <sys/stat.h>
181N/A
181N/Astruct maildir_filename {
181N/A struct maildir_filename *next;
181N/A const char *basename, *dest;
181N/A};
3532N/A
181N/Astruct maildir_save_context {
181N/A struct mail_save_context ctx;
181N/A pool_t pool;
181N/A
181N/A struct index_mailbox *ibox;
181N/A struct mail_index_transaction *trans;
181N/A struct maildir_uidlist_sync_ctx *sync_ctx;
181N/A struct index_mail mail;
181N/A
181N/A const char *tmpdir, *newdir, *curdir;
181N/A struct maildir_filename *files;
181N/A
181N/A struct istream *input;
181N/A struct ostream *output;
181N/A int fd;
181N/A time_t received_date;
181N/A uint32_t seq;
1210N/A
1210N/A unsigned int save_crlf:1;
1210N/A unsigned int failed:1;
181N/A};
181N/A
181N/Astatic int maildir_file_move(struct maildir_save_context *ctx,
181N/A const char *basename, const char *dest)
181N/A{
181N/A const char *tmp_path, *new_path;
181N/A int ret;
181N/A
181N/A t_push();
181N/A
181N/A /* if we have flags, we'll move it to cur/ directly, because files in
181N/A new/ directory can't have flags. alternative would be to write it
181N/A in new/ and set the flags dirty in index file, but in that case
181N/A external MUAs would see wrong flags. */
181N/A tmp_path = t_strconcat(ctx->tmpdir, "/", basename, NULL);
181N/A new_path = dest == NULL ?
181N/A t_strconcat(ctx->newdir, "/", basename, NULL) :
181N/A t_strconcat(ctx->curdir, "/", dest, NULL);
181N/A
181N/A if (link(tmp_path, new_path) == 0)
181N/A ret = 0;
181N/A else {
181N/A ret = -1;
181N/A if (ENOSPACE(errno)) {
1210N/A mail_storage_set_error(ctx->ibox->box.storage,
448N/A "Not enough disk space");
448N/A } else {
181N/A mail_storage_set_critical(ctx->ibox->box.storage,
448N/A "link(%s, %s) failed: %m", tmp_path, new_path);
1210N/A }
1210N/A }
1210N/A
1210N/A if (unlink(tmp_path) < 0 && errno != ENOENT) {
181N/A mail_storage_set_critical(ctx->ibox->box.storage,
448N/A "unlink(%s) failed: %m", tmp_path);
448N/A }
448N/A t_pop();
448N/A return ret;
448N/A}
181N/A
448N/Astatic struct maildir_save_context *
181N/Amaildir_transaction_save_init(struct maildir_transaction_context *t)
1210N/A{
1210N/A struct index_mailbox *ibox = t->ictx.ibox;
1210N/A struct maildir_save_context *ctx;
181N/A pool_t pool;
181N/A
181N/A pool = pool_alloconly_create("maildir_save_context", 4096);
181N/A ctx = p_new(pool, struct maildir_save_context, 1);
181N/A ctx->ctx.box = &ibox->box;
181N/A ctx->pool = pool;
181N/A ctx->ibox = ibox;
181N/A ctx->trans = mail_index_transaction_begin(ibox->view, FALSE, TRUE);
2238N/A
181N/A index_mail_init(&t->ictx, &ctx->mail, 0, NULL);
181N/A
1210N/A ctx->tmpdir = p_strconcat(pool, ibox->path, "/tmp", NULL);
448N/A ctx->newdir = p_strconcat(pool, ibox->path, "/new", NULL);
181N/A ctx->curdir = p_strconcat(pool, ibox->path, "/cur", NULL);
448N/A
448N/A ctx->save_crlf = getenv("MAIL_SAVE_CRLF") != NULL;
448N/A return ctx;
448N/A}
1210N/A
1210N/Astruct mail_save_context *
1210N/Amaildir_save_init(struct mailbox_transaction_context *_t,
448N/A const struct mail_full_flags *flags,
448N/A time_t received_date, int timezone_offset __attr_unused__,
448N/A const char *from_envelope __attr_unused__,
448N/A struct istream *input, int want_mail __attr_unused__)
181N/A{
448N/A struct maildir_transaction_context *t =
181N/A (struct maildir_transaction_context *)_t;
448N/A struct maildir_save_context *ctx;
181N/A struct index_mailbox *ibox = t->ictx.ibox;
181N/A struct maildir_filename *mf;
181N/A struct ostream *output;
1210N/A const char *fname, *dest_fname, *path;
181N/A enum mail_flags mail_flags;
464N/A keywords_mask_t keywords;
464N/A
181N/A t_push();
448N/A
181N/A if (t->save_ctx == NULL)
181N/A t->save_ctx = maildir_transaction_save_init(t);
181N/A ctx = t->save_ctx;
181N/A
181N/A /* create a new file in tmp/ directory */
181N/A ctx->fd = maildir_create_tmp(ibox, ctx->tmpdir, ibox->mail_create_mode,
1210N/A &path);
1210N/A if (ctx->fd == -1) {
1210N/A ctx->failed = TRUE;
181N/A t_pop();
181N/A return &ctx->ctx;
181N/A }
181N/A
181N/A fname = strrchr(path, '/');
181N/A i_assert(fname != NULL);
181N/A fname++;
379N/A
464N/A ctx->received_date = received_date;
464N/A ctx->input = input;
181N/A
379N/A output = o_stream_create_file(ctx->fd, system_pool, 0, FALSE);
464N/A ctx->output = ctx->save_crlf ?
464N/A o_stream_create_crlf(default_pool, output) :
464N/A o_stream_create_lf(default_pool, output);
464N/A o_stream_unref(output);
181N/A
181N/A mail_flags = (flags->flags & ~MAIL_RECENT) |
448N/A (ibox->keep_recent ? MAIL_RECENT : 0);
448N/A /*FIXME:if (!index_mailbox_fix_keywords(ibox, &mail_flags,
181N/A flags->keywords,
181N/A flags->keywords_count))
448N/A return FALSE;*/
181N/A
181N/A /* now, we want to be able to rollback the whole append session,
448N/A so we'll just store the name of this temp file and move it later
181N/A into new/ or cur/. if dest_fname is NULL, it's moved to new/,
181N/A otherwise to cur/. */
181N/A dest_fname = mail_flags == MAIL_RECENT ? NULL :
181N/A maildir_filename_set_flags(fname, mail_flags, NULL);
181N/A
448N/A mf = p_new(ctx->pool, struct maildir_filename, 1);
181N/A mf->next = ctx->files;
1210N/A mf->basename = p_strdup(ctx->pool, fname);
1210N/A mf->dest = p_strdup(ctx->pool, dest_fname);
181N/A ctx->files = mf;
181N/A
181N/A /* insert into index */
448N/A memset(keywords, 0, INDEX_KEYWORDS_BYTE_COUNT);
181N/A // FIXME: set keywords
181N/A
1210N/A mail_index_append(t->ictx.trans, 0, &ctx->seq);
1210N/A mail_index_update_flags(t->ictx.trans, ctx->seq, MODIFY_REPLACE,
181N/A mail_flags, keywords);
1210N/A t_pop();
1210N/A
181N/A ctx->failed = FALSE;
448N/A return &ctx->ctx;
181N/A}
181N/A
181N/Aint maildir_save_continue(struct mail_save_context *_ctx)
181N/A{
181N/A struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
181N/A
181N/A if (ctx->failed)
181N/A return -1;
181N/A
181N/A if (o_stream_send_istream(ctx->output, ctx->input) < 0) {
181N/A ctx->failed = TRUE;
448N/A return -1;
448N/A }
448N/A return 0;
448N/A}
448N/A
448N/Aint maildir_save_finish(struct mail_save_context *_ctx, struct mail **mail_r)
448N/A{
448N/A struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
448N/A struct utimbuf buf;
448N/A const char *path;
448N/A int output_errno;
448N/A
448N/A if (ctx->failed && ctx->fd == -1) {
181N/A /* tmp file creation failed */
181N/A return -1;
448N/A }
448N/A
448N/A t_push();
448N/A path = t_strconcat(ctx->tmpdir, "/", ctx->files->basename, NULL);
448N/A
448N/A if (ctx->received_date != (time_t)-1) {
448N/A /* set the received_date by modifying mtime */
448N/A buf.actime = ioloop_time;
448N/A buf.modtime = ctx->received_date;
448N/A
448N/A if (utime(path, &buf) < 0) {
448N/A ctx->failed = TRUE;
448N/A mail_storage_set_critical(ctx->ibox->box.storage,
448N/A "utime(%s) failed: %m", path);
448N/A }
448N/A }
448N/A
448N/A output_errno = ctx->output->stream_errno;
448N/A o_stream_unref(ctx->output);
448N/A ctx->output = NULL;
448N/A
448N/A /* FIXME: when saving multiple messages, we could get better
181N/A performance if we left the fd open and fsync()ed it later */
181N/A if (fsync(ctx->fd) < 0) {
181N/A mail_storage_set_critical(ctx->ibox->box.storage,
181N/A "fsync(%s) failed: %m", path);
181N/A ctx->failed = TRUE;
181N/A }
181N/A if (close(ctx->fd) < 0) {
181N/A mail_storage_set_critical(ctx->ibox->box.storage,
181N/A "close(%s) failed: %m", path);
181N/A ctx->failed = TRUE;
181N/A }
181N/A ctx->fd = -1;
181N/A
181N/A if (ctx->failed) {
181N/A /* delete the tmp file */
181N/A if (unlink(path) < 0 && errno != ENOENT) {
181N/A mail_storage_set_critical(ctx->ibox->box.storage,
181N/A "unlink(%s) failed: %m", path);
448N/A }
448N/A
448N/A errno = output_errno;
181N/A if (ENOSPACE(errno)) {
181N/A mail_storage_set_error(ctx->ibox->box.storage,
181N/A "Not enough disk space");
181N/A } else if (errno != 0) {
452N/A mail_storage_set_critical(ctx->ibox->box.storage,
181N/A "write(%s) failed: %m", ctx->ibox->path);
181N/A }
181N/A
3532N/A ctx->files = ctx->files->next;
3532N/A t_pop();
3532N/A return -1;
3532N/A }
3532N/A
3532N/A if (mail_r != NULL) {
3532N/A i_assert(ctx->seq != 0);
3532N/A
3532N/A if (index_mail_next(&ctx->mail, ctx->seq) < 0)
3532N/A return -1;
3532N/A *mail_r = &ctx->mail.mail;
3532N/A }
3532N/A
3532N/A t_pop();
3532N/A return 0;
3532N/A}
3532N/A
181N/Avoid maildir_save_cancel(struct mail_save_context *_ctx)
181N/A{
181N/A struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
181N/A
181N/A ctx->failed = TRUE;
181N/A (void)maildir_save_finish(_ctx, NULL);
181N/A}
181N/A
181N/Astatic void maildir_save_commit_abort(struct maildir_save_context *ctx,
181N/A struct maildir_filename *pos)
181N/A{
181N/A struct maildir_filename *mf;
181N/A string_t *str;
181N/A
181N/A t_push();
181N/A str = t_str_new(1024);
181N/A
181N/A /* try to unlink the mails already moved */
181N/A for (mf = ctx->files; mf != pos; mf = mf->next) {
181N/A str_truncate(str, 0);
181N/A if (mf->dest == NULL)
181N/A str_printfa(str, "%s/%s", ctx->newdir, mf->basename);
181N/A else
181N/A str_printfa(str, "%s/%s", ctx->curdir, mf->dest);
181N/A (void)unlink(str_c(str));
181N/A }
181N/A ctx->files = pos;
181N/A t_pop();
181N/A
181N/A maildir_transaction_save_rollback(ctx);
181N/A}
181N/A
181N/Aint maildir_transaction_save_commit_pre(struct maildir_save_context *ctx)
181N/A{
181N/A struct maildir_index_sync_context *sync_ctx;
181N/A struct maildir_filename *mf;
181N/A uint32_t first_uid, last_uid;
181N/A enum maildir_uidlist_rec_flag flags;
181N/A const char *fname;
181N/A uint32_t seq;
181N/A uoff_t offset;
181N/A int ret = 0;
181N/A
181N/A i_assert(ctx->output == NULL);
181N/A
181N/A sync_ctx = maildir_sync_index_begin(ctx->ibox);
181N/A if (sync_ctx == NULL) {
181N/A maildir_save_commit_abort(ctx, ctx->files);
3532N/A return -1;
181N/A }
181N/A
181N/A ret = maildir_uidlist_lock(ctx->ibox->uidlist);
181N/A if (ret <= 0) {
181N/A /* error or timeout - our transaction is broken */
181N/A maildir_sync_index_abort(sync_ctx);
maildir_save_commit_abort(ctx, ctx->files);
return -1;
}
if (maildir_sync_index_finish(sync_ctx, TRUE) < 0) {
maildir_save_commit_abort(ctx, ctx->files);
return -1;
}
first_uid = maildir_uidlist_get_next_uid(ctx->ibox->uidlist);
mail_index_append_assign_uids(ctx->trans, first_uid, &last_uid);
flags = MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
/* move them into new/ */
ctx->sync_ctx = maildir_uidlist_sync_init(ctx->ibox->uidlist, TRUE);
for (mf = ctx->files; mf != NULL; mf = mf->next) {
fname = mf->dest != NULL ? mf->dest : mf->basename;
if (maildir_file_move(ctx, mf->basename, mf->dest) < 0 ||
maildir_uidlist_sync_next(ctx->sync_ctx,
fname, flags) < 0) {
(void)maildir_uidlist_sync_deinit(ctx->sync_ctx);
maildir_save_commit_abort(ctx, mf);
return -1;
}
}
if (mail_index_transaction_commit(ctx->trans, &seq, &offset) < 0)
ret = -1;
return ret;
}
void maildir_transaction_save_commit_post(struct maildir_save_context *ctx)
{
/* can't do anything anymore if we fail */
(void)maildir_uidlist_sync_deinit(ctx->sync_ctx);
index_mail_deinit(&ctx->mail);
pool_unref(ctx->pool);
}
void maildir_transaction_save_rollback(struct maildir_save_context *ctx)
{
struct maildir_filename *mf;
string_t *str;
i_assert(ctx->output == NULL);
t_push();
str = t_str_new(1024);
/* clean up the temp files */
for (mf = ctx->files; mf != NULL; mf = mf->next) {
str_truncate(str, 0);
str_printfa(str, "%s/%s", ctx->tmpdir, mf->basename);
(void)unlink(str_c(str));
}
t_pop();
mail_index_transaction_rollback(ctx->trans);
index_mail_deinit(&ctx->mail);
pool_unref(ctx->pool);
}