maildir-save.c revision 6fe91298731abf0b70dfd796ecc30d3be81fa5d1
/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "buffer.h"
#include "istream.h"
#include "istream-crlf.h"
#include "ostream.h"
#include "fdatasync-path.h"
#include "eacces-error.h"
#include "str.h"
#include "index-mail.h"
#include "maildir-storage.h"
#include "maildir-uidlist.h"
#include "maildir-keywords.h"
#include "maildir-filename.h"
#include "maildir-filename-flags.h"
#include "maildir-sync.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <utime.h>
#define MAILDIR_FILENAME_FLAG_MOVED 0x10000000
struct maildir_filename {
struct maildir_filename *next;
const char *tmp_name, *dest_basename;
enum mail_flags flags;
unsigned int preserve_filename:1;
unsigned int keywords_count;
/* unsigned int keywords[]; */
};
struct maildir_save_context {
struct mail_save_context ctx;
struct maildir_mailbox *mbox;
struct mail_index_transaction *trans;
struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
struct maildir_index_sync_context *sync_ctx;
unsigned int files_count;
int fd;
unsigned int have_keywords:1;
unsigned int have_preserved_filenames:1;
unsigned int locked:1;
unsigned int failed:1;
unsigned int last_save_finished:1;
unsigned int locked_uidlist_refresh:1;
};
bool newdir)
{
/* 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. */
/* maildir spec says we should use link() + unlink() here. however
since our filename is guaranteed to be unique, rename() works just
as well, except faster. even if the filename wasn't unique, the
problem could still happen if the file was already moved from
new/ to cur/, so link() doesn't really provide any safety anyway.
Besides the small temporary performance benefits, this rename() is
almost required with OSX's HFS+ filesystem, since it implements
hard links in a pretty ugly way, which makes the performance crawl
when a lot of hard links are used. */
return 0;
return -1;
} else {
return -1;
}
}
static struct mail_save_context *
{
struct maildir_save_context *ctx;
sizeof(unsigned int));
}
struct maildir_filename *
{
struct maildir_filename *mf;
unsigned int keyword_count;
/* allow caller to specify recent flag only when uid is specified
(we're replicating, converting, etc.). */
/* 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/. */
/* @UNSAFE */
sizeof(unsigned int) * keyword_count);
ctx->files_count++;
/* @UNSAFE */
sizeof(unsigned int) * keyword_count);
}
/* insert into index */
}
if (_ctx->min_modseq != 0) {
_ctx->min_modseq);
}
}
}
/* copying with hardlinking. */
} else {
}
return mf;
}
struct maildir_filename *mf,
const char *basename)
{
}
{
}
static bool
struct maildir_filename *mf,
const char **fname_r)
{
}
}
if (mf->keywords_count == 0) {
return TRUE;
}
return FALSE;
}
mf->keywords_count * sizeof(unsigned int));
&ctx->keywords_array);
return FALSE;
}
struct maildir_filename *mf)
{
/* file is still in tmp/ */
}
/* already moved to new/ or cur/ */
}
const char *maildir_save_file_get_path(struct mailbox_transaction_context *t,
{
struct maildir_save_context *save_ctx =
(struct maildir_save_context *)t->save_ctx;
struct maildir_filename *mf;
while (seq > 0) {
seq--;
}
}
const char **fname_r)
{
unsigned int prefix_len;
const char *tmp_fname;
int fd;
do {
/* the generated filename is unique. the only reason why it
might return an existing filename is if the time moved
backwards. so we'll use O_EXCL anyway, although it's mostly
useless. */
if (fd == -1) {
} else {
}
eperm_error_get_chgrp("fchown",
} else {
}
}
}
return fd;
}
struct mail_save_context *
maildir_save_alloc(struct mailbox_transaction_context *t)
{
t->save_ctx = maildir_save_transaction_init(t);
return t->save_ctx;
}
{
struct maildir_filename *mf;
/* new mail, new failure state */
T_BEGIN {
/* create a new file in tmp/ directory */
const char *fname;
else {
else
}
}
} T_END;
}
}
{
return -1;
do {
if (!mail_storage_set_error_from_errno(storage)) {
"o_stream_send_istream(%s/%s) "
"failed: %m",
}
return -1;
}
/* both tee input readers may consume data from our primary
input stream. we'll have to make sure we don't return with
one of the streams still having data in them. */
return 0;
}
const char *path)
{
/* set the received_date by modifying mtime */
"utime(%s) failed: %m", path);
return -1;
}
else {
"fstat(%s) failed: %m", path);
return -1;
}
} else {
/* hardlinked */
else {
"stat(%s) failed: %m", path);
return -1;
}
}
return 0;
}
{
struct index_transaction_context *t =
struct maildir_filename **fm;
/* currently we can't just drop pending cache updates for this one
specific record, so we'll reset the whole cache transaction. */
ctx->files_count--;
}
{
const char *path;
int output_errno;
/* tmp file creation failed */
return -1;
}
if (!mail_storage_set_error_from_errno(storage)) {
"o_stream_flush(%s) failed: %m", path);
}
}
/* we can't change ctime, but we can add the date to cache */
}
}
/* remember the size in case we want to add it to filename */
if (!mail_storage_set_error_from_errno(storage)) {
"fsync(%s) failed: %m", path);
}
}
}
if (!mail_storage_set_error_from_errno(storage)) {
"close(%s) failed: %m", path);
}
}
/* delete the tmp file */
"unlink(%s) failed: %m", path);
}
} else if (errno != 0) {
"write(%s) failed: %m", path);
}
return -1;
}
return 0;
}
{
int ret;
T_BEGIN {
} T_END;
return ret;
}
{
(void)maildir_save_finish(_ctx);
}
static void
{
struct maildir_filename *mf;
} T_END;
}
bool new_changed, bool cur_changed)
{
return 0;
if (new_changed) {
return -1;
}
}
if (cur_changed) {
return -1;
}
}
return 0;
}
{
return -1;
return 1;
else
return 0;
}
static uint32_t
{
unsigned int i, count;
if (count == 0)
return 0;
for (i = 0; i < count; i++) {
}
}
static int
{
int ret;
/* we'll need to keep the lock past the sync deinit */
if (maildir_sync_header_refresh(mbox) < 0)
return -1;
return -1;
if (ret == 0) {
/* uidlist doesn't exist. make sure all existing message
are added to uidlist first. */
(void)maildir_storage_sync_force(mbox, 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. */
return -1;
/* if messages were added to index, assign them UIDs */
/* these mails are all recent in our session */
T_BEGIN {
} T_END;
else if (ctx->last_nonrecent_uid != 0)
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. */
offsetof(struct mail_index_header,
sizeof(first_recent_uid), FALSE);
}
return 0;
}
static void
{
struct index_transaction_context *t =
return;
}
struct maildir_filename *prev_mf)
{
/* already used this */
return TRUE;
}
/* previous filename also had a conflict */
return TRUE;
}
return FALSE;
}
static void
struct maildir_filename *mf,
struct maildir_filename *prev_mf)
{
if (!ctx->locked_uidlist_refresh) {
}
/* file already exists. give it another name.
&size))
}
}
static int
struct maildir_filename *const *f2)
{
}
static int
{
int ret;
/* put files into an array sorted by the destination filename.
this way we can easily check if there are duplicate destination
filenames within this transaction. */
T_BEGIN {
const char *dest;
if (mf->preserve_filename) {
prev_mf);
}
if (newdir)
new_changed = TRUE;
else
cur_changed = TRUE;
} T_END;
if (ret < 0)
return -1;
}
array_count(&files));
}
}
{
struct maildir_filename *mf;
struct seq_range_iter iter;
struct maildir_uidlist_rec *rec;
unsigned int n = 0;
int ret;
const char *dest;
if (newdir)
}
}
} T_END;
}
{
int ret;
if (ctx->files_count == 0)
return 0;
/* we want to assign UIDs, we must lock uidlist */
} else if (ctx->have_keywords) {
/* keywords file updating relies on uidlist lock. */
} else if (ctx->have_preserved_filenames) {
/* we're trying to use some potentially existing filenames.
we must lock to avoid race conditions where two sessions
try to save the same filename. */
} else {
/* no requirement to lock uidlist. if we happen to get a lock,
assign uids. */
}
&ctx->uidlist_sync_ctx);
if (ret > 0) {
if (maildir_save_sync_index(ctx) < 0) {
return -1;
}
} else if (ret == 0 &&
(sync_flags & MAILDIR_UIDLIST_SYNC_TRYLOCK) != 0) {
/* since we couldn't lock uidlist, we'll have to drop the
appends to index. */
} else {
return -1;
}
T_BEGIN {
} T_END;
if (ret == 0) {
/* update dovecot-uidlist file. */
}
ret == 0) < 0)
ret = -1;
}
/* 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. */
}
/* It doesn't matter if index syncing fails */
if (ret < 0)
else
}
if (ret < 0) {
/* 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. */
/* returning failure finishes the save_context */
return -1;
}
return 0;
}
{
}
{
if (!ctx->last_save_finished)
/* delete files in tmp/ */
}