maildir-save.c revision a058590b1c1834650b1f7e552322b30566ab31b7
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
9bf2dc275ec21bff3d468ab1bc4fddc8874f7d1bTimo Sirainen /* unsigned int keywords[]; */
5c0034beb9933bca2a8b7d83d11dface1ea3b7faTimo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
5c0034beb9933bca2a8b7d83d11dface1ea3b7faTimo Sirainen struct maildir_keywords_sync_ctx *keywords_sync_ctx;
9bf2dc275ec21bff3d468ab1bc4fddc8874f7d1bTimo Sirainen struct maildir_filename *files, **files_tail, *file_last;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstatic int maildir_file_move(struct maildir_save_context *ctx,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* if we have flags, we'll move it to cur/ directly, because files in
5c0034beb9933bca2a8b7d83d11dface1ea3b7faTimo Sirainen new/ directory can't have flags. alternative would be to write it
5c0034beb9933bca2a8b7d83d11dface1ea3b7faTimo Sirainen in new/ and set the flags dirty in index file, but in that case
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen external MUAs would see wrong flags. */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen tmp_path = t_strconcat(ctx->tmpdir, "/", tmpname, NULL);
9bf2dc275ec21bff3d468ab1bc4fddc8874f7d1bTimo Sirainen t_strconcat(ctx->newdir, "/", destname, NULL) :
9bf2dc275ec21bff3d468ab1bc4fddc8874f7d1bTimo Sirainen t_strconcat(ctx->curdir, "/", destname, NULL);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* maildir spec says we should use link() + unlink() here. however
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen since our filename is guaranteed to be unique, rename() works just
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen as well, except faster. even if the filename wasn't unique, the
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen problem could still happen if the file was already moved from
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen new/ to cur/, so link() doesn't really provide any safety anyway.
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen Besides the small temporary performance benefits, this rename() is
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen almost required with OSX's HFS+ filesystem, since it implements
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen hard links in a pretty ugly way, which makes the performance crawl
be889d9b142fbb5604a922c6955bd7f6ea32f163Timo Sirainen when a lot of hard links are used. */
0cea9b1f4fa0495a48f5f097e40492517d67e1baTimo Sirainen "rename(%s, %s) failed: %m",
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenmaildir_save_transaction_init(struct maildir_transaction_context *t)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->ictx.ibox;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen pool = pool_alloconly_create("maildir_save_context", 4096);
8d6cb44a0161d88743756733f83c4fb278485987Timo Sirainen ctx = p_new(pool, struct maildir_save_context, 1);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen ctx->tmpdir = p_strconcat(pool, mbox->path, "/tmp", NULL);
8d6cb44a0161d88743756733f83c4fb278485987Timo Sirainen ctx->newdir = p_strconcat(pool, mbox->path, "/new", NULL);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen ctx->curdir = p_strconcat(pool, mbox->path, "/cur", NULL);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen ctx->keywords_buffer = buffer_create_const_data(pool, NULL, 0);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen array_create_from_buffer(&ctx->keywords_array, ctx->keywords_buffer,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen sizeof(unsigned int));
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenuint32_t maildir_save_add(struct maildir_transaction_context *t,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen const char *base_fname, enum mail_flags flags,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct maildir_save_context *ctx = t->save_ctx;
16598a1fb870ae40d6177755a4306216e4d6a4cdTimo Sirainen /* now, we want to be able to rollback the whole append session,
16598a1fb870ae40d6177755a4306216e4d6a4cdTimo Sirainen so we'll just store the name of this temp file and move it later
16598a1fb870ae40d6177755a4306216e4d6a4cdTimo Sirainen into new/ or cur/. */
16598a1fb870ae40d6177755a4306216e4d6a4cdTimo Sirainen /* @UNSAFE */
16598a1fb870ae40d6177755a4306216e4d6a4cdTimo Sirainen sizeof(unsigned int) * (keywords == NULL ? 0 :
16598a1fb870ae40d6177755a4306216e4d6a4cdTimo Sirainen mf->basename = p_strdup(ctx->pool, base_fname);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen i_assert(sizeof(keywords->idx[0]) == sizeof(unsigned int));
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* @UNSAFE */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* insert into index */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_REPLACE, flags);
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen mail_index_update_keywords(ctx->trans, ctx->seq,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* FIXME: copying with hardlinking. we could copy the
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen cached data directly */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen input = index_mail_cache_parse_init(dest_mail, ctx->input);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenmaildir_get_updated_filename(struct maildir_save_context *ctx,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen const char **fname_r)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen if (ctx->mbox->storage->save_size_in_filename &&
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /*if (mf->vsize != (uoff_t)-1) {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen MAILDIR_EXTRA_VIRTUAL_SIZE,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen if ((mf->flags & MAIL_FLAGS_MASK) == MAIL_RECENT) {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen *fname_r = maildir_filename_set_flags(NULL, basename,
114a0f74e0f825c6bd8aeadfafb248a030762a1fTimo Sirainen buffer_update_const_data(ctx->keywords_buffer, mf + 1,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen *fname_r = maildir_filename_set_flags(ctx->keywords_sync_ctx, basename,
265e9d8457aa5ce01b7aad954632aed8c36deeb8Timo Sirainenstatic const char *maildir_mf_get_path(struct maildir_save_context *ctx,
265e9d8457aa5ce01b7aad954632aed8c36deeb8Timo Sirainen if (!ctx->moving && (mf->flags & MAILDIR_SAVE_FLAG_HARDLINK) == 0) {
265e9d8457aa5ce01b7aad954632aed8c36deeb8Timo Sirainen /* file is still in tmp/ */
265e9d8457aa5ce01b7aad954632aed8c36deeb8Timo Sirainen return t_strdup_printf("%s/%s", ctx->tmpdir, mf->basename);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* already moved to new/ or cur/ */
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen if (maildir_get_updated_filename(ctx, mf, &fname))
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen return t_strdup_printf("%s/%s", ctx->newdir, mf->basename);
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen return t_strdup_printf("%s/%s", ctx->curdir, fname);
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainenconst char *maildir_save_file_get_path(struct mailbox_transaction_context *_t,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct maildir_save_context *ctx = t->save_ctx;
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen while (seq > 0) {
5c0034beb9933bca2a8b7d83d11dface1ea3b7faTimo Sirainenstatic int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
5c0034beb9933bca2a8b7d83d11dface1ea3b7faTimo Sirainen const char **fname_r)
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen /* stat() first to see if it exists. pretty much the only
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen possibility of that happening is if time had moved
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen backwards, but even then it's highly unlikely. */
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen /* try another file name */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* doesn't exist */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen mode_t old_mask = umask(0777 & ~box->file_create_mode);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* race condition between stat() and open().
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen highly unlikely. */
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen } else if (box->file_create_gid != (gid_t)-1) {
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainenint maildir_save_init(struct mailbox_transaction_context *_t,
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen enum mail_flags flags, struct mail_keywords *keywords,
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen time_t received_date, int timezone_offset ATTR_UNUSED,
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen struct istream *input, struct mail *dest_mail,
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->ictx.ibox;
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen t->save_ctx = maildir_save_transaction_init(t);
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen /* create a new file in tmp/ directory */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen ctx->fd = maildir_create_tmp(mbox, ctx->tmpdir, &fname);
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen ctx->input = (ctx->mbox->storage->storage.flags &
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen maildir_save_add(t, fname, flags, keywords, dest_mail);
e96fb85799dc95603bb1a6b4d3685df2d042a2f8Timo Sirainen ctx->output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainenint maildir_save_continue(struct mail_save_context *_ctx)
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen if (o_stream_send_istream(ctx->output, ctx->input) < 0) {
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen "o_stream_send_istream(%s/%s) "
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen "failed: %m",
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen index_mail_cache_parse_continue(ctx->cur_dest_mail);
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen /* both tee input readers may consume data from our primary
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen input stream. we'll have to make sure we don't return with
a817fdcc43aedf423e2134091d5f83f91d64bcc9Timo Sirainen one of the streams still having data in them. */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstatic int maildir_save_finish_real(struct mail_save_context *_ctx)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* tmp file creation failed */
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen path = t_strconcat(ctx->tmpdir, "/", ctx->file_last->basename, NULL);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* set the received_date by modifying mtime */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* hardlinked */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen index_mail_cache_parse_deinit(ctx->cur_dest_mail,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen /* remember the size in case we want to add it to filename */
} else if (errno != 0) {
int ret;
T_BEGIN {
} T_END;
return ret;
T_BEGIN {
} T_END;
return ret;
if (new_changed) {
if (cur_changed) {
struct maildir_transaction_context *t =
int ret;
ret = 0;
T_BEGIN {
const char *dest;
ret = 0;
if (newdir)
} T_END;
if (ret < 0)
if (ret == 0) {
const char *dest;
if (newdir)
} T_END;
if (sync_commit) {
if (ret < 0) {
return ret;
if (hardlinks)
T_BEGIN {
} T_END;