Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib-storage/index/maildir/maildir-copy.c @ 9507:9af9343084d4 HEAD
maildir: Always add S=size to filenames.
It's necessary for using zlib plugin and doesn't hurt much anyway.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 15 Dec 2009 14:05:06 -0500 |
parents | 8472e487da49 |
children | 00cd9aacd03c |
line wrap: on
line source
/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ #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 "maildir-filename.h" #include "maildir-keywords.h" #include "maildir-sync.h" #include "index-mail.h" #include "mail-copy.h" #include <stdlib.h> #include <unistd.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; 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.flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) ret = nfs_safe_link(path, str_c(ctx->dest_path), FALSE); else ret = link(path, str_c(ctx->dest_path)); if (ret < 0) { if (errno == ENOENT) return 0; if (ENOSPACE(errno)) { mail_storage_set_error(&mbox->storage->storage, MAIL_ERROR_NOSPACE, MAIL_ERRSTR_NO_SPACE); return -1; } /* we could handle the EEXIST condition by changing the filename, but it practically never happens so just fallback to standard copying for the rare cases when it does. */ if (errno == EACCES || ECANTLINK(errno) || errno == EEXIST) return 1; mail_storage_set_critical(&mbox->storage->storage, "link(%s, %s) failed: %m", path, str_c(ctx->dest_path)); return -1; } ctx->success = TRUE; return 1; } static const char * maildir_copy_get_preserved_fname(struct maildir_mailbox *src_mbox, struct maildir_mailbox *dest_mbox, uint32_t uid) { enum maildir_uidlist_rec_flag flags; const char *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. */ if (maildir_uidlist_lookup(src_mbox->uidlist, uid, &flags, &fname) <= 0) return NULL; if (maildir_uidlist_refresh(dest_mbox->uidlist) <= 0) return NULL; if (maildir_uidlist_get_full_filename(dest_mbox->uidlist, fname) != NULL) { /* already exists in destination */ return NULL; } /* fname may be freed by a later uidlist sync. make sure it gets strduped. */ return t_strcut(t_strdup(fname), ':'); } static int maildir_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 hardlink_ctx do_ctx; const char *path, *filename = NULL; i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0); if (strcmp(mail->box->storage->name, MAILDIR_STORAGE_NAME) == 0) src_mbox = (struct maildir_mailbox *)mail->box; else if (strcmp(mail->box->storage->name, "raw") == 0) { /* deliver uses raw format */ src_mbox = NULL; } else { /* Can't hard link files from the source storage */ return 0; } if (t->save_ctx == NULL) t->save_ctx = maildir_save_transaction_init(t); /* 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 && src_mbox != NULL) { filename = maildir_copy_get_preserved_fname(src_mbox, dest_mbox, mail->uid); } 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 (src_mbox != NULL) { /* maildir */ if (maildir_file_do(src_mbox, mail->uid, do_hardlink, &do_ctx) < 0) return -1; } else { /* raw / deliver */ 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) 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 */ 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 */ 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 mail_save_context *ctx, struct mail *mail) { struct maildir_transaction_context *t = (struct maildir_transaction_context *)ctx->transaction; struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->ictx.ibox; int ret; if (mbox->storage->copy_with_hardlinks && maildir_compatible_file_modes(&mbox->ibox.box, mail->box)) { T_BEGIN { ret = maildir_copy_hardlink(t, mail, ctx->flags, ctx->keywords, ctx->dest_mail); } T_END; if (ret != 0) { index_save_context_free(ctx); return ret > 0 ? 0 : -1; } /* non-fatal hardlinking failure, try the slow way */ } return mail_storage_copy(ctx, mail); }