view src/lib-storage/index/maildir/maildir-save.c @ 3279:b698ae839a18 HEAD

Moved mbox/maildir-specific variables from struct index_mailbox to mbox_mailbox and maildir_mailbox.
author Timo Sirainen <tss@iki.fi>
date Fri, 08 Apr 2005 16:13:45 +0300
parents 6a179bf1272e
children 2c72492dfd91
line wrap: on
line source

/* Copyright (C) 2002-2004 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "ostream.h"
#include "ostream-crlf.h"
#include "str.h"
#include "maildir-storage.h"
#include "maildir-uidlist.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <utime.h>
#include <sys/stat.h>

struct maildir_filename {
	struct maildir_filename *next;
	const char *basename, *dest;
};

struct maildir_save_context {
	struct mail_save_context ctx;
	pool_t pool;

	struct maildir_mailbox *mbox;
	struct mail_index_transaction *trans;
	struct maildir_uidlist_sync_ctx *sync_ctx;

	const char *tmpdir, *newdir, *curdir;
	struct maildir_filename *files;

	struct istream *input;
	struct ostream *output;
	int fd;
	time_t received_date;
	uint32_t seq;

	unsigned int failed:1;
};

static int maildir_file_move(struct maildir_save_context *ctx,
			     const char *basename, const char *dest)
{
	const char *tmp_path, *new_path;
	int ret;

	t_push();

	/* if we have flags, we'll move it to cur/ directly, because files in
	   new/ directory can't have flags. alternative would be to write it
	   in new/ and set the flags dirty in index file, but in that case
	   external MUAs would see wrong flags. */
	tmp_path = t_strconcat(ctx->tmpdir, "/", basename, NULL);
	new_path = dest == NULL ?
		t_strconcat(ctx->newdir, "/", basename, NULL) :
		t_strconcat(ctx->curdir, "/", dest, NULL);

	if (link(tmp_path, new_path) == 0)
		ret = 0;
	else {
		ret = -1;
		if (ENOSPACE(errno)) {
			mail_storage_set_error(&ctx->mbox->storage->storage,
					       "Not enough disk space");
		} else {
			mail_storage_set_critical(&ctx->mbox->storage->storage,
				"link(%s, %s) failed: %m", tmp_path, new_path);
		}
	}

	if (unlink(tmp_path) < 0 && errno != ENOENT) {
		mail_storage_set_critical(&ctx->mbox->storage->storage,
			"unlink(%s) failed: %m", tmp_path);
	}
	t_pop();
	return ret;
}

static struct maildir_save_context *
maildir_save_transaction_init(struct maildir_transaction_context *t)
{
        struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->ictx.ibox;
	struct maildir_save_context *ctx;
	pool_t pool;

	pool = pool_alloconly_create("maildir_save_context", 4096);
	ctx = p_new(pool, struct maildir_save_context, 1);
	ctx->ctx.transaction = &t->ictx.mailbox_ctx;
	ctx->pool = pool;
	ctx->mbox = mbox;
	ctx->trans = t->ictx.trans;

	ctx->tmpdir = p_strconcat(pool, mbox->path, "/tmp", NULL);
	ctx->newdir = p_strconcat(pool, mbox->path, "/new", NULL);
	ctx->curdir = p_strconcat(pool, mbox->path, "/cur", NULL);
	return ctx;
}

struct mail_save_context *
maildir_save_init(struct mailbox_transaction_context *_t,
		  enum mail_flags flags, struct mail_keywords *keywords,
		  time_t received_date, int timezone_offset __attr_unused__,
		  const char *from_envelope __attr_unused__,
		  struct istream *input, int want_mail __attr_unused__)
{
	struct maildir_transaction_context *t =
		(struct maildir_transaction_context *)_t;
	struct maildir_save_context *ctx;
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->ictx.ibox;
	struct maildir_filename *mf;
	struct ostream *output;
	const char *fname, *dest_fname, *path;

