maildir-copy.c revision 746a7ec64a09649ed3c96c88b97cdc370a7bbe2f
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe/* Copyright (c) 2002-2007 Dovecot authors, see the included COPYING file */
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "lib.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "array.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "ioloop.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "str.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "maildir-storage.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "maildir-uidlist.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "maildir-filename.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "maildir-keywords.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "maildir-sync.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "index-mail.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include "mail-copy.h"
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include <stdlib.h>
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include <unistd.h>
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe#include <sys/stat.h>
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowestruct hardlink_ctx {
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe string_t *dest_path;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe const char *dest_fname;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe unsigned int base_end_pos;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe unsigned int size_set:1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe unsigned int success:1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe unsigned int preserve_filename:1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe};
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowestatic int do_save_mail_size(struct maildir_mailbox *mbox, const char *path,
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe struct hardlink_ctx *ctx)
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe{
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe const char *fname, *str;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe struct stat st;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe uoff_t size;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
8172c01648719ec02a7e1cbb172baab590a4fb31wrowe fname = strrchr(path, '/');
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe fname = fname != NULL ? fname + 1 : path;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if (!maildir_filename_get_size(fname, MAILDIR_EXTRA_FILE_SIZE,
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe &size)) {
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if (stat(path, &st) < 0) {
5569cc0f6cf6a35c9f4bff089a4668da6ade3f71gsmith if (errno == ENOENT)
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return 0;
8172c01648719ec02a7e1cbb172baab590a4fb31wrowe mail_storage_set_critical(&mbox->storage->storage,
f84cab2da5f8958575b1ce99ca2bf4fda34cecb6mturk "stat(%s) failed: %m", path);
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return -1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe }
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe size = st.st_size;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe }
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe str = t_strdup_printf(",S=%"PRIuUOFF_T, size);
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe str_insert(ctx->dest_path, ctx->base_end_pos, str);
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe ctx->dest_fname = strrchr(str_c(ctx->dest_path), '/') + 1;
8172c01648719ec02a7e1cbb172baab590a4fb31wrowe ctx->size_set = TRUE;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return 1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe}
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowestatic int do_hardlink(struct maildir_mailbox *mbox, const char *path,
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe struct hardlink_ctx *ctx)
5569cc0f6cf6a35c9f4bff089a4668da6ade3f71gsmith{
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe int ret;
8172c01648719ec02a7e1cbb172baab590a4fb31wrowe
f84cab2da5f8958575b1ce99ca2bf4fda34cecb6mturk if (!ctx->preserve_filename && mbox->storage->save_size_in_filename &&
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe !ctx->size_set) {
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if ((ret = do_save_mail_size(mbox, path, ctx)) <= 0)
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return ret;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe }
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if (link(path, str_c(ctx->dest_path)) < 0) {
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if (errno == ENOENT)
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return 0;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if (ENOSPACE(errno)) {
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe mail_storage_set_error(&mbox->storage->storage,
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe MAIL_ERROR_NOSPACE, MAIL_ERRSTR_NO_SPACE);
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return -1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe }
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe /* we could handle the EEXIST condition by changing the
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe filename, but it practically never happens so just fallback
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe to standard copying for the rare cases when it does. */
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe if (errno == EACCES || ECANTLINK(errno) || errno == EEXIST)
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return 1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe mail_storage_set_critical(&mbox->storage->storage,
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe "link(%s, %s) failed: %m",
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe path, str_c(ctx->dest_path));
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return -1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe }
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe ctx->success = TRUE;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe return 1;
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe}
d9d416febbde142f8afd14d5472e5ab3253c640cwrowe
d9d416febbde142f8afd14d5472e5ab3253c640cwrowestatic int
d9d416febbde142f8afd14d5472e5ab3253c640cwrowemaildir_copy_hardlink(struct maildir_transaction_context *t, struct mail *mail,
enum mail_flags flags, struct mail_keywords *keywords,
struct mail *dest_mail)
{
struct maildir_mailbox *dest_mbox =
(struct maildir_mailbox *)t->ictx.ibox;
struct maildir_mailbox *src_mbox =
(struct maildir_mailbox *)mail->box;
struct maildir_save_context *ctx;
struct hardlink_ctx do_ctx;
const char *filename = NULL;
uint32_t seq;
i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
if (t->save_ctx == NULL)
t->save_ctx = maildir_save_transaction_init(t);
ctx = t->save_ctx;
/* don't allow caller to specify recent flag */
flags &= ~MAIL_RECENT;
if (dest_mbox->ibox.keep_recent)
flags |= MAIL_RECENT;
memset(&do_ctx, 0, sizeof(do_ctx));
do_ctx.dest_path = str_new(default_pool, 512);
if (dest_mbox->storage->copy_preserve_filename) {
enum maildir_uidlist_rec_flag src_flags;
const char *src_fname;
/* see if the filename exists in destination maildir's
uidlist. if it doesn't, we can use it. otherwise generate
a new filename. FIXME: There's a race condition here if
another process is just doing the same copy. */
src_fname = maildir_uidlist_lookup(src_mbox->uidlist,
mail->uid, &src_flags);
if (src_fname != NULL &&
maildir_uidlist_refresh(dest_mbox->uidlist, FALSE) >= 0 &&
maildir_uidlist_get_full_filename(dest_mbox->uidlist,
src_fname) == NULL)
filename = t_strcut(src_fname, ':');
}
if (filename == NULL) {
/* 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 = filename;
do_ctx.preserve_filename = TRUE;
}
/* FIXME: We could hardlink the files directly to destination, but
that would require checking if someone else had already assigned
UIDs for them after we have the uidlist locked. Index would also
need to be properly not-updated somehow.. */
#if 0
if (keywords == NULL || keywords->count == 0) {
/* no keywords, hardlink directly to destination */
if (flags == MAIL_RECENT) {
str_printfa(do_ctx.dest_path, "%s/new/%s",
dest_mbox->path, do_ctx.dest_fname);
do_ctx.base_end_pos = str_len(do_ctx.dest_path);
} else {
str_printfa(do_ctx.dest_path, "%s/cur/",
dest_mbox->path);
do_ctx.base_end_pos = str_len(do_ctx.dest_path) +
strlen(do_ctx.dest_fname);
str_append(do_ctx.dest_path,
maildir_filename_set_flags(NULL,
do_ctx.dest_fname,
flags, NULL));
}
} else
#endif
{
/* keywords, hardlink to tmp/ with basename and later when we
have uidlist locked, move it to new/cur. */
str_printfa(do_ctx.dest_path, "%s/tmp/%s",
dest_mbox->path, do_ctx.dest_fname);
do_ctx.base_end_pos = str_len(do_ctx.dest_path);
}
if (maildir_file_do(src_mbox, mail->uid, do_hardlink, &do_ctx) < 0)
return -1;
if (!do_ctx.success) {
/* couldn't copy with hardlinking, fallback to copying */
return 0;
}
#if 0
if (keywords == NULL || keywords->count == 0) {
/* hardlinked to destination, set hardlinked-flag */
seq = maildir_save_add(t, do_ctx.dest_fname,
flags | MAILDIR_SAVE_FLAG_HARDLINK, NULL,
dest_mail);
} else
#endif
{
/* hardlinked to tmp/, treat as normal copied mail */
seq = maildir_save_add(t, do_ctx.dest_fname, flags, keywords,
dest_mail);
}
return 1;
}
static bool
maildir_compatible_file_modes(struct mailbox *box1, struct mailbox *box2)
{
return box1->file_create_mode == box2->file_create_mode &&
box1->file_create_gid == box2->file_create_gid;
}
int maildir_copy(struct mailbox_transaction_context *_t, struct mail *mail,
enum mail_flags flags, struct mail_keywords *keywords,
struct mail *dest_mail)
{
struct maildir_transaction_context *t =
(struct maildir_transaction_context *)_t;
struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->ictx.ibox;
int ret;
if (mbox->storage->copy_with_hardlinks &&
mail->box->storage == mbox->ibox.box.storage &&
maildir_compatible_file_modes(&mbox->ibox.box, mail->box)) {
t_push();
ret = maildir_copy_hardlink(t, mail, flags,
keywords, dest_mail);
t_pop();
if (ret > 0)
return 0;
if (ret < 0)
return -1;
/* non-fatal hardlinking failure, try the slow way */
}
return mail_storage_copy(_t, mail, flags, keywords, dest_mail);
}