view src/lib-storage/index/maildir/maildir-copy.c @ 4848:967de900c73a HEAD

Mailbox list indexing and related changes. Currently works only with maildir and mmap_disable=no. This allows doing STATUS to synced mailboxes without opening their index files at all.
author Timo Sirainen <tss@iki.fi>
date Sun, 26 Nov 2006 00:17:39 +0200
parents 76e2177be0bf
children 5b4c9b20eba0
line wrap: on
line source

/* Copyright (C) 2002-2004 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "ioloop.h"
#include "str.h"
#include "maildir-storage.h"
#include "maildir-uidlist.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 success: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(STORAGE(mbox->storage),
						  "stat(%s) failed: %m", path);
			return -1;
		}
		size = st.st_size;
	}

	str = t_strdup_printf(",S=%"PRIuUOFF_T, 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 int do_hardlink(struct maildir_mailbox *mbox, const char *path,
		       void *context)
{
	struct hardlink_ctx *ctx = context;
	int ret;

	if (mbox->storage->save_size_in_filename && !ctx->size_set) {
		if ((ret = do_save_mail_size(mbox, path, ctx)) <= 0)
			return ret;
	}

	if (link(path, str_c(ctx->dest_path)) < 0) {
		if (errno == ENOENT)
			return 0;

		if (ENOSPACE(errno)) {
			mail_storage_set_error(STORAGE(mbox->storage),
					       "Not enough disk space");
			return -1;
		}
		if (errno == EACCES || ECANTLINK(errno))
			return 1;

		mail_storage_set_critical(STORAGE(mbox->storage),
					  "link(%s, %s) failed: %m",
					  path, str_c(ctx->dest_path));
		return -1;
	}

	ctx->success = TRUE;
	return 1;
}

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 maildir_mailbox *)mail->box;
	struct maildir_save_context *ctx;
	struct hardlink_ctx do_ctx;
	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);

	/* the generated filename is _always_ unique, so we don't bother
	   trying to check if it already exists */
	do_ctx.dest_fname = maildir_generate_tmp_filename(&ioloop_timeval);
	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 {
		/* 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 (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 {
		/* hardlinked to tmp/, treat as normal copied mail */
		seq = maildir_save_add(t, do_ctx.dest_fname, flags, keywords,
				       dest_mail);
	}
	return 1;
}

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) {
		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);
}