view src/lib-storage/index/maildir/maildir-copy.c @ 10657:e7f066508299 HEAD

lib-storage: Moved struct mail_index_* from index_mailbox to mailbox. We're relying more and more of all mailboxes being used via lib-index, and this change makes accessing the indexes easier.
author Timo Sirainen <tss@iki.fi>
date Sun, 07 Feb 2010 15:44:33 +0200
parents fc0ac73f0b36
children 56b1d4dd9c7d
line wrap: on
line source

/* Copyright (c) 2002-2010 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.set->mail_nfs_storage)
		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 mail_save_context *ctx, struct mail *mail)
{
	struct maildir_mailbox *dest_mbox =
		(struct maildir_mailbox *)ctx->transaction->box;
	struct maildir_mailbox *src_mbox;
	struct hardlink_ctx do_ctx;
	const char *path, *filename = NULL;

	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) {
		/* lda uses raw format */
		src_mbox = NULL;
	} else {
		/* Can't hard link files from the source storage */
		return 0;
	}

	if (mail_index_is_deleted(dest_mbox->ibox.box.index)) {
		mailbox_set_deleted(&dest_mbox->ibox.box);
		return -1;
	}

	memset(&do_ctx, 0, sizeof(do_ctx));
	do_ctx.dest_path = str_new(default_pool, 512);

	if (dest_mbox->storage->set->maildir_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;
	}

	/* hard link 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->ibox.box.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 / 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)
			return -1;
	}

	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);
	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 mailbox_transaction_context *_t = ctx->transaction;
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)_t->box;
	int ret;

	i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);

	if (mbox->storage->set->maildir_copy_with_hardlinks &&
	    maildir_compatible_file_modes(&mbox->ibox.box, mail->box)) {
		T_BEGIN {
			ret = maildir_copy_hardlink(ctx, 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);
}