maildir-save.c revision 5192589b0f8942a741c8ea58bb45377b2703d752
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen/* Copyright (c) 2002-2014 Dovecot authors, see the included COPYING file */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* unsigned int keywords[]; */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_keywords_sync_ctx *keywords_sync_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_filename *files, **files_tail, *file_last;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int maildir_file_move(struct maildir_save_context *ctx,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_filename *mf, const char *destname,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* if we have flags, we'll move it to cur/ directly, because files in
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen new/ directory can't have flags. alternative would be to write it
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen in new/ and set the flags dirty in index file, but in that case
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen external MUAs would see wrong flags. */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen tmp_path = t_strconcat(ctx->tmpdir, "/", mf->tmp_name, NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen t_strconcat(ctx->newdir, "/", destname, NULL) :
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen t_strconcat(ctx->curdir, "/", destname, NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* maildir spec says we should use link() + unlink() here. however
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen since our filename is guaranteed to be unique, rename() works just
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen as well, except faster. even if the filename wasn't unique, the
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen problem could still happen if the file was already moved from
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen new/ to cur/, so link() doesn't really provide any safety anyway.
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen Besides the small temporary performance benefits, this rename() is
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen almost required with OSX's HFS+ filesystem, since it implements
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen hard links in a pretty ugly way, which makes the performance crawl
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen when a lot of hard links are used. */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_storage_set_error(storage, MAIL_ERROR_NOSPACE,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainenmaildir_save_transaction_init(struct mailbox_transaction_context *t)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->box;
049da065aa64c1a5ed46eed6cde7382b011612a9Timo Sirainen pool = pool_alloconly_create("maildir_save_context", 4096);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx = p_new(pool, struct maildir_save_context, 1);
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen ctx->tmpdir = p_strconcat(pool, path, "/tmp", NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->newdir = p_strconcat(pool, path, "/new", NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->curdir = p_strconcat(pool, path, "/cur", NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen buffer_create_from_const_data(&ctx->keywords_buffer, "", 0);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen array_create_from_buffer(&ctx->keywords_array, &ctx->keywords_buffer,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen sizeof(unsigned int));
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenmaildir_save_add(struct mail_save_context *_ctx, const char *tmp_fname,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* allow caller to specify recent flag only when uid is specified
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen (we're replicating, converting, etc.). */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* now, we want to be able to rollback the whole append session,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen so we'll just store the name of this temp file and move it later
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen into new/ or cur/. */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* @UNSAFE */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen keyword_count = mdata->keywords == NULL ? 0 : mdata->keywords->count;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen sizeof(unsigned int) * keyword_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mf->tmp_name = mf->dest_basename = p_strdup(ctx->pool, tmp_fname);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* @UNSAFE */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen sizeof(unsigned int) * keyword_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mf->pop3_uidl = p_strdup(ctx->pool, mdata->pop3_uidl);
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen /* insert into index */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_index_append(ctx->trans, mdata->uid, &ctx->seq);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_index_update_keywords(ctx->trans, ctx->seq,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_index_update_modseq(ctx->trans, ctx->seq,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->mail = mail_alloc(_ctx->transaction, 0, NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_set_seq_saving(_ctx->dest_mail, ctx->seq);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* copying with hardlinking. */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen index_copy_cache_fields(_ctx, src_mail, ctx->seq);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen input = index_mail_cache_parse_init(_ctx->dest_mail,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenvoid maildir_save_set_dest_basename(struct mail_save_context *_ctx,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mf->dest_basename = p_strdup(ctx->pool, basename);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenvoid maildir_save_set_sizes(struct maildir_filename *mf,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenmaildir_get_dest_filename(struct maildir_save_context *ctx,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen const char **fname_r)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (mf->size != (uoff_t)-1 && !mf->preserve_filename) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (mf->vsize != (uoff_t)-1 && !mf->preserve_filename) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if ((mf->flags & MAIL_FLAGS_MASK) == MAIL_RECENT) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen *fname_r = maildir_filename_flags_set(basename,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_assert(ctx->keywords_sync_ctx != NULL || mf->keywords_count == 0);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen buffer_create_from_const_data(&ctx->keywords_buffer, mf + 1,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen *fname_r = maildir_filename_flags_kw_set(ctx->keywords_sync_ctx,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic const char *maildir_mf_get_path(struct maildir_save_context *ctx,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if ((mf->flags & MAILDIR_FILENAME_FLAG_MOVED) == 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* file is still in tmp/ */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen return t_strdup_printf("%s/%s", ctx->tmpdir, mf->tmp_name);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* already moved to new/ or cur/ */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen dir = maildir_get_dest_filename(ctx, mf, &fname) ?
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenmaildir_save_get_mf(struct mailbox_transaction_context *t, uint32_t seq)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen while (seq > 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenint maildir_save_file_get_size(struct mailbox_transaction_context *t,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_filename *mf = maildir_save_get_mf(t, seq);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenconst char *maildir_save_file_get_path(struct mailbox_transaction_context *t,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_filename *mf = maildir_save_get_mf(t, seq);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen const char **fname_r)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen const struct mailbox_permissions *perm = mailbox_get_permissions(box);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* the generated filename is unique. the only reason why it
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen might return an existing filename is if the time moved
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen backwards. so we'll use O_EXCL anyway, although it's mostly
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen old_mask = umask(0777 & ~perm->file_create_mode);
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen } else if (perm->file_create_gid != (gid_t)-1) {
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen if (fchown(fd, (uid_t)-1, perm->file_create_gid) < 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenmaildir_save_alloc(struct mailbox_transaction_context *t)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen t->save_ctx = maildir_save_transaction_init(t);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenint maildir_save_begin(struct mail_save_context *_ctx, struct istream *input)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* new mail, new failure state */
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen /* create a new file in tmp/ directory */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->fd = maildir_create_tmp(ctx->mbox, ctx->tmpdir, &fname);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (ctx->mbox->storage->storage.set->mail_save_crlf)
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen _ctx->data.output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenint maildir_save_continue(struct mail_save_context *_ctx)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (o_stream_send_istream(_ctx->data.output, ctx->input) < 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "o_stream_send_istream(%s/%s) "
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "failed: %m",
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen index_mail_cache_parse_continue(ctx->cur_dest_mail);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* both tee input readers may consume data from our primary
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen input stream. we'll have to make sure we don't return with
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen one of the streams still having data in them. */
0e7d5ff38f28d8c85e197a031bbb66b322ff89e6Timo Sirainenstatic int maildir_save_finish_received_date(struct maildir_save_context *ctx,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (ctx->ctx.data.received_date != (time_t)-1) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* set the received_date by modifying mtime */
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen /* hardlinked */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic void maildir_save_remove_last_filename(struct maildir_save_context *ctx)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* currently we can't just drop pending cache updates for this one
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen specific record, so we'll reset the whole cache transaction. */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_cache_transaction_reset(ctx->ctx.transaction->cache_trans);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen for (fm = &ctx->files; (*fm)->next != NULL; fm = &(*fm)->next) ;
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainenstatic int maildir_save_finish_real(struct mail_save_context *_ctx)
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* tmp file creation failed */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen path = t_strconcat(ctx->tmpdir, "/", ctx->file_last->tmp_name, NULL);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (!ctx->failed && o_stream_nfinish(_ctx->data.output) < 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* we can't change ctime, but we can add the date to cache */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct index_mail *mail = (struct index_mail *)_ctx->dest_mail;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t));
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (maildir_save_finish_received_date(ctx, path) < 0)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen index_mail_cache_parse_deinit(ctx->cur_dest_mail,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* remember the size in case we want to add it to filename */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->file_last->size = _ctx->data.output->offset;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen output_errno = _ctx->data.output->last_failed_errno;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER &&
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen } else if (real_size != (off_t)ctx->file_last->size &&
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen (!maildir_filename_get_size(ctx->file_last->dest_basename,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* e.g. zlib plugin was used. the "physical size" must be in
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen the maildir filename, since stat() will return wrong size */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* preserve the GUID if needed */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->file_last->guid = ctx->file_last->dest_basename;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* reset the base name as well, just in case there's a
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx->file_last->dest_basename = ctx->file_last->tmp_name;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* delete the tmp file */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen } else if (errno != 0) {
int ret;
T_BEGIN {
} T_END;
return ret;
} T_END;
if (new_changed) {
if (cur_changed) {
static uint32_t
unsigned int i, count;
if (count == 0)
for (i = 0; i < count; i++) {
int ret;
if (ret == 0) {
T_BEGIN {
} T_END;
first_recent_uid = 0;
if (first_recent_uid != 0) {
return TRUE;
return TRUE;
return FALSE;
&size))
int ret;
T_BEGIN {
const char *dest;
prev_mf);
if (newdir)
} T_END;
if (ret < 0)
int ret;
const char *dest;
if (newdir)
} T_END;
int ret;
if (ret > 0) {
} else if (ret == 0 &&
T_BEGIN {
} T_END;
if (ret == 0) {
ret == 0) < 0)
if (ret < 0)
if (ret < 0) {