#include "lib.h"
#include "array.h"
#include "ioloop.h"
-#include "str.h"
#include "nfs-workarounds.h"
#include "maildir-storage.h"
#include "maildir-uidlist.h"
#include <sys/stat.h>
struct hardlink_ctx {
- string_t *dest_path;
- const char *dest_fname;
- unsigned int base_end_pos;
-
- unsigned int size_set:1;
- unsigned int vsize_set:1;
+ const char *dest_path;
unsigned int success:1;
- unsigned int preserve_filename:1;
};
-static int do_save_mail_size(struct maildir_mailbox *mbox, const char *path,
- struct hardlink_ctx *ctx)
-{
- const char *fname, *str;
- struct stat st;
- uoff_t size;
-
- fname = strrchr(path, '/');
- fname = fname != NULL ? fname + 1 : path;
-
- if (!maildir_filename_get_size(fname, MAILDIR_EXTRA_FILE_SIZE,
- &size)) {
- if (stat(path, &st) < 0) {
- if (errno == ENOENT)
- return 0;
- mail_storage_set_critical(&mbox->storage->storage,
- "stat(%s) failed: %m", path);
- return -1;
- }
- size = st.st_size;
- }
-
- str = t_strdup_printf(",%c=%"PRIuUOFF_T, MAILDIR_EXTRA_FILE_SIZE, size);
- str_insert(ctx->dest_path, ctx->base_end_pos, str);
-
- ctx->dest_fname = strrchr(str_c(ctx->dest_path), '/') + 1;
- ctx->size_set = TRUE;
- return 1;
-}
-
-static void do_save_mail_vsize(const char *path, struct hardlink_ctx *ctx)
-{
- const char *fname, *str;
- uoff_t size;
-
- fname = strrchr(path, '/');
- fname = fname != NULL ? fname + 1 : path;
-
- if (!maildir_filename_get_size(fname, MAILDIR_EXTRA_VIRTUAL_SIZE,
- &size))
- return;
-
- str = t_strdup_printf(",%c=%"PRIuUOFF_T,
- MAILDIR_EXTRA_VIRTUAL_SIZE, size);
- str_insert(ctx->dest_path, ctx->base_end_pos, str);
-
- ctx->dest_fname = strrchr(str_c(ctx->dest_path), '/') + 1;
- ctx->vsize_set = TRUE;
-}
-
static int do_hardlink(struct maildir_mailbox *mbox, const char *path,
struct hardlink_ctx *ctx)
{
int ret;
- if (!ctx->preserve_filename) {
- if (!ctx->size_set) {
- if ((ret = do_save_mail_size(mbox, path, ctx)) <= 0)
- return ret;
- }
- /* set virtual size if it's in the original file name */
- if (!ctx->vsize_set)
- do_save_mail_vsize(path, ctx);
- }
-
if (mbox->storage->storage.set->mail_nfs_storage)
- ret = nfs_safe_link(path, str_c(ctx->dest_path), FALSE);
+ ret = nfs_safe_link(path, ctx->dest_path, FALSE);
else
- ret = link(path, str_c(ctx->dest_path));
+ ret = link(path, ctx->dest_path);
if (ret < 0) {
if (errno == ENOENT)
return 0;
mail_storage_set_critical(&mbox->storage->storage,
"link(%s, %s) failed: %m",
- path, str_c(ctx->dest_path));
+ path, ctx->dest_path);
return -1;
}
}
static int
-maildir_copy_hardlink(struct mail_save_context *ctx,
- struct hardlink_ctx *do_ctx, struct mail *mail)
+maildir_copy_hardlink(struct mail_save_context *ctx, struct mail *mail)
{
struct maildir_mailbox *dest_mbox =
(struct maildir_mailbox *)ctx->transaction->box;
struct maildir_mailbox *src_mbox;
- const char *path, *guid;
+ struct maildir_filename *mf;
+ struct hardlink_ctx do_ctx;
+ const char *path, *guid, *dest_fname;
+ uoff_t vsize, size;
+ enum mail_lookup_abort old_abort;
if (strcmp(mail->box->storage->name, MAILDIR_STORAGE_NAME) == 0)
src_mbox = (struct maildir_mailbox *)mail->box;
return 0;
}
- do_ctx->dest_path = str_new(default_pool, 512);
-
- if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0)
- guid = "";
- if (*guid == '\0') {
- /* the generated filename is _always_ unique, so we don't
- bother trying to check if it already exists */
- do_ctx->dest_fname = maildir_filename_generate();
- } else {
- do_ctx->dest_fname = guid;
- do_ctx->preserve_filename = TRUE;
- }
-
- /* hard link to tmp/ with basename and later when we
+ /* hard link to tmp/ with a newly generated filename and later when we
have uidlist locked, move it to new/cur. */
- str_printfa(do_ctx->dest_path, "%s/tmp/%s",
- dest_mbox->box.path, do_ctx->dest_fname);
- do_ctx->base_end_pos = str_len(do_ctx->dest_path);
+ dest_fname = maildir_filename_generate();
+ memset(&do_ctx, 0, sizeof(do_ctx));
+ do_ctx.dest_path =
+ t_strdup_printf("%s/tmp/%s", dest_mbox->box.path, dest_fname);
if (src_mbox != NULL) {
/* maildir */
if (maildir_file_do(src_mbox, mail->uid,
- do_hardlink, do_ctx) < 0)
+ do_hardlink, &do_ctx) < 0)
return -1;
} else {
/* raw / lda */
if (mail_get_special(mail, MAIL_FETCH_UIDL_FILE_NAME,
&path) < 0 || *path == '\0')
return 0;
- if (do_hardlink(dest_mbox, path, do_ctx) < 0)
+ if (do_hardlink(dest_mbox, path, &do_ctx) < 0)
return -1;
}
- if (!do_ctx->success) {
+ if (!do_ctx.success) {
/* couldn't copy with hardlinking, fallback to copying */
return 0;
}
/* hardlinked to tmp/, treat as normal copied mail */
- maildir_save_add(ctx, do_ctx->dest_fname, do_ctx->preserve_filename);
+ mf = maildir_save_add(ctx, dest_fname);
+ if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) == 0) {
+ if (*guid != '\0')
+ maildir_save_set_dest_basename(ctx, mf, guid);
+ }
+
+ /* remember size/vsize if possible */
+ old_abort = mail->lookup_abort;
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
+ if (mail_get_physical_size(mail, &size) < 0)
+ size = (uoff_t)-1;
+ if (mail_get_virtual_size(mail, &vsize) < 0)
+ vsize = (uoff_t)-1;
+ maildir_save_set_sizes(mf, size, vsize);
+ mail->lookup_abort = old_abort;
return 1;
}
{
struct mailbox_transaction_context *_t = ctx->transaction;
struct maildir_mailbox *mbox = (struct maildir_mailbox *)_t->box;
- struct hardlink_ctx do_ctx;
int ret;
i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
if (mbox->storage->set->maildir_copy_with_hardlinks &&
maildir_compatible_file_modes(&mbox->box, mail->box)) {
T_BEGIN {
- memset(&do_ctx, 0, sizeof(do_ctx));
- ret = maildir_copy_hardlink(ctx, &do_ctx, mail);
- if (do_ctx.dest_path != NULL)
- str_free(&do_ctx.dest_path);
+ ret = maildir_copy_hardlink(ctx, mail);
} T_END;
if (ret != 0) {
return &ctx->ctx;
}
-void maildir_save_add(struct mail_save_context *_ctx, const char *base_fname,
- bool preserve_filename)
+struct maildir_filename *
+maildir_save_add(struct mail_save_context *_ctx, const char *tmp_fname)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
struct maildir_filename *mf;
keyword_count = _ctx->keywords == NULL ? 0 : _ctx->keywords->count;
mf = p_malloc(ctx->pool, sizeof(*mf) +
sizeof(unsigned int) * keyword_count);
- mf->tmp_name = mf->dest_basename = p_strdup(ctx->pool, base_fname);
+ mf->tmp_name = mf->dest_basename = p_strdup(ctx->pool, tmp_fname);
mf->flags = _ctx->flags;
mf->size = (uoff_t)-1;
mf->vsize = (uoff_t)-1;
- mf->preserve_filename = preserve_filename;
- if (preserve_filename)
- ctx->have_preserved_filenames = TRUE;
ctx->file_last = mf;
i_assert(*ctx->files_tail == NULL);
ctx->input = input;
ctx->cur_dest_mail = _ctx->dest_mail;
}
+ return mf;
+}
+
+void maildir_save_set_dest_basename(struct mail_save_context *_ctx,
+ struct maildir_filename *mf,
+ const char *basename)
+{
+ struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
+
+ mf->preserve_filename = TRUE;
+ mf->dest_basename = p_strdup(ctx->pool, basename);
+ ctx->have_preserved_filenames = TRUE;
+}
+
+void maildir_save_set_sizes(struct maildir_filename *mf,
+ uoff_t size, uoff_t vsize)
+{
+ mf->size = size;
+ mf->vsize = vsize;
}
static bool
}
static int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
- const char **fname)
+ const char **fname_r)
{
struct mailbox *box = &mbox->box;
struct stat st;
unsigned int prefix_len;
- const char *tmp_fname = *fname;
+ const char *tmp_fname;
string_t *path;
int fd;
prefix_len = str_len(path);
for (;;) {
- if (tmp_fname == NULL)
- tmp_fname = maildir_filename_generate();
+ tmp_fname = maildir_filename_generate();
str_truncate(path, prefix_len);
str_append(path, tmp_fname);
tmp_fname = NULL;
}
- *fname = tmp_fname;
+ *fname_r = tmp_fname;
if (fd == -1) {
if (ENOSPACE(errno)) {
mail_storage_set_error(box->storage,
int maildir_save_begin(struct mail_save_context *_ctx, struct istream *input)
{
struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
+ struct maildir_filename *mf;
/* new mail, new failure state */
ctx->failed = FALSE;
T_BEGIN {
/* create a new file in tmp/ directory */
- const char *fname = _ctx->guid;
+ const char *fname;
ctx->fd = maildir_create_tmp(ctx->mbox, ctx->tmpdir, &fname);
if (ctx->fd == -1)
ctx->input = i_stream_create_crlf(input);
else
ctx->input = i_stream_create_lf(input);
- maildir_save_add(_ctx, fname, fname == _ctx->guid);
+ mf = maildir_save_add(_ctx, fname);
+ if (_ctx->guid != NULL) {
+ maildir_save_set_dest_basename(_ctx, mf,
+ _ctx->guid);
+ }
}
} T_END;
static void
maildir_filename_check_conflicts(struct maildir_save_context *ctx,
- struct maildir_filename *mf)
+ struct maildir_filename *mf,
+ struct maildir_filename *prev_mf)
{
uoff_t size;
ctx->locked_uidlist_refresh = TRUE;
}
- if (maildir_uidlist_get_full_filename(ctx->mbox->uidlist,
+ if ((prev_mf != NULL &&
+ strcmp(mf->dest_basename, prev_mf->dest_basename) == 0) ||
+ maildir_uidlist_get_full_filename(ctx->mbox->uidlist,
mf->dest_basename) != NULL) {
/* file already exists. give it another name.
but preserve the size/vsize in the filename if possible */
}
static int
-maildir_save_move_files_to_newcur(struct maildir_save_context *ctx,
- struct maildir_filename **last_mf_r)
+maildir_filename_dest_basename_cmp(struct maildir_filename *const *f1,
+ struct maildir_filename *const *f2)
{
- struct maildir_filename *mf;
+ return strcmp((*f1)->dest_basename, (*f2)->dest_basename);
+}
+
+static int
+maildir_save_move_files_to_newcur(struct maildir_save_context *ctx)
+{
+ ARRAY_DEFINE(files, struct maildir_filename *);
+ struct maildir_filename *mf, *const *mfp, *prev_mf;
bool newdir, new_changed, cur_changed;
int ret;
- *last_mf_r = NULL;
-
- new_changed = cur_changed = FALSE;
- for (mf = ctx->files; mf != NULL; mf = mf->next) {
+ /* 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_array_init(&files, ctx->files_count);
+ for (mf = ctx->files; mf != NULL; mf = mf->next)
+ array_append(&files, &mf, 1);
+ array_sort(&files, maildir_filename_dest_basename_cmp);
+
+ new_changed = cur_changed = FALSE; prev_mf = FALSE;
+ array_foreach(&files, mfp) {
+ mf = *mfp;
T_BEGIN {
const char *dest;
- if (mf->preserve_filename)
- maildir_filename_check_conflicts(ctx, mf);
+ if (mf->preserve_filename) {
+ maildir_filename_check_conflicts(ctx, mf,
+ prev_mf);
+ }
newdir = maildir_get_dest_filename(ctx, mf, &dest);
if (newdir)
} T_END;
if (ret < 0)
return -1;
- *last_mf_r = mf;
+ prev_mf = mf;
}
return maildir_transaction_fsync_dirs(ctx, new_changed, cur_changed);
{
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;
return -1;
}
- ret = maildir_save_move_files_to_newcur(ctx, &last_mf);
+ T_BEGIN {
+ ret = maildir_save_move_files_to_newcur(ctx);
+ } T_END;
if (ctx->locked) {
if (ret == 0) {
/* update dovecot-uidlist file. */