	i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);

	t_push();

	if (t->save_ctx == NULL)
		t->save_ctx = maildir_save_transaction_init(t);
	ctx = t->save_ctx;

	/* create a new file in tmp/ directory */
	ctx->fd = maildir_create_tmp(mbox, ctx->tmpdir, mbox->mail_create_mode,
				     &path);
	if (ctx->fd == -1) {
		ctx->failed = TRUE;
		t_pop();
		return &ctx->ctx;
	}

	fname = strrchr(path, '/');
	i_assert(fname != NULL);
	fname++;

	ctx->received_date = received_date;
	ctx->input = input;

	output = o_stream_create_file(ctx->fd, system_pool, 0, FALSE);
	ctx->output = (ctx->mbox->storage->storage.flags &
		       MAIL_STORAGE_FLAG_SAVE_CRLF) != 0 ?
		o_stream_create_crlf(default_pool, output) :
		o_stream_create_lf(default_pool, output);
	o_stream_unref(output);

	flags &= ~MAIL_RECENT;
	if (mbox->ibox.keep_recent)
		flags |= MAIL_RECENT;

	/* now, we want to be able to rollback the whole append session,
	   so we'll just store the name of this temp file and move it later
	   into new/ or cur/. if dest_fname is NULL, it's moved to new/,
	   otherwise to cur/. */
	dest_fname = flags == MAIL_RECENT ? NULL :
		maildir_filename_set_flags(fname, flags, NULL); // FIXME

	mf = p_new(ctx->pool, struct maildir_filename, 1);
	mf->next = ctx->files;
	mf->basename = p_strdup(ctx->pool, fname);
	mf->dest = p_strdup(ctx->pool, dest_fname);
	ctx->files = mf;

	/* insert into index */
	mail_index_append(ctx->trans, 0, &ctx->seq);
	mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_REPLACE, flags);
	if (keywords != NULL) {
		mail_index_update_keywords(ctx->trans, ctx->seq,
					   MODIFY_REPLACE, keywords);
	}
	t_pop();

	ctx->failed = FALSE;
	return &ctx->ctx;
}

int maildir_save_continue(struct mail_save_context *_ctx)
{
	struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;

	if (ctx->failed)
		return -1;

	if (o_stream_send_istream(ctx->output, ctx->input) < 0) {
		ctx->failed = TRUE;
		return -1;
	}
	return 0;
}

int maildir_save_finish(struct mail_save_context *_ctx, struct mail *dest_mail)
{
	struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
	struct utimbuf buf;
	const char *path;
	int output_errno;

	if (ctx->failed && ctx->fd == -1) {
		/* tmp file creation failed */
		return -1;
	}

	t_push();
	path = t_strconcat(ctx->tmpdir, "/", ctx->files->basename, NULL);

	if (ctx->received_date != (time_t)-1) {
		/* set the received_date by modifying mtime */
		buf.actime = ioloop_time;
		buf.modtime = ctx->received_date;

		if (utime(path, &buf) < 0) {
			ctx->failed = TRUE;
			mail_storage_set_critical(&ctx->mbox->storage->storage,
						  "utime(%s) failed: %m", path);
		}
	}

	output_errno = ctx->output->stream_errno;
	o_stream_unref(ctx->output);
	ctx->output = NULL;

	/* FIXME: when saving multiple messages, we could get better
	   performance if we left the fd open and fsync()ed it later */
	if (fsync(ctx->fd) < 0) {
		mail_storage_set_critical(&ctx->mbox->storage->storage,
					  "fsync(%s) failed: %m", path);
		ctx->failed = TRUE;
	}
	if (close(ctx->fd) < 0) {
		mail_storage_set_critical(&ctx->mbox->storage->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(&ctx->mbox->storage->storage,
				"unlink(%s) failed: %m", path);
		}

		errno = output_errno;
		if (ENOSPACE(errno)) {
			mail_storage_set_error(&ctx->mbox->storage->storage,
					       "Not enough disk space");
		} else if (errno != 0) {
			mail_storage_set_critical(&ctx->mbox->storage->storage,
				"write(%s) failed: %m", ctx->mbox->path);
		}

		ctx->files = ctx->files->next;
		t_pop();
		return -1;
	}

	if (dest_mail != NULL) {
		i_assert(ctx->seq != 0);

		if (mail_set_seq(dest_mail, ctx->seq) < 0)
			return -1;
	}

	t_pop();
	return 0;
}

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, NULL);
}

static void maildir_save_commit_abort(struct maildir_save_context *ctx,
				      struct maildir_filename *pos)
{
	struct maildir_filename *mf;
	string_t *str;

	t_push();
	str = t_str_new(1024);

	/* try to unlink the mails already moved */
	for (mf = ctx->files; mf != pos; mf = mf->next) {
		str_truncate(str, 0);
		if (mf->dest == NULL)
			str_printfa(str, "%s/%s", ctx->newdir, mf->basename);
		else
			str_printfa(str, "%s/%s", ctx->curdir, mf->dest);
		(void)unlink(str_c(str));
	}
	ctx->files = pos;
	t_pop();

	maildir_transaction_save_rollback(ctx);
}

int maildir_transaction_save_commit_pre(struct maildir_save_context *ctx)
{
	struct maildir_index_sync_context *sync_ctx;
	struct maildir_filename *mf;
	uint32_t first_uid, last_uid;
	enum maildir_uidlist_rec_flag flags;
	const char *fname;
	int ret;

	i_assert(ctx->output == NULL);

	sync_ctx = maildir_sync_index_begin(ctx->mbox);
	if (sync_ctx == NULL) {
		maildir_save_commit_abort(ctx, ctx->files);
		return -1;
	}

	ret = maildir_uidlist_lock(ctx->mbox->uidlist);
	if (ret <= 0) {
		/* error or timeout - our transaction is broken */
		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->mbox->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->mbox->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;
		}
	}

	return 0;
}

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);

	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();

	pool_unref(ctx->pool);
}