]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
maildir: Cleaned up filename/guid preserving code on save/copy.
authorTimo Sirainen <tss@iki.fi>
Thu, 23 Sep 2010 16:26:20 +0000 (17:26 +0100)
committerTimo Sirainen <tss@iki.fi>
Thu, 23 Sep 2010 16:26:20 +0000 (17:26 +0100)
src/lib-storage/index/maildir/maildir-copy.c
src/lib-storage/index/maildir/maildir-save.c
src/lib-storage/index/maildir/maildir-storage.h

index 7971f4e262f838e3b45dc7adab1d4318cc1b298d..080f85893b537ccb3868b3bd1e7d96cd4a374a87 100644 (file)
@@ -3,7 +3,6 @@
 #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;
@@ -115,7 +48,7 @@ static int do_hardlink(struct maildir_mailbox *mbox, const char *path,
 
                mail_storage_set_critical(&mbox->storage->storage,
                                          "link(%s, %s) failed: %m",
-                                         path, str_c(ctx->dest_path));
+                                         path, ctx->dest_path);
                return -1;
        }
 
@@ -124,13 +57,16 @@ static int do_hardlink(struct maildir_mailbox *mbox, const char *path,
 }
 
 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;
@@ -142,45 +78,47 @@ maildir_copy_hardlink(struct mail_save_context *ctx,
                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;
 }
 
@@ -195,7 +133,6 @@ int maildir_copy(struct mail_save_context *ctx, struct mail *mail)
 {
        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);
@@ -203,10 +140,7 @@ int maildir_copy(struct mail_save_context *ctx, struct mail *mail)
        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) {
index a705d6e97dcff961c1ee73a21d31eabc5e7999f6..ee5978bbde818dfcdea980e3dbfb31e6b1e0f38e 100644 (file)
@@ -136,8 +136,8 @@ maildir_save_transaction_init(struct mailbox_transaction_context *t)
        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;
@@ -159,13 +159,10 @@ void maildir_save_add(struct mail_save_context *_ctx, const char *base_fname,
        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);
@@ -220,6 +217,25 @@ void maildir_save_add(struct mail_save_context *_ctx, const char *base_fname,
                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
@@ -297,12 +313,12 @@ const char *maildir_save_file_get_path(struct mailbox_transaction_context *t,
 }
 
 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;
 
@@ -312,8 +328,7 @@ static int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
        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);
 
@@ -341,7 +356,7 @@ static int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir,
                tmp_fname = NULL;
        }
 
-       *fname = tmp_fname;
+       *fname_r = tmp_fname;
        if (fd == -1) {
                if (ENOSPACE(errno)) {
                        mail_storage_set_error(box->storage,
@@ -381,13 +396,14 @@ maildir_save_alloc(struct mailbox_transaction_context *t)
 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)
@@ -397,7 +413,11 @@ int maildir_save_begin(struct mail_save_context *_ctx, struct istream *input)
                                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;
 
@@ -766,7 +786,8 @@ maildir_save_rollback_index_changes(struct maildir_save_context *ctx)
 
 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;
 
@@ -775,7 +796,9 @@ maildir_filename_check_conflicts(struct maildir_save_context *ctx,
                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 */
@@ -795,22 +818,38 @@ maildir_filename_check_conflicts(struct maildir_save_context *ctx,
 }
 
 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)
@@ -821,7 +860,7 @@ maildir_save_move_files_to_newcur(struct maildir_save_context *ctx,
                } T_END;
                if (ret < 0)
                        return -1;
-               *last_mf_r = mf;
+               prev_mf = mf;
        }
 
        return maildir_transaction_fsync_dirs(ctx, new_changed, cur_changed);
@@ -871,7 +910,6 @@ 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;
 
@@ -917,7 +955,9 @@ int maildir_transaction_save_commit_pre(struct mail_save_context *_ctx)
                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. */
index 34ecd9afb17b5b4ed19b41fe821eecfb16f6dd74..2f532d5f6fa78da7d2abeffcf7cb2760371ae8ac 100644 (file)
@@ -117,8 +117,13 @@ int maildir_save_continue(struct mail_save_context *ctx);
 int maildir_save_finish(struct mail_save_context *ctx);
 void maildir_save_cancel(struct mail_save_context *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);
+void maildir_save_set_dest_basename(struct mail_save_context *ctx,
+                                   struct maildir_filename *mf,
+                                   const char *basename);
+void maildir_save_set_sizes(struct maildir_filename *mf,
+                           uoff_t size, uoff_t vsize);
 const char *maildir_save_file_get_path(struct mailbox_transaction_context *t,
                                       uint32_t seq);