maildir-save.c revision 28ec56f48d67d0a43f77f9b16b5005418a404bbf
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "lib.h"
80c1d98d3638b71e57a39cafa88b9122bf8169c6Timo Sirainen#include "ioloop.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "array.h"
94a8cb0ee1d85569ad1a2acacd92d3ce22f8a1cbTimo Sirainen#include "buffer.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "istream.h"
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen#include "istream-crlf.h"
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen#include "ostream.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "fdatasync-path.h"
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen#include "eacces-error.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "str.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "index-mail.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "maildir-storage.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "maildir-uidlist.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "maildir-keywords.h"
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainen#include "maildir-filename.h"
8887bf3757d51d73887dd20b1db3334d867d3817Timo Sirainen#include "maildir-sync.h"
8887bf3757d51d73887dd20b1db3334d867d3817Timo Sirainen
369a1084c500a9df7448ffa9409ce32e42060bc2Timo Sirainen#include <stdio.h>
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen#include <stdlib.h>
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen#include <unistd.h>
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen#include <fcntl.h>
a53cb86b4d733d9c48ee4d285bed477c80825804Timo Sirainen#include <utime.h>
a53cb86b4d733d9c48ee4d285bed477c80825804Timo Sirainen#include <sys/stat.h>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#define MAILDIR_FILENAME_FLAG_MOVED 0x10000000
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstruct maildir_filename {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_filename *next;
b7b81543899e306c71e6152516d8698416162bcbTimo Sirainen const char *basename;
fd3d711f219fd6813492acbe051e04327f0ca0f0Timo Sirainen
6ec7cf71ccd0eed1f9cc1b0bda8960796b04160bTimo Sirainen uoff_t size, vsize;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen enum mail_flags flags;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int preserve_filename:1;
fd3d711f219fd6813492acbe051e04327f0ca0f0Timo Sirainen unsigned int keywords_count;
fd3d711f219fd6813492acbe051e04327f0ca0f0Timo Sirainen /* unsigned int keywords[]; */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen};
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstruct maildir_save_context {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct mail_save_context ctx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen pool_t pool;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_mailbox *mbox;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct mail_index_transaction *trans;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_keywords_sync_ctx *keywords_sync_ctx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_index_sync_context *sync_ctx;
8e7da21696c9f8a6d5e601243fb6172ec85d47b2Timo Sirainen struct mail *mail, *cur_dest_mail;
024815ea2ffdda9ea79919f18e865663977f73eaTimo Sirainen
024815ea2ffdda9ea79919f18e865663977f73eaTimo Sirainen const char *tmpdir, *newdir, *curdir;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen struct maildir_filename *files, **files_tail, *file_last;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int files_count;
811f2e26d9782d9cb99fdf82e18ffa0a77564fe2Timo Sirainen
811f2e26d9782d9cb99fdf82e18ffa0a77564fe2Timo Sirainen buffer_t keywords_buffer;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ARRAY_TYPE(keyword_indexes) keywords_array;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct istream *input;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen int fd;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen uint32_t first_seq, seq, last_nonrecent_uid;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int have_keywords:1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int locked:1;
1d3f7c1278168d5b1cbfa9a2cc9929a0909056b4Timo Sirainen unsigned int failed:1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int last_save_finished:1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen};
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
1d3f7c1278168d5b1cbfa9a2cc9929a0909056b4Timo Sirainenstatic int maildir_file_move(struct maildir_save_context *ctx,
1d3f7c1278168d5b1cbfa9a2cc9929a0909056b4Timo Sirainen struct maildir_filename *mf, const char *destname,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen bool newdir)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char *tmp_path, *new_path;
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen /* if we have flags, we'll move it to cur/ directly, because files in
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen new/ directory can't have flags. alternative would be to write it
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen in new/ and set the flags dirty in index file, but in that case
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen external MUAs would see wrong flags. */
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen tmp_path = t_strconcat(ctx->tmpdir, "/", mf->basename, NULL);
2a734f36105e33ab452d057df6bc7a2b7d9f96f0Timo Sirainen new_path = newdir ?
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen t_strconcat(ctx->newdir, "/", destname, NULL) :
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen t_strconcat(ctx->curdir, "/", destname, NULL);
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen /* maildir spec says we should use link() + unlink() here. however
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen since our filename is guaranteed to be unique, rename() works just
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen as well, except faster. even if the filename wasn't unique, the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen problem could still happen if the file was already moved from
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen new/ to cur/, so link() doesn't really provide any safety anyway.
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Besides the small temporary performance benefits, this rename() is
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen almost required with OSX's HFS+ filesystem, since it implements
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen hard links in a pretty ugly way, which makes the performance crawl
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen when a lot of hard links are used. */
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen if (rename(tmp_path, new_path) == 0) {
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen mf->flags |= MAILDIR_FILENAME_FLAG_MOVED;
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen return 0;
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen } else if (ENOSPACE(errno)) {
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen mail_storage_set_error(storage, MAIL_ERROR_NOSPACE,
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen MAIL_ERRSTR_NO_SPACE);
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen return -1;
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen } else {
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen tmp_path, new_path);
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen return -1;
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen }
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainen}
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainen
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainenstatic struct mail_save_context *
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainenmaildir_save_transaction_init(struct mailbox_transaction_context *t)
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainen{
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->box;
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen struct maildir_save_context *ctx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen pool_t pool;
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen pool = pool_alloconly_create("maildir_save_context", 4096);
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen ctx = p_new(pool, struct maildir_save_context, 1);
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen ctx->ctx.transaction = t;
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen ctx->pool = pool;
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen ctx->mbox = mbox;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->trans = ((struct index_transaction_context *)t)->trans;
6bc98d3898c475ba7615ba2b016e5142c8b2c09fTimo Sirainen ctx->files_tail = &ctx->files;
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen ctx->fd = -1;
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen ctx->tmpdir = p_strconcat(pool, mbox->ibox.box.path, "/tmp", NULL);
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen ctx->newdir = p_strconcat(pool, mbox->ibox.box.path, "/new", NULL);
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainen ctx->curdir = p_strconcat(pool, mbox->ibox.box.path, "/cur", NULL);
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainen
c8adec8db635f5efb13b9879a5f3fb523abdc969Timo Sirainen buffer_create_const_data(&ctx->keywords_buffer, NULL, 0);
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen array_create_from_buffer(&ctx->keywords_array, &ctx->keywords_buffer,
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen sizeof(unsigned int));
7761758f43d6150be4b07f4c54457ce662f78c4cTimo Sirainen ctx->last_save_finished = TRUE;
6bc98d3898c475ba7615ba2b016e5142c8b2c09fTimo Sirainen return &ctx->ctx;
62f4a199b5c9a0862f486cbf18e195cc621bbe25Timo Sirainen}
62f4a199b5c9a0862f486cbf18e195cc621bbe25Timo Sirainen
62f4a199b5c9a0862f486cbf18e195cc621bbe25Timo Sirainenstruct maildir_filename *
62f4a199b5c9a0862f486cbf18e195cc621bbe25Timo Sirainenmaildir_save_add(struct mail_save_context *_ctx, const char *base_fname)
62f4a199b5c9a0862f486cbf18e195cc621bbe25Timo Sirainen{
6bc98d3898c475ba7615ba2b016e5142c8b2c09fTimo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_filename *mf;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct istream *input;
659fe5d24825b160cae512538088020d97a60239Timo Sirainen unsigned int keyword_count;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* allow caller to specify recent flag only when uid is specified
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen (we're replicating, converting, etc.). */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (_ctx->uid == 0)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen _ctx->flags |= MAIL_RECENT;
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen else if ((_ctx->flags & MAIL_RECENT) == 0 &&
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen ctx->last_nonrecent_uid < _ctx->uid)
26ff8f8a4867bf8e9551a27a2de8c12cd138b065Timo Sirainen ctx->last_nonrecent_uid = _ctx->uid;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen /* now, we want to be able to rollback the whole append session,
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen so we'll just store the name of this temp file and move it later
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen into new/ or cur/. */
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen /* @UNSAFE */
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen keyword_count = _ctx->keywords == NULL ? 0 : _ctx->keywords->count;
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen mf = p_malloc(ctx->pool, sizeof(*mf) +
020a39a395d2adb768e0179631b37bc78ecd9471Timo Sirainen sizeof(unsigned int) * keyword_count);
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen mf->basename = p_strdup(ctx->pool, base_fname);
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen mf->flags = _ctx->flags;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mf->size = (uoff_t)-1;
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen mf->vsize = (uoff_t)-1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->file_last = mf;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_assert(*ctx->files_tail == NULL);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen *ctx->files_tail = mf;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->files_tail = &mf->next;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->files_count++;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (_ctx->keywords != NULL) {
20a802016205bbcafc90f164f769ea801f88d014Timo Sirainen /* @UNSAFE */
20a802016205bbcafc90f164f769ea801f88d014Timo Sirainen mf->keywords_count = keyword_count;
20a802016205bbcafc90f164f769ea801f88d014Timo Sirainen memcpy(mf + 1, _ctx->keywords->idx,
a53cb86b4d733d9c48ee4d285bed477c80825804Timo Sirainen sizeof(unsigned int) * keyword_count);
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen ctx->have_keywords = TRUE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
a045c3aba2610c6ed0bf1c346df1c6d8f7b9fbfdTimo Sirainen /* insert into index */
a045c3aba2610c6ed0bf1c346df1c6d8f7b9fbfdTimo Sirainen mail_index_append(ctx->trans, _ctx->uid, &ctx->seq);
a045c3aba2610c6ed0bf1c346df1c6d8f7b9fbfdTimo Sirainen mail_index_update_flags(ctx->trans, ctx->seq,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen MODIFY_REPLACE, _ctx->flags & ~MAIL_RECENT);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (_ctx->keywords != NULL) {
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen mail_index_update_keywords(ctx->trans, ctx->seq,
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen MODIFY_REPLACE, _ctx->keywords);
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen }
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen if (_ctx->min_modseq != 0) {
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen mail_index_update_modseq(ctx->trans, ctx->seq,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen _ctx->min_modseq);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (ctx->first_seq == 0) {
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen ctx->first_seq = ctx->seq;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_assert(ctx->files->next == NULL);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (_ctx->dest_mail == NULL) {
df4018ae2f0a95be602f724ca70df7e0e3bd6a7dTimo Sirainen if (ctx->mail == NULL)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->mail = mail_alloc(_ctx->transaction, 0, NULL);
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen _ctx->dest_mail = ctx->mail;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mail_set_seq(_ctx->dest_mail, ctx->seq);
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen if (ctx->input == NULL) {
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen /* FIXME: copying with hardlinking. we could copy the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen cached data directly */
7c95b03620a03a43dd72d39608cea5fc77393ad6Timo Sirainen ctx->cur_dest_mail = NULL;
db87d16551d1081ada01f787ea21aa3ed1402c31Timo Sirainen } else {
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen input = index_mail_cache_parse_init(_ctx->dest_mail,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->input);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_stream_unref(&ctx->input);
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen ctx->input = input;
eac3948d67eff8623d51aeaea9eca582f3aec677Timo Sirainen ctx->cur_dest_mail = _ctx->dest_mail;
2674b4f0cf8f3c203d8e56b29735f5e267038dafTimo Sirainen }
e376693bfa3985232c41df99c7010fca22612c89Timo Sirainen return mf;
48136ae5a0eb49daa44e343553f3688a500307e2Timo Sirainen}
48136ae5a0eb49daa44e343553f3688a500307e2Timo Sirainen
2674b4f0cf8f3c203d8e56b29735f5e267038dafTimo Sirainenstatic bool
e376693bfa3985232c41df99c7010fca22612c89Timo Sirainenmaildir_get_updated_filename(struct maildir_save_context *ctx,
e376693bfa3985232c41df99c7010fca22612c89Timo Sirainen struct maildir_filename *mf,
e376693bfa3985232c41df99c7010fca22612c89Timo Sirainen const char **fname_r)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen const char *basename = mf->basename;
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen if (ctx->mbox->storage->save_size_in_filename &&
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen mf->size != (uoff_t)-1 && !mf->preserve_filename) {
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen MAILDIR_EXTRA_FILE_SIZE, mf->size);
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (mf->vsize != (uoff_t)-1 && !mf->preserve_filename) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
b92813e2f96d4b28f989528ed5dd6115da7d9bdbTimo Sirainen MAILDIR_EXTRA_VIRTUAL_SIZE,
b92813e2f96d4b28f989528ed5dd6115da7d9bdbTimo Sirainen mf->vsize);
b92813e2f96d4b28f989528ed5dd6115da7d9bdbTimo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen if (mf->keywords_count == 0) {
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen if ((mf->flags & MAIL_FLAGS_MASK) == MAIL_RECENT) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen *fname_r = basename;
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen return TRUE;
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen }
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen *fname_r = maildir_filename_set_flags(NULL, basename,
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen mf->flags & MAIL_FLAGS_MASK, NULL);
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen return FALSE;
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen }
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen i_assert(ctx->keywords_sync_ctx != NULL || mf->keywords_count == 0);
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen buffer_create_const_data(&ctx->keywords_buffer, mf + 1,
d152ccd0d29fae1bc6092bf198ee7eb843202f96Timo Sirainen mf->keywords_count * sizeof(unsigned int));
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen *fname_r = maildir_filename_set_flags(ctx->keywords_sync_ctx, basename,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mf->flags & MAIL_FLAGS_MASK,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen &ctx->keywords_array);
aa0647f2debf0d48d504a321186f66c85596aaf4Timo Sirainen return FALSE;
2d39dc1a453546892109b35c0d9770369011a13dTimo Sirainen}
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainenstatic const char *maildir_mf_get_path(struct maildir_save_context *ctx,
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen struct maildir_filename *mf)
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen{
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen const char *fname;
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen if ((mf->flags & MAILDIR_FILENAME_FLAG_MOVED) == 0) {
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen /* file is still in tmp/ */
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen return t_strdup_printf("%s/%s", ctx->tmpdir, mf->basename);
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen }
8f8315e4b4e27ead12dd1c3da65bf4dee3762f18Timo Sirainen
768b7f5783c8de119d7386321e5d0c72d5c2d9f6Timo Sirainen /* already moved to new/ or cur/ */
768b7f5783c8de119d7386321e5d0c72d5c2d9f6Timo Sirainen if (maildir_get_updated_filename(ctx, mf, &fname))
768b7f5783c8de119d7386321e5d0c72d5c2d9f6Timo Sirainen return t_strdup_printf("%s/%s", ctx->newdir, mf->basename);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen else
b08b33d1f5ce3721dc2d83586c9cb0ca141331fdTimo Sirainen return t_strdup_printf("%s/%s", ctx->curdir, fname);
b08b33d1f5ce3721dc2d83586c9cb0ca141331fdTimo Sirainen}
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainenconst char *maildir_save_file_get_path(struct mailbox_transaction_context *t,
89caf81340a4da959ef18c5f9b9c99824a53066bTimo Sirainen uint32_t seq)
89caf81340a4da959ef18c5f9b9c99824a53066bTimo Sirainen{
89caf81340a4da959ef18c5f9b9c99824a53066bTimo Sirainen struct maildir_save_context *save_ctx =
d9de52132072d80b8c268094b879c0ef5a108db3Timo Sirainen (struct maildir_save_context *)t->save_ctx;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen struct maildir_filename *mf;
d9de52132072d80b8c268094b879c0ef5a108db3Timo Sirainen
d9de52132072d80b8c268094b879c0ef5a108db3Timo Sirainen i_assert(seq >= save_ctx->first_seq);
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen seq -= save_ctx->first_seq;
685393de106e55b61f754d420e378d05bd462ebbTimo Sirainen mf = save_ctx->files;
87712707722ef7d73acb065546e61afa4455cd9eTimo Sirainen while (seq > 0) {
3b80595fcf2001cf7b2fcc6290823e38f4a142fcTimo Sirainen mf = mf->next;
3b80595fcf2001cf7b2fcc6290823e38f4a142fcTimo Sirainen i_assert(mf != NULL);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen seq--;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen return maildir_mf_get_path(save_ctx, mf);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen}
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
7761758f43d6150be4b07f4c54457ce662f78c4cTimo Sirainenstatic int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char **fname)
c979eeda1f46483d9c963e265786b701d7683d77Timo Sirainen{
c979eeda1f46483d9c963e265786b701d7683d77Timo Sirainen struct mailbox *box = &mbox->ibox.box;
94a8cb0ee1d85569ad1a2acacd92d3ce22f8a1cbTimo Sirainen struct stat st;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int prefix_len;
94a8cb0ee1d85569ad1a2acacd92d3ce22f8a1cbTimo Sirainen const char *tmp_fname = *fname;
c979eeda1f46483d9c963e265786b701d7683d77Timo Sirainen string_t *path;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen int fd;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
b20fb5b1df9d604a7541f5118fc5b4b466d211efTimo Sirainen path = t_str_new(256);
b20fb5b1df9d604a7541f5118fc5b4b466d211efTimo Sirainen str_append(path, dir);
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen str_append_c(path, '/');
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen prefix_len = str_len(path);
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen for (;;) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen if (tmp_fname == NULL)
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen tmp_fname = maildir_filename_generate();
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen str_truncate(path, prefix_len);
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen str_append(path, tmp_fname);
bb26f09873c18f342cd1ab2d0ee0b9018e6546d9Timo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen /* stat() first to see if it exists. pretty much the only
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen possibility of that happening is if time had moved
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen backwards, but even then it's highly unlikely. */
b5ea11802f2bafbec06282a7b3b6704dc5fae584Timo Sirainen if (stat(str_c(path), &st) == 0) {
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen /* try another file name */
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen } else if (errno != ENOENT) {
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen mail_storage_set_critical(box->storage,
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen "stat(%s) failed: %m", str_c(path));
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen return -1;
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen } else {
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen /* doesn't exist */
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen mode_t old_mask = umask(0777 & ~box->file_create_mode);
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen fd = open(str_c(path),
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen umask(old_mask);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen if (fd != -1 || errno != EEXIST)
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen break;
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen /* race condition between stat() and open().
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen highly unlikely. */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen tmp_fname = NULL;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen *fname = tmp_fname;
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen if (fd == -1) {
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen if (ENOSPACE(errno)) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mail_storage_set_error(box->storage,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen MAIL_ERROR_NOSPACE, MAIL_ERRSTR_NO_SPACE);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen } else {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mail_storage_set_critical(box->storage,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen "open(%s) failed: %m", str_c(path));
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen } else if (box->file_create_gid != (gid_t)-1) {
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen if (errno == EPERM) {
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen mail_storage_set_critical(box->storage, "%s",
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen eperm_error_get_chgrp("fchown",
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen str_c(path),
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen box->file_create_gid,
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen box->file_create_gid_origin));
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen } else {
6825360d446542046757b06064282301c4c6b27cTimo Sirainen mail_storage_set_critical(box->storage,
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen "fchown(%s) failed: %m", str_c(path));
6825360d446542046757b06064282301c4c6b27cTimo Sirainen }
6825360d446542046757b06064282301c4c6b27cTimo Sirainen }
6825360d446542046757b06064282301c4c6b27cTimo Sirainen }
6825360d446542046757b06064282301c4c6b27cTimo Sirainen
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen return fd;
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen}
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainenstruct mail_save_context *
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainenmaildir_save_alloc(struct mailbox_transaction_context *t)
61f5256ef248d35459b53534ae428bf6d016e1c5Timo Sirainen{
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
61f5256ef248d35459b53534ae428bf6d016e1c5Timo Sirainen
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen if (t->save_ctx == NULL)
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen t->save_ctx = maildir_save_transaction_init(t);
61f5256ef248d35459b53534ae428bf6d016e1c5Timo Sirainen return t->save_ctx;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen}
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen
faed8babca9914257f34fb2e603d74016d563b2dTimo Sirainenint maildir_save_begin(struct mail_save_context *_ctx, struct istream *input)
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen{
61f5256ef248d35459b53534ae428bf6d016e1c5Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen struct maildir_filename *mf;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen T_BEGIN {
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen /* create a new file in tmp/ directory */
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen const char *fname = _ctx->guid;
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen ctx->fd = maildir_create_tmp(ctx->mbox, ctx->tmpdir, &fname);
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen if (ctx->fd == -1)
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen ctx->failed = TRUE;
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen else {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (ctx->mbox->storage->storage.set->mail_save_crlf)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->input = i_stream_create_crlf(input);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen else
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->input = i_stream_create_lf(input);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mf = maildir_save_add(_ctx, fname);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (fname == _ctx->guid)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mf->preserve_filename = TRUE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen } T_END;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen if (!ctx->failed) {
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen _ctx->output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen o_stream_cork(_ctx->output);
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen ctx->last_save_finished = FALSE;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen return ctx->failed ? -1 : 0;
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen}
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainenint maildir_save_continue(struct mail_save_context *_ctx)
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen{
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen if (ctx->failed)
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen return -1;
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen do {
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen if (o_stream_send_istream(_ctx->output, ctx->input) < 0) {
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen mail_storage_set_critical(storage,
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen "o_stream_send_istream(%s/%s) "
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen "failed: %m",
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen ctx->tmpdir, ctx->file_last->basename);
893e5bbd5184ec5c21f47c67c8ea6efbea41f7d0Timo Sirainen }
893e5bbd5184ec5c21f47c67c8ea6efbea41f7d0Timo Sirainen ctx->failed = TRUE;
893e5bbd5184ec5c21f47c67c8ea6efbea41f7d0Timo Sirainen return -1;
893e5bbd5184ec5c21f47c67c8ea6efbea41f7d0Timo Sirainen }
893e5bbd5184ec5c21f47c67c8ea6efbea41f7d0Timo Sirainen if (ctx->cur_dest_mail != NULL)
5278c93bd7105c32ac7ec37f36015d5950f6cbcaTimo Sirainen index_mail_cache_parse_continue(ctx->cur_dest_mail);
5278c93bd7105c32ac7ec37f36015d5950f6cbcaTimo Sirainen
44c5e644cb413a6559bf2d4179cbe48f9a82f366Timo Sirainen /* both tee input readers may consume data from our primary
5278c93bd7105c32ac7ec37f36015d5950f6cbcaTimo Sirainen input stream. we'll have to make sure we don't return with
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen one of the streams still having data in them. */
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen } while (i_stream_read(ctx->input) > 0);
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen return 0;
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen}
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainenstatic int maildir_save_finish_received_date(struct maildir_save_context *ctx,
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen const char *path)
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen{
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen struct mail_storage *storage = &ctx->mbox->storage->storage;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen struct utimbuf buf;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen struct stat st;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen if (ctx->ctx.received_date != (time_t)-1) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen /* set the received_date by modifying mtime */
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen buf.actime = ioloop_time;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen buf.modtime = ctx->ctx.received_date;
bb2b91b4c5363348b737237893d414639510a561Timo Sirainen
bb2b91b4c5363348b737237893d414639510a561Timo Sirainen if (utime(path, &buf) < 0) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen mail_storage_set_critical(storage,
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen "utime(%s) failed: %m", path);
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen return -1;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen }
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen } else if (ctx->fd != -1) {
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen if (fstat(ctx->fd, &st) == 0)
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen ctx->ctx.received_date = st.st_mtime;
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen else {
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen mail_storage_set_critical(storage,
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen "fstat(%s) failed: %m", path);
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen return -1;
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen }
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen } else {
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen /* hardlinked */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen 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->basename, 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->fsync_disable && !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->fsync_disable)
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;
t_array_init(&saved_sorted_uids,
array_count(&ctx->ctx.transaction->changes->saved_uids));
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);
i_assert(count > 0);
for (i = 0; i < count; i++) {
for (uid = uids[i].seq1; uid <= uids[i].seq2; uid++)
index_mailbox_set_recent_uid(&mbox->ibox, uid);
}
return uids[count-1].seq2;
}
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_index_begin(mbox, NULL, &ctx->sync_ctx) < 0)
return -1;
ctx->keywords_sync_ctx =
maildir_sync_get_keywords_sync_ctx(ctx->sync_ctx);
if (maildir_sync_header_refresh(mbox) < 0)
return -1;
if (maildir_uidlist_refresh_fast_init(mbox->uidlist) < 0)
return -1;
/* 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->ibox.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;
for (seq = ctx->seq; seq >= ctx->first_seq; seq--)
mail_index_expunge(ctx->trans, seq);
mail_cache_transaction_reset(t->cache_trans);
}
static int
maildir_save_move_files_to_newcur(struct maildir_save_context *ctx,
struct maildir_filename **last_mf_r)
{
struct maildir_filename *mf;
bool newdir, new_changed, cur_changed;
int ret = 0;
*last_mf_r = NULL;
ret = 0;
new_changed = cur_changed = FALSE;
for (mf = ctx->files; mf != NULL; mf = mf->next) {
T_BEGIN {
const char *dest;
newdir = maildir_get_updated_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;
*last_mf_r = mf;
}
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;
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_updated_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);
i_assert(ret > 0);
} 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;
struct maildir_filename *last_mf;
enum maildir_uidlist_sync_flags sync_flags;
int ret;
i_assert(_ctx->output == NULL);
i_assert(ctx->last_save_finished);
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 {
/* 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;
}
ret = maildir_save_move_files_to_newcur(ctx, &last_mf);
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->ibox.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 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);
}