changeset 7663:8fc919084252 HEAD

Added initial support for virtual mailboxes.
author Timo Sirainen <tss@iki.fi>
date Fri, 14 Mar 2008 12:00:32 +0200
parents 2d3d1a61f734
children df3728c2093c
files configure.in src/plugins/Makefile.am src/plugins/virtual/Makefile.am src/plugins/virtual/virtual-config.c src/plugins/virtual/virtual-mail.c src/plugins/virtual/virtual-plugin.c src/plugins/virtual/virtual-plugin.h src/plugins/virtual/virtual-storage.c src/plugins/virtual/virtual-storage.h src/plugins/virtual/virtual-sync.c src/plugins/virtual/virtual-transaction.c
diffstat 11 files changed, 1711 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Fri Mar 14 11:59:36 2008 +0200
+++ b/configure.in	Fri Mar 14 12:00:32 2008 +0200
@@ -2180,6 +2180,7 @@
 src/plugins/quota/Makefile
 src/plugins/imap-quota/Makefile
 src/plugins/trash/Makefile
+src/plugins/virtual/Makefile
 src/plugins/zlib/Makefile
 stamp.h
 dovecot-config.in])
--- a/src/plugins/Makefile.am	Fri Mar 14 11:59:36 2008 +0200
+++ b/src/plugins/Makefile.am	Fri Mar 14 12:00:32 2008 +0200
@@ -8,5 +8,5 @@
 
 SUBDIRS = \
 	acl convert expire fts fts-squat lazy-expunge mail-log mbox-snarf \
-	quota imap-quota trash \
+	quota imap-quota trash virtual \
 	$(ZLIB) $(FTS_LUCENE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/Makefile.am	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,30 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-storage/index
+
+lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+	lib20_virtual_plugin.la
+
+lib20_virtual_plugin_la_SOURCES = \
+	virtual-config.c \
+	virtual-mail.c \
+	virtual-plugin.c \
+	virtual-storage.c \
+	virtual-sync.c \
+	virtual-transaction.c
+
+noinst_HEADERS = \
+	virtual-storage.h
+
+install-exec-local:
+	for d in imap pop3 lda; do \
+	  $(mkdir_p) $(DESTDIR)$(moduledir)/$$d; \
+	  rm -f $(DESTDIR)$(moduledir)/$$d/lib20_virtual_plugin.so; \
+	  $(LN_S) ../lib20_virtual_plugin.so $(DESTDIR)$(moduledir)/$$d; \
+	done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-config.c	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,159 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "str.h"
+#include "imap-parser.h"
+#include "mail-search-build.h"
+#include "virtual-storage.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+struct virtual_parse_context {
+	struct virtual_mailbox *mbox;
+	struct istream *input;
+
+	pool_t pool;
+	string_t *rule;
+	unsigned int mailbox_id;
+	unsigned int rule_idx;
+};
+
+static struct mail_search_arg *
+virtual_search_args_parse(pool_t pool, const string_t *rule,
+			  const char **error_r)
+{
+	struct istream *input;
+	struct imap_parser *parser;
+	const struct imap_arg *args;
+	struct mail_search_arg *sargs;
+	bool fatal;
+	int ret;
+
+	input = i_stream_create_from_data(str_data(rule), str_len(rule));
+	(void)i_stream_read(input);
+
+	parser = imap_parser_create(input, NULL, (size_t)-1);
+	ret = imap_parser_finish_line(parser, 0,  0, &args);
+	if (ret < 0) {
+		sargs = NULL;
+		*error_r = t_strdup(imap_parser_get_error(parser, &fatal));
+	} else {
+		sargs = mail_search_build_from_imap_args(pool, args, error_r);
+	}
+
+	imap_parser_destroy(&parser);
+	i_stream_destroy(&input);
+	return sargs;
+}
+
+static int
+virtual_config_add_rule(struct virtual_parse_context *ctx, const char **error_r)
+{
+	struct virtual_backend_box *const *bboxes;
+	struct mail_search_arg *search_args;
+	unsigned int i, count;
+
+	if (str_len(ctx->rule) == 0)
+		return 0;
+
+	search_args = virtual_search_args_parse(ctx->pool, ctx->rule, error_r);
+	str_truncate(ctx->rule, 0);
+	if (search_args == NULL) {
+		*error_r = t_strconcat("Previous search rule is invalid: ",
+				       *error_r, NULL);
+		return -1;
+	}
+
+	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+	i_assert(ctx->rule_idx < count);
+	for (i = ctx->rule_idx; i < count; i++)
+		bboxes[i]->search_args = search_args;
+
+	ctx->rule_idx = array_count(&ctx->mbox->backend_boxes);
+	return 0;
+}
+
+static int
+virtual_config_parse_line(struct virtual_parse_context *ctx, const char *line,
+			  const char **error_r)
+{
+	struct virtual_backend_box *bbox;
+
+	if (*line == ' ') {
+		/* continues the previous search rule */
+		if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) {
+			*error_r = "Search rule without a mailbox";
+			return -1;
+		}
+		str_append(ctx->rule, line);
+		return 0;
+	}
+	if (virtual_config_add_rule(ctx, error_r) < 0)
+		return -1;
+
+	/* new mailbox */
+	bbox = p_new(ctx->pool, struct virtual_backend_box, 1);
+	bbox->mailbox_id = ++ctx->mailbox_id;
+	bbox->name = p_strdup(ctx->pool, line);
+	array_append(&ctx->mbox->backend_boxes, &bbox, 1);
+	return 0;
+}
+
+int virtual_config_read(struct virtual_mailbox *mbox)
+{
+	struct virtual_parse_context ctx;
+	const char *path, *line, *error;
+	unsigned int linenum = 0;
+	int fd, ret = 0;
+
+	i_array_init(&mbox->backend_boxes, 8);
+
+	path = t_strconcat(mbox->path, "/"VIRTUAL_CONFIG_FNAME, NULL);
+	fd = open(path, O_RDWR);
+	if (fd == -1) {
+		if (errno == ENOENT) {
+			mail_storage_set_error(mbox->ibox.storage,
+				MAIL_ERROR_NOTPOSSIBLE,
+				"Virtual mailbox missing configuration file");
+			return -1;
+		}
+		mail_storage_set_critical(mbox->ibox.storage,
+					  "open(%s) failed: %m", path);
+		return -1;
+	}
+
+	memset(&ctx, 0, sizeof(ctx));
+	ctx.mbox = mbox;
+	ctx.pool = mbox->ibox.box.pool;
+	ctx.rule = t_str_new(256);
+	ctx.input = i_stream_create_fd(fd, (size_t)-1, FALSE);
+	while ((line = i_stream_read_next_line(ctx.input)) != NULL) {
+		linenum++;
+		if (*line == '#')
+			continue;
+		if (*line == '\0')
+			ret = virtual_config_add_rule(&ctx, &error);
+		else
+			ret = virtual_config_parse_line(&ctx, line, &error);
+		if (ret < 0) {
+			mail_storage_set_critical(mbox->ibox.storage,
+						  "%s: Error at line %u: %s",
+						  path, linenum, error);
+			break;
+		}
+	}
+	if (ret == 0)
+		ret = virtual_config_add_rule(&ctx, &error);
+
+	if (ret == 0 && array_count(&mbox->backend_boxes) == 0) {
+		mail_storage_set_critical(mbox->ibox.storage,
+					  "%s: No mailboxes defined", path);
+		ret = -1;
+	}
+	i_stream_unref(&ctx.input);
+	(void)close(fd);
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-mail.c	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,296 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "index-mail.h"
+#include "virtual-storage.h"
+
+struct virtual_mail {
+	struct index_mail imail;
+
+	enum mail_fetch_field wanted_fields;
+	struct mailbox_header_lookup_ctx *wanted_headers;
+
+	/* currently active mail */
+	struct mail *backend_mail;
+	/* all allocated mails */
+	ARRAY_DEFINE(backend_mails, struct mail *);
+};
+
+struct mail *
+virtual_mail_alloc(struct mailbox_transaction_context *t,
+		   enum mail_fetch_field wanted_fields,
+		   struct mailbox_header_lookup_ctx *wanted_headers)
+{
+	struct virtual_mailbox *mbox = (struct virtual_mailbox *)t->box;
+	struct virtual_mail *vmail;
+	pool_t pool;
+
+	pool = pool_alloconly_create("vmail", 1024);
+	vmail = p_new(pool, struct virtual_mail, 1);
+	vmail->imail.mail.pool = pool;
+	vmail->imail.mail.v = virtual_mail_vfuncs;
+	vmail->imail.mail.mail.box = t->box;
+	vmail->imail.mail.mail.transaction = t;
+	array_create(&vmail->imail.mail.module_contexts, pool,
+		     sizeof(void *), 5);
+
+	vmail->imail.data_pool =
+		pool_alloconly_create("virtual index_mail", 512);
+	vmail->imail.ibox = &mbox->ibox;
+	vmail->imail.trans = (struct index_transaction_context *)t;
+
+	vmail->wanted_fields = wanted_fields;
+	vmail->wanted_headers = wanted_headers;
+	i_array_init(&vmail->backend_mails, array_count(&mbox->backend_boxes));
+	return &vmail->imail.mail.mail;
+}
+
+static void virtual_mail_free(struct mail *mail)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+	struct mail **mails;
+	unsigned int i, count;
+
+	mails = array_get_modifiable(&vmail->backend_mails, &count);
+	for (i = 0; i < count; i++)
+		mail_free(&mails[i]);
+	array_free(&vmail->backend_mails);
+
+	pool_unref(&vmail->imail.data_pool);
+	pool_unref(&vmail->imail.mail.pool);
+}
+
+static struct mail *
+backend_mail_find(struct virtual_mail *vmail, struct mailbox *box)
+{
+	struct mail *const *mails;
+	unsigned int i, count;
+
+	mails = array_get(&vmail->backend_mails, &count);
+	for (i = 0; i < count; i++) {
+		if (mails[i]->box == box)
+			return mails[i];
+	}
+	return NULL;
+}
+
+static void virtual_mail_set_seq(struct mail *mail, uint32_t seq)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+	struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box;
+	struct virtual_backend_box *bbox;
+	struct mailbox_transaction_context *backend_trans;
+	const struct virtual_mail_index_record *vrec;
+	const struct mail_index_record *rec;
+	const void *data;
+	bool expunged;
+
+	mail_index_lookup_ext(mbox->ibox.view, seq, mbox->virtual_ext_id,
+			      &data, &expunged);
+	vrec = data;
+
+	bbox = virtual_backend_box_lookup(mbox, vrec->mailbox_id);
+	vmail->backend_mail = backend_mail_find(vmail, bbox->box);
+	if (vmail->backend_mail == NULL) {
+		backend_trans =
+			virtual_transaction_get(mail->transaction, bbox->box);
+		vmail->backend_mail = mail_alloc(backend_trans,
+						 vmail->wanted_fields,
+						 vmail->wanted_headers);
+		array_append(&vmail->backend_mails, &vmail->backend_mail, 1);
+	}
+	mail_set_uid(vmail->backend_mail, vrec->real_uid);
+	memset(&vmail->imail.data, 0, sizeof(vmail->imail.data));
+	p_clear(vmail->imail.data_pool);
+
+	rec = mail_index_lookup(mbox->ibox.view, seq);
+	vmail->imail.data.seq = seq;
+	vmail->imail.data.flags = rec->flags & MAIL_FLAGS_NONRECENT;
+
+	mail->seq = seq;
+	mail->uid = rec->uid;
+
+	mail->expunged = vmail->backend_mail->expunged;
+	mail->has_nuls = vmail->backend_mail->has_nuls;
+	mail->has_no_nuls = vmail->backend_mail->has_no_nuls;
+}
+
+static bool virtual_mail_set_uid(struct mail *mail, uint32_t uid)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+	struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box;
+	uint32_t seq;
+
+	if (!mail_index_lookup_seq(mbox->ibox.view, uid, &seq))
+		return FALSE;
+
+	virtual_mail_set_seq(vmail->backend_mail, seq);
+	return TRUE;
+}
+
+static int
+virtual_mail_get_parts(struct mail *mail, const struct message_part **parts_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_parts(vmail->backend_mail, parts_r);
+}
+
+static int
+virtual_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+	int tz;
+
+	if (timezone_r == NULL)
+		timezone_r = &tz;
+
+	return mail_get_date(vmail->backend_mail, date_r, timezone_r);
+}
+
+static int virtual_mail_get_received_date(struct mail *mail, time_t *date_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_received_date(vmail->backend_mail, date_r);
+}
+
+static int virtual_mail_get_save_date(struct mail *mail, time_t *date_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_save_date(vmail->backend_mail, date_r);
+}
+
+static int virtual_mail_get_virtual_mail_size(struct mail *mail, uoff_t *size_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_virtual_size(vmail->backend_mail, size_r);
+}
+
+static int virtual_mail_get_physical_size(struct mail *mail, uoff_t *size_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_physical_size(vmail->backend_mail, size_r);
+}
+
+static int
+virtual_mail_get_first_header(struct mail *mail, const char *field,
+			      bool decode_to_utf8, const char **value_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+	struct mail_private *p = (struct mail_private *)vmail->backend_mail;
+
+	return p->v.get_first_header(vmail->backend_mail, field,
+				     decode_to_utf8, value_r);
+}
+
+static int
+virtual_mail_get_headers(struct mail *mail, const char *field,
+			 bool decode_to_utf8, const char *const **value_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+	struct mail_private *p = (struct mail_private *)vmail->backend_mail;
+
+	return p->v.get_headers(vmail->backend_mail, field,
+				decode_to_utf8, value_r);
+}
+
+static int
+virtual_mail_get_header_stream(struct mail *mail,
+			       struct mailbox_header_lookup_ctx *headers,
+			       struct istream **stream_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_header_stream(vmail->backend_mail, headers, stream_r);
+}
+
+static int
+virtual_mail_get_stream(struct mail *mail, struct message_size *hdr_size,
+			struct message_size *body_size,
+			struct istream **stream_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_stream(vmail->backend_mail, hdr_size, body_size, stream_r);
+}
+
+static int
+virtual_mail_get_special(struct mail *mail, enum mail_fetch_field field,
+			 const char **value_r)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return mail_get_special(vmail->backend_mail, field, value_r);
+}
+
+static void
+virtual_mail_update_flags(struct mail *mail, enum modify_type modify_type,
+			  enum mail_flags flags)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	mail_update_flags(vmail->backend_mail, modify_type, flags);
+}
+
+static void
+virtual_mail_update_keywords(struct mail *mail, enum modify_type modify_type,
+			     struct mail_keywords *keywords)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	mail_update_keywords(vmail->backend_mail, modify_type, keywords);
+}
+
+static void virtual_mail_expunge(struct mail *mail)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	mail_expunge(vmail->backend_mail);
+}
+
+static void
+virtual_mail_set_cache_corrupted(struct mail *mail, enum mail_fetch_field field)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	mail_set_cache_corrupted(vmail->backend_mail, field);
+}
+
+static struct index_mail *virtual_mail_get_index_mail(struct mail *mail)
+{
+	struct virtual_mail *vmail = (struct virtual_mail *)mail;
+
+	return (struct index_mail *)vmail->backend_mail;
+}
+
+struct mail_vfuncs virtual_mail_vfuncs = {
+	NULL,
+	virtual_mail_free,
+	virtual_mail_set_seq,
+	virtual_mail_set_uid,
+
+	index_mail_get_flags,
+	index_mail_get_keywords,
+	index_mail_get_keyword_indexes,
+	virtual_mail_get_parts,
+	virtual_mail_get_date,
+	virtual_mail_get_received_date,
+	virtual_mail_get_save_date,
+	virtual_mail_get_virtual_mail_size,
+	virtual_mail_get_physical_size,
+	virtual_mail_get_first_header,
+	virtual_mail_get_headers,
+	virtual_mail_get_header_stream,
+	virtual_mail_get_stream,
+	virtual_mail_get_special,
+	virtual_mail_update_flags,
+	virtual_mail_update_keywords,
+	virtual_mail_expunge,
+	virtual_mail_set_cache_corrupted,
+	virtual_mail_get_index_mail
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-plugin.c	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,40 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-namespace.h"
+#include "virtual-storage.h"
+#include "virtual-plugin.h"
+
+static void (*virtual_next_hook_mail_namespaces_created)
+	(struct mail_namespace *namespaces);
+
+const char *virtual_plugin_version = PACKAGE_VERSION;
+struct mail_namespace *virtual_all_namespaces;
+
+static void
+virtual_hook_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+	if (virtual_next_hook_mail_namespaces_created != NULL)
+		virtual_next_hook_mail_namespaces_created(namespaces);
+
+	/* FIXME: some day we should support multiple clients and this
+	   global namespaces list doesn't work */
+	virtual_all_namespaces = namespaces;
+}
+
+void virtual_plugin_init(void)
+{
+	mail_storage_class_register(&virtual_storage);
+
+	virtual_next_hook_mail_namespaces_created =
+		hook_mail_namespaces_created;
+	hook_mail_namespaces_created = virtual_hook_mail_namespaces_created;
+}
+
+void virtual_plugin_deinit(void)
+{
+	mail_storage_class_unregister(&virtual_storage);
+
+	hook_mail_namespaces_created =
+		virtual_next_hook_mail_namespaces_created;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-plugin.h	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,9 @@
+#ifndef VIRTUAL_PLUGIN_H
+#define VIRTUAL_PLUGIN_H
+
+extern struct mail_namespace *virtual_all_namespaces;
+
+void virtual_plugin_init(void);
+void virtual_plugin_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-storage.c	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,517 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mkdir-parents.h"
+#include "unlink-directory.h"
+#include "index-mail.h"
+#include "mail-copy.h"
+#include "mail-search.h"
+#include "virtual-plugin.h"
+#include "virtual-storage.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define VIRTUAL_LIST_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, virtual_mailbox_list_module)
+
+extern struct mail_storage virtual_storage;
+extern struct mailbox virtual_mailbox;
+
+static MODULE_CONTEXT_DEFINE_INIT(virtual_mailbox_list_module,
+				  &mailbox_list_module_register);
+
+static int
+virtual_list_delete_mailbox(struct mailbox_list *list, const char *name);
+static int
+virtual_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
+			     const char *dir, const char *fname,
+			     enum mailbox_list_file_type type,
+			     enum mailbox_info_flags *flags);
+
+static int
+virtual_get_list_settings(struct mailbox_list_settings *list_set,
+			  const char *data, enum mail_storage_flags flags,
+			  const char **layout_r, const char **error_r)
+{
+	bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
+
+	*layout_r = "fs";
+
+	memset(list_set, 0, sizeof(*list_set));
+	list_set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME;
+	list_set->maildir_name = "";
+
+	if (data == NULL || *data == '\0' || *data == ':') {
+		/* we won't do any guessing for this format. */
+		if (debug)
+			i_info("virtual: mailbox location not given");
+		*error_r = "Root mail directory not given";
+		return -1;
+	}
+
+	if (debug)
+		i_info("virtual: data=%s", data);
+	return mailbox_list_settings_parse(data, list_set, layout_r, NULL,
+					   error_r);
+}
+
+static struct mail_storage *virtual_alloc(void)
+{
+	struct virtual_storage *storage;
+	pool_t pool;
+
+	pool = pool_alloconly_create("virtual storage", 512+256);
+	storage = p_new(pool, struct virtual_storage, 1);
+	storage->storage = virtual_storage;
+	storage->storage.pool = pool;
+
+	return &storage->storage;
+}
+
+static int virtual_create(struct mail_storage *_storage, const char *data,
+			  const char **error_r)
+{
+	struct virtual_storage *storage = (struct virtual_storage *)_storage;
+	struct mailbox_list_settings list_set;
+	struct stat st;
+	const char *layout;
+
+	if (virtual_get_list_settings(&list_set, data, _storage->flags,
+				      &layout, error_r) < 0)
+		return -1;
+	list_set.mail_storage_flags = &_storage->flags;
+	list_set.lock_method = &_storage->lock_method;
+
+	if (stat(list_set.root_dir, &st) < 0) {
+		if (errno == ENOENT) {
+			*error_r = t_strdup_printf(
+				"Root mail directory doesn't exist: %s",
+				list_set.root_dir);
+		} else if (errno == EACCES) {
+			*error_r = mail_storage_eacces_msg("stat",
+							   list_set.root_dir);
+		} else {
+			*error_r = t_strdup_printf("stat(%s) failed: %m",
+						   list_set.root_dir);
+		}
+		return -1;
+	}
+
+	if (mailbox_list_alloc(layout, &_storage->list, error_r) < 0)
+		return -1;
+	storage->list_module_ctx.super = _storage->list->v;
+	_storage->list->v.iter_is_mailbox = virtual_list_iter_is_mailbox;
+	_storage->list->v.delete_mailbox = virtual_list_delete_mailbox;
+
+	MODULE_CONTEXT_SET_FULL(_storage->list, virtual_mailbox_list_module,
+				storage, &storage->list_module_ctx);
+
+	/* finish list init after we've overridden vfuncs */
+	mailbox_list_init(_storage->list, _storage->ns, &list_set,
+			  mail_storage_get_list_flags(_storage->flags));
+	return 0;
+}
+
+struct virtual_backend_box *
+virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id)
+{
+	struct virtual_backend_box *const *bboxes;
+	unsigned int i, count;
+
+	if (mailbox_id == 0)
+		return NULL;
+
+	bboxes = array_get(&mbox->backend_boxes, &count);
+	for (i = mailbox_id-1; i < count; i++) {
+		if (bboxes[i]->mailbox_id == mailbox_id)
+			return bboxes[i];
+	}
+	return NULL;
+}
+
+static int virtual_mailboxes_open(struct virtual_mailbox *mbox,
+				  enum mailbox_open_flags open_flags)
+{
+	struct virtual_backend_box *const *bboxes;
+	struct mail_namespace *ns;
+	unsigned int i, count;
+	enum mail_error error;
+	const char *str;
+
+	open_flags |= MAILBOX_OPEN_KEEP_RECENT;
+
+	bboxes = array_get(&mbox->backend_boxes, &count);
+	for (i = 0; i < count; i++) {
+		ns = mail_namespace_find_inbox(virtual_all_namespaces);
+		bboxes[i]->box = mailbox_open(ns->storage, bboxes[i]->name,
+					      NULL, open_flags);
+		if (bboxes[i]->box == NULL) {
+			str = mail_storage_get_last_error(ns->storage, &error);
+			mail_storage_set_error(mbox->ibox.box.storage,
+					       error, str);
+			break;
+		}
+		i_array_init(&bboxes[i]->uids, 64);
+	}
+	if (i == count)
+		return 0;
+	else {
+		/* failed */
+		for (; i > 0; i--) {
+			mailbox_close(&bboxes[i-1]->box);
+			array_free(&bboxes[i-1]->uids);
+		}
+		return -1;
+	}
+}
+
+static struct mailbox *
+virtual_open(struct virtual_storage *storage, const char *name,
+	     enum mailbox_open_flags flags)
+{
+	struct mail_storage *_storage = &storage->storage;
+	struct virtual_mailbox *mbox;
+	struct mail_index *index;
+	const char *path;
+	pool_t pool;
+
+	path = mailbox_list_get_path(_storage->list, name,
+				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	index = index_storage_alloc(_storage, name, flags,
+				    VIRTUAL_INDEX_PREFIX);
+	mail_index_set_fsync_types(index, MAIL_INDEX_SYNC_TYPE_APPEND |
+				   MAIL_INDEX_SYNC_TYPE_EXPUNGE);
+
+	pool = pool_alloconly_create("virtual mailbox", 1024+512);
+	mbox = p_new(pool, struct virtual_mailbox, 1);
+	mbox->ibox.box = virtual_mailbox;
+	mbox->ibox.box.pool = pool;
+	mbox->ibox.box.storage = &storage->storage;
+	mbox->ibox.storage = &storage->storage;
+	mbox->ibox.mail_vfuncs = &virtual_mail_vfuncs;
+	mbox->ibox.index = index;
+
+	mbox->storage = storage;
+	mbox->path = p_strdup(pool, path);
+
+	mbox->virtual_ext_id =
+		mail_index_ext_register(index, "virtual", 0,
+			sizeof(struct virtual_mail_index_record),
+			sizeof(uint32_t));
+
+	if (virtual_config_read(mbox) < 0 ||
+	    virtual_mailboxes_open(mbox, flags) < 0) {
+		pool_unref(&pool);
+		return NULL;
+	}
+
+	index_storage_mailbox_init(&mbox->ibox, name, flags, FALSE);
+	return &mbox->ibox.box;
+}
+
+static struct mailbox *
+virtual_mailbox_open(struct mail_storage *_storage, const char *name,
+		     struct istream *input, enum mailbox_open_flags flags)
+{
+	struct virtual_storage *storage = (struct virtual_storage *)_storage;
+	const char *path;
+	struct stat st;
+
+	if (input != NULL) {
+		mail_storage_set_critical(_storage,
+			"virtual doesn't support streamed mailboxes");
+		return NULL;
+	}
+
+	path = mailbox_list_get_path(_storage->list, name,
+				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	if (stat(path, &st) == 0)
+		return virtual_open(storage, name, flags);
+	else if (errno == ENOENT) {
+		mail_storage_set_error(_storage, MAIL_ERROR_NOTFOUND,
+			T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+	} else if (errno == EACCES) {
+		mail_storage_set_critical(_storage, "%s",
+			mail_storage_eacces_msg("stat", path));
+	} else {
+		mail_storage_set_critical(_storage, "stat(%s) failed: %m",
+					  path);
+	}
+	return NULL;
+}
+
+static int virtual_storage_mailbox_close(struct mailbox *box)
+{
+	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+	struct virtual_backend_box **bboxes;
+	unsigned int i, count;
+	int ret = 0;
+
+	bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+	for (i = 0; i < count; i++) {
+		if (mailbox_close(&bboxes[i]->box) < 0)
+			ret = -1;
+		array_free(&bboxes[i]->uids);
+	}
+	array_free(&mbox->backend_boxes);
+	return index_storage_mailbox_close(box) < 0 ? -1 : ret;
+}
+
+static int virtual_mailbox_create(struct mail_storage *_storage,
+				  const char *name ATTR_UNUSED,
+				  bool directory ATTR_UNUSED)
+{
+	mail_storage_set_error(_storage, MAIL_ERROR_NOTPOSSIBLE,
+			       "Can't create virtual mailboxes");
+	return -1;
+}
+
+static int
+virtual_delete_nonrecursive(struct mailbox_list *list, const char *path,
+			    const char *name)
+{
+	DIR *dir;
+	struct dirent *d;
+	string_t *full_path;
+	unsigned int dir_len;
+	bool unlinked_something = FALSE;
+
+	dir = opendir(path);
+	if (dir == NULL) {
+		if (!mailbox_list_set_error_from_errno(list)) {
+			mailbox_list_set_critical(list,
+				"opendir(%s) failed: %m", path);
+		}
+		return -1;
+	}
+
+	full_path = t_str_new(256);
+	str_append(full_path, path);
+	str_append_c(full_path, '/');
+	dir_len = str_len(full_path);
+
+	errno = 0;
+	while ((d = readdir(dir)) != NULL) {
+		if (d->d_name[0] == '.') {
+			/* skip . and .. */
+			if (d->d_name[1] == '\0')
+				continue;
+			if (d->d_name[1] == '.' && d->d_name[2] == '\0')
+				continue;
+		}
+
+		str_truncate(full_path, dir_len);
+		str_append(full_path, d->d_name);
+
+		/* trying to unlink() a directory gives either EPERM or EISDIR
+		   (non-POSIX). it doesn't really work anywhere in practise,
+		   so don't bother stat()ing the file first */
+		if (unlink(str_c(full_path)) == 0)
+			unlinked_something = TRUE;
+		else if (errno != ENOENT && errno != EISDIR && errno != EPERM) {
+			mailbox_list_set_critical(list,
+				"unlink(%s) failed: %m",
+				str_c(full_path));
+		}
+	}
+
+	if (closedir(dir) < 0) {
+		mailbox_list_set_critical(list, "closedir(%s) failed: %m",
+					  path);
+	}
+
+	if (rmdir(path) == 0)
+		unlinked_something = TRUE;
+	else if (errno != ENOENT && errno != ENOTEMPTY) {
+		mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path);
+		return -1;
+	}
+
+	if (!unlinked_something) {
+		mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
+			t_strdup_printf("Directory %s isn't empty, "
+					"can't delete it.", name));
+		return -1;
+	}
+	return 0;
+}
+
+static int
+virtual_list_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+	struct virtual_storage *storage = VIRTUAL_LIST_CONTEXT(list);
+	struct stat st;
+	const char *src;
+
+	/* Make sure the indexes are closed before trying to delete the
+	   directory that contains them. It can still fail with some NFS
+	   implementations if indexes are opened by another session, but
+	   that can't really be helped. */
+	index_storage_destroy_unrefed();
+
+	/* delete the index and control directories */
+	if (storage->list_module_ctx.super.delete_mailbox(list, name) < 0)
+		return -1;
+
+	/* check if the mailbox actually exists */
+	src = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	if (stat(src, &st) != 0 && errno == ENOENT) {
+		mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+			T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+		return -1;
+	}
+
+	return virtual_delete_nonrecursive(list, src, name);
+}
+
+static void virtual_notify_changes(struct mailbox *box)
+{
+	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+	// FIXME
+}
+
+static int
+virtual_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx
+			     	ATTR_UNUSED,
+			     const char *dir, const char *fname,
+			     enum mailbox_list_file_type type,
+			     enum mailbox_info_flags *flags)
+{
+	const char *path, *maildir_path;
+	struct stat st;
+	int ret = 1;
+
+	/* try to avoid stat() with these checks */
+	if (type != MAILBOX_LIST_FILE_TYPE_DIR &&
+	    type != MAILBOX_LIST_FILE_TYPE_SYMLINK &&
+	    type != MAILBOX_LIST_FILE_TYPE_UNKNOWN) {
+		/* it's a file */
+		*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+		return 0;
+	}
+
+	/* need to stat() then */
+	path = t_strconcat(dir, "/", fname, NULL);
+	if (stat(path, &st) == 0) {
+		if (!S_ISDIR(st.st_mode)) {
+			/* non-directory */
+			*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+			ret = 0;
+		} else if (st.st_nlink == 2) {
+			/* no subdirectories */
+			*flags |= MAILBOX_NOCHILDREN;
+		} else if (*ctx->list->set.maildir_name != '\0') {
+			/* non-default configuration: we have one directory
+			   containing the mailboxes. if there are 3 links,
+			   either this is a selectable mailbox without children
+			   or non-selectable mailbox with children */
+			if (st.st_nlink > 3)
+				*flags |= MAILBOX_CHILDREN;
+		} else {
+			/* default configuration: all subdirectories are
+			   child mailboxes. */
+			if (st.st_nlink > 2)
+				*flags |= MAILBOX_CHILDREN;
+		}
+	} else {
+		/* non-selectable. probably either access denied, or symlink
+		   destination not found. don't bother logging errors. */
+		*flags |= MAILBOX_NOSELECT;
+	}
+	if ((*flags & MAILBOX_NOSELECT) == 0) {
+		/* make sure it's a selectable mailbox */
+		maildir_path = t_strconcat(path, "/"VIRTUAL_CONFIG_FNAME, NULL);
+		if (stat(maildir_path, &st) < 0 || !S_ISDIR(st.st_mode))
+			*flags |= MAILBOX_NOSELECT;
+	}
+	return ret;
+}
+
+static int
+virtual_save_init(struct mailbox_transaction_context *_t,
+		  enum mail_flags flags ATTR_UNUSED,
+		  struct mail_keywords *keywords ATTR_UNUSED,
+		  time_t received_date ATTR_UNUSED,
+		  int timezone_offset ATTR_UNUSED,
+		  const char *from_envelope ATTR_UNUSED,
+		  struct istream *input ATTR_UNUSED,
+		  struct mail *dest_mail ATTR_UNUSED,
+		  struct mail_save_context **ctx_r)
+{
+	mail_storage_set_error(_t->box->storage, MAIL_ERROR_NOTPOSSIBLE,
+			       "Can't save to virtual mailboxes");
+	*ctx_r = NULL;
+	return -1;
+}
+
+static void virtual_class_init(void)
+{
+	virtual_transaction_class_init();
+}
+
+static void virtual_class_deinit(void)
+{
+	virtual_transaction_class_deinit();
+}
+
+struct mail_storage virtual_storage = {
+	MEMBER(name) VIRTUAL_STORAGE_NAME,
+	MEMBER(mailbox_is_file) FALSE,
+
+	{
+		virtual_class_init,
+		virtual_class_deinit,
+		virtual_alloc,
+		virtual_create,
+		index_storage_destroy,
+		NULL,
+		virtual_mailbox_open,
+		virtual_mailbox_create
+	}
+};
+
+struct mailbox virtual_mailbox = {
+	MEMBER(name) NULL, 
+	MEMBER(storage) NULL, 
+
+	{
+		index_storage_is_readonly,
+		index_storage_allow_new_keywords,
+		virtual_storage_mailbox_close,
+		index_storage_get_status,
+		NULL,
+		NULL,
+		virtual_storage_sync_init,
+		index_mailbox_sync_next,
+		index_mailbox_sync_deinit,
+		NULL,
+		virtual_notify_changes,
+		index_transaction_begin,
+		index_transaction_commit,
+		index_transaction_rollback,
+		index_keywords_create,
+		index_keywords_free,
+		index_storage_get_uids,
+		virtual_mail_alloc,
+		index_header_lookup_init,
+		index_header_lookup_deinit,
+		index_storage_search_init,
+		index_storage_search_deinit,
+		index_storage_search_next_nonblock,
+		index_storage_search_next_update_seq,
+		virtual_save_init,
+		NULL,
+		NULL,
+		NULL,
+		mail_storage_copy,
+		index_storage_is_inconsistent
+	}
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-storage.h	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,70 @@
+#ifndef VIRTUAL_STORAGE_H
+#define VIRTUAL_STORAGE_H
+
+#include "seq-range-array.h"
+#include "index-storage.h"
+#include "mailbox-list-private.h"
+
+#define VIRTUAL_STORAGE_NAME "virtual"
+#define VIRTUAL_SUBSCRIPTION_FILE_NAME ".virtual-subscriptions"
+#define VIRTUAL_CONFIG_FNAME "dovecot-virtual"
+#define VIRTUAL_INDEX_PREFIX "dovecot.index"
+
+struct virtual_mail_index_record {
+	uint32_t mailbox_id;
+	uint32_t real_uid;
+};
+
+struct virtual_storage {
+	struct mail_storage storage;
+	union mailbox_list_module_context list_module_ctx;
+};
+
+struct virtual_backend_box {
+	uint32_t mailbox_id;
+	const char *name;
+	struct mail_search_arg *search_args;
+
+	struct mailbox *box;
+	/* Sorted list of UIDs currently included in the virtual mailbox */
+	ARRAY_TYPE(seq_range) uids;
+
+	struct mail *sync_mail;
+	unsigned int sync_iter_idx;
+	unsigned int sync_iter_prev_real_uid;
+};
+
+struct virtual_mailbox {
+	struct index_mailbox ibox;
+	struct virtual_storage *storage;
+
+	const char *path;
+	uint32_t virtual_ext_id;
+
+	/* Mailboxes this virtual mailbox consists of, sorted by mailbox_id */
+	ARRAY_DEFINE(backend_boxes, struct virtual_backend_box *);
+};
+
+extern struct mail_storage virtual_storage;
+extern struct mail_vfuncs virtual_mail_vfuncs;
+
+int virtual_config_read(struct virtual_mailbox *mbox);
+
+struct virtual_backend_box *
+virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id);
+struct mailbox_transaction_context *
+virtual_transaction_get(struct mailbox_transaction_context *trans,
+			struct mailbox *backend_box);
+
+struct mail *
+virtual_mail_alloc(struct mailbox_transaction_context *t,
+		   enum mail_fetch_field wanted_fields,
+		   struct mailbox_header_lookup_ctx *wanted_headers);
+
+struct mailbox_sync_context *
+virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
+
+void virtual_transaction_class_init(void);
+void virtual_transaction_class_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-sync.c	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,474 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-search-build.h"
+#include "virtual-storage.h"
+
+#include <stdlib.h>
+
+struct virtual_sync_mail {
+	uint32_t vseq;
+	struct virtual_mail_index_record vrec;
+};
+
+struct virtual_sync_context {
+	struct virtual_mailbox *mbox;
+        struct mail_index_sync_ctx *index_sync_ctx;
+	struct mail_index *index;
+	struct mail_index_view *sync_view;
+	struct mail_index_transaction *trans;
+	const char *const *kw_all;
+
+	enum mailbox_sync_flags flags;
+	uint32_t uid_validity;
+	unsigned int expunge_removed:1;
+};
+
+static void virtual_sync_set_uidvalidity(struct virtual_sync_context *ctx)
+{
+	uint32_t uid_validity = ioloop_time;
+
+	mail_index_update_header(ctx->trans,
+		offsetof(struct mail_index_header, uid_validity),
+		&uid_validity, sizeof(uid_validity), TRUE);
+	ctx->uid_validity = uid_validity;
+}
+
+static void virtual_sync_external_flags(struct virtual_sync_context *ctx,
+					struct virtual_backend_box *bbox,
+					uint32_t vseq, uint32_t real_uid)
+{
+	enum mail_flags flags;
+	const char *const *kw_names;
+	struct mail_keywords *keywords;
+
+	if (!mail_set_uid(bbox->sync_mail, real_uid))
+		i_panic("UID lost unexpectedly");
+
+	/* copy flags */
+	flags = mail_get_flags(bbox->sync_mail);
+	mail_index_update_flags(ctx->trans, vseq, MODIFY_REPLACE, flags);
+
+	/* copy keywords */
+	kw_names = mail_get_keywords(bbox->sync_mail);
+	if (kw_names[0] != NULL) {
+		keywords = mail_index_keywords_create(ctx->index, kw_names);
+		mail_index_update_keywords(ctx->trans, vseq,
+					   MODIFY_REPLACE, keywords);
+		mail_index_keywords_free(&keywords);
+	}
+}
+
+static void virtual_sync_external_appends(struct virtual_sync_context *ctx,
+					  struct virtual_backend_box *bbox,
+					  uint32_t uid1, uint32_t uid2)
+{
+	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+	struct virtual_mail_index_record vrec;
+	uint32_t uid, vseq;
+
+	vrec.mailbox_id = bbox->mailbox_id;
+	for (uid = uid1; uid <= uid2; uid++) {
+		mail_index_append(ctx->trans, 0, &vseq);
+		vrec.real_uid = uid;
+		mail_index_update_ext(ctx->trans, vseq, virtual_ext_id,
+				      &vrec, NULL);
+		virtual_sync_external_flags(ctx, bbox, vseq, uid);
+	}
+}
+
+static void
+virtual_sync_external_appends_finish_box(struct virtual_sync_context *ctx,
+					 struct virtual_backend_box *bbox)
+{
+	const struct seq_range *seqs;
+	unsigned int seqs_count;
+	uint32_t first_ruid, last_ruid;
+
+	seqs = array_get(&bbox->uids, &seqs_count);
+	while (bbox->sync_iter_idx < seqs_count) {
+		/* max(seq1,prev_uid+1)..seq2 contain newly seen UIDs */
+		first_ruid = I_MAX(seqs[bbox->sync_iter_idx].seq1,
+				   bbox->sync_iter_prev_real_uid + 1);
+		last_ruid = seqs[bbox->sync_iter_idx].seq2;
+
+		if (first_ruid <= last_ruid) {
+			virtual_sync_external_appends(ctx, bbox,
+						      first_ruid, last_ruid);
+		}
+		bbox->sync_iter_idx++;
+	}
+}
+
+static void
+virtual_sync_external_appends_finish(struct virtual_sync_context *ctx)
+{
+	struct virtual_backend_box *const *bboxes;
+	unsigned int i, count;
+	uint32_t next_uid;
+
+	next_uid = mail_index_get_header(ctx->sync_view)->next_uid;
+	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+	for (i = 0; i < count; i++) {
+		virtual_sync_external_appends_finish_box(ctx, bboxes[i]);
+		mail_index_append_assign_uids(ctx->trans, next_uid, &next_uid);
+	}
+}
+
+static int virtual_sync_mail_cmp(const void *p1, const void *p2)
+{
+	const struct virtual_sync_mail *m1 = p1, *m2 = p2;
+
+	if (m1->vrec.mailbox_id < m2->vrec.mailbox_id)
+		return -1;
+	if (m1->vrec.mailbox_id > m2->vrec.mailbox_id)
+		return 1;
+
+	if (m1->vrec.real_uid < m2->vrec.real_uid)
+		return -1;
+	if (m1->vrec.real_uid > m2->vrec.real_uid)
+		return 1;
+	/* broken */
+	return 0;
+}
+
+static void virtual_sync_external(struct virtual_sync_context *ctx)
+{
+	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+	struct virtual_backend_box *bbox;
+	struct virtual_sync_mail *vmails;
+	const struct virtual_mail_index_record *vrec;
+	const void *data;
+	const struct seq_range *seqs;
+	unsigned int i, seqs_count;
+	uint32_t vseq, first_ruid, last_ruid, messages;
+	bool expunged;
+
+	messages = mail_index_view_get_messages_count(ctx->sync_view);
+
+	/* sort the messages by their backend mailbox and real UID */
+	vmails = messages == 0 ? NULL :
+		i_new(struct virtual_sync_mail, messages);
+	for (vseq = 1; vseq <= messages; vseq++) {
+		mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id,
+				      &data, &expunged);
+		vrec = data;
+		vmails[vseq-1].vseq = vseq;
+		vmails[vseq-1].vrec = *vrec;
+	}
+	qsort(vmails, messages, sizeof(*vmails), virtual_sync_mail_cmp);
+
+	bbox = NULL;
+	for (i = 0; i < messages; i++) {
+		vseq = vmails[i].vseq;
+		vrec = &vmails[i].vrec;
+
+		if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+			bbox = virtual_backend_box_lookup(ctx->mbox,
+							  vrec->mailbox_id);
+			if (bbox == NULL) {
+				/* the entire mailbox is lost */
+				mail_index_expunge(ctx->trans, vseq);
+				continue;
+			}
+		}
+
+		seqs = array_get(&bbox->uids, &seqs_count);
+		while (bbox->sync_iter_idx < seqs_count) {
+			/* max(seq1,prev_uid+1)..min(seq2,uid-1) contain
+			   newly seen UIDs */
+			first_ruid = I_MAX(seqs[bbox->sync_iter_idx].seq1,
+					   bbox->sync_iter_prev_real_uid + 1);
+			last_ruid = I_MIN(seqs[bbox->sync_iter_idx].seq2,
+					  vrec->real_uid - 1);
+			if (first_ruid <= last_ruid) {
+				virtual_sync_external_appends(ctx, bbox,
+							      first_ruid,
+							      last_ruid);
+			}
+			if (vrec->real_uid <= seqs[bbox->sync_iter_idx].seq2)
+				break;
+			bbox->sync_iter_idx++;
+		}
+		if (bbox->sync_iter_idx >= seqs_count ||
+		    vrec->real_uid < seqs[bbox->sync_iter_idx].seq1) {
+			if (ctx->expunge_removed) {
+				mail_index_expunge(ctx->trans, vseq);
+				continue;
+			}
+		}
+
+		/* uid is within seq1..seq2 */
+		bbox->sync_iter_prev_real_uid = vrec->real_uid;
+		virtual_sync_external_flags(ctx, bbox, vseq, vrec->real_uid);
+	}
+	i_free(vmails);
+
+	virtual_sync_external_appends_finish(ctx);
+}
+
+static void virtual_sync_index_rec(struct virtual_sync_context *ctx,
+				   const struct mail_index_sync_rec *sync_rec)
+{
+	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+	struct virtual_backend_box *bbox;
+	const struct virtual_mail_index_record *vrec;
+	const void *data;
+	enum mail_flags flags;
+	struct mail_keywords *keywords;
+	enum modify_type modify_type;
+	const char *kw_names[2];
+	uint32_t vseq, seq1, seq2;
+	bool expunged;
+
+	switch (sync_rec->type) {
+	case MAIL_INDEX_SYNC_TYPE_APPEND:
+		/* don't care */
+		return;
+	case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
+	case MAIL_INDEX_SYNC_TYPE_FLAGS:
+	case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+	case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+	case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
+		break;
+	}
+	if (!mail_index_lookup_seq_range(ctx->sync_view,
+					 sync_rec->uid1, sync_rec->uid2,
+					 &seq1, &seq2)) {
+		/* already expunged, nothing to do. */
+		return;
+	}
+
+	for (vseq = seq1; vseq <= seq2; vseq++) {
+		mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id,
+				      &data, &expunged);
+		vrec = data;
+
+		bbox = virtual_backend_box_lookup(ctx->mbox, vrec->mailbox_id);
+		if (bbox == NULL)
+			continue;
+
+		if (!mail_set_uid(bbox->sync_mail, vrec->real_uid))
+			i_panic("UID lost unexpectedly");
+
+		switch (sync_rec->type) {
+		case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
+			mail_expunge(bbox->sync_mail);
+			break;
+		case MAIL_INDEX_SYNC_TYPE_FLAGS:
+			flags = sync_rec->add_flags & MAIL_FLAGS_NONRECENT;
+			if (flags != 0) {
+				mail_update_flags(bbox->sync_mail,
+						  MODIFY_ADD, flags);
+			}
+			flags = sync_rec->remove_flags & MAIL_FLAGS_NONRECENT;
+			if (flags != 0) {
+				mail_update_flags(bbox->sync_mail,
+						  MODIFY_REMOVE, flags);
+			}
+			break;
+		case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+		case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+			kw_names[0] = ctx->kw_all[sync_rec->keyword_idx];
+			kw_names[1] = NULL;
+			keywords = mailbox_keywords_create_valid(bbox->box,
+								 kw_names);
+
+			modify_type = sync_rec->type ==
+				MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD ?
+				MODIFY_ADD : MODIFY_REMOVE;
+			mail_update_keywords(bbox->sync_mail,
+					     modify_type, keywords);
+			mailbox_keywords_free(bbox->box, &keywords);
+			break;
+		case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
+			kw_names[0] = NULL;
+			keywords = mailbox_keywords_create_valid(bbox->box,
+								 kw_names);
+			mail_update_keywords(bbox->sync_mail, MODIFY_REPLACE,
+					     keywords);
+			mailbox_keywords_free(bbox->box, &keywords);
+			break;
+		case MAIL_INDEX_SYNC_TYPE_APPEND:
+			i_unreached();
+		}
+	}
+}
+
+static void virtual_sync_index(struct virtual_sync_context *ctx)
+{
+	struct mailbox *box = &ctx->mbox->ibox.box;
+	const ARRAY_TYPE(keywords) *keywords;
+	const struct mail_index_header *hdr;
+	struct mail_index_sync_rec sync_rec;
+	uint32_t seq1, seq2;
+
+	hdr = mail_index_get_header(ctx->sync_view);
+	if (hdr->uid_validity != 0)
+		ctx->uid_validity = hdr->uid_validity;
+	else
+		virtual_sync_set_uidvalidity(ctx);
+
+	/* mark the newly seen messages as recent */
+	if (mail_index_lookup_seq_range(ctx->sync_view, hdr->first_recent_uid,
+					hdr->next_uid, &seq1, &seq2)) {
+		index_mailbox_set_recent_seq(&ctx->mbox->ibox, ctx->sync_view,
+					     seq1, seq2);
+	}
+
+	keywords = mail_index_get_keywords(ctx->index);
+	ctx->kw_all = array_count(keywords) == 0 ? NULL :
+		array_idx(keywords, 0);
+	while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec))
+		virtual_sync_index_rec(ctx, &sync_rec);
+
+	if (box->v.sync_notify != NULL)
+		box->v.sync_notify(box, 0, 0);
+}
+
+static int virtual_sync_backend_box(struct virtual_sync_context *ctx,
+				    struct virtual_backend_box *bbox)
+{
+	struct mailbox_transaction_context *trans;
+	struct mail_search_context *search_ctx;
+	struct mail *mail;
+	enum mailbox_sync_flags sync_flags;
+	int ret;
+
+	sync_flags = ctx->flags & (MAILBOX_SYNC_FLAG_FULL_READ |
+				   MAILBOX_SYNC_FLAG_FULL_WRITE |
+				   MAILBOX_SYNC_FLAG_FAST);
+	if (mailbox_sync(bbox->box, sync_flags, 0, NULL) < 0)
+		return -1;
+
+	trans = mailbox_transaction_begin(bbox->box, 0);
+	mail = mail_alloc(trans, 0, NULL);
+
+	mail_search_args_init(bbox->search_args, bbox->box, FALSE);
+	search_ctx = mailbox_search_init(trans, "UTF-8",
+					 bbox->search_args, NULL);
+
+	array_clear(&bbox->uids);
+	while (mailbox_search_next(search_ctx, mail) > 0)
+		seq_range_array_add(&bbox->uids, 0, mail->uid);
+	ret = mailbox_search_deinit(&search_ctx);
+	mail_free(&mail);
+
+	mail_search_args_deinit(bbox->search_args, bbox->box);
+	(void)mailbox_transaction_commit(&trans);
+	return ret;
+}
+
+static int virtual_sync_backend_boxes(struct virtual_sync_context *ctx)
+{
+	struct virtual_backend_box *const *bboxes;
+	struct mailbox_transaction_context *trans;
+	unsigned int i, count;
+
+	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+	for (i = 0; i < count; i++) {
+		if (virtual_sync_backend_box(ctx, bboxes[i]) < 0)
+			return -1;
+
+		bboxes[i]->sync_iter_idx = 0;
+		bboxes[i]->sync_iter_prev_real_uid = 0;
+
+		i_assert(bboxes[i]->sync_mail == NULL);
+		trans = mailbox_transaction_begin(bboxes[i]->box, 0);
+		bboxes[i]->sync_mail = mail_alloc(trans, 0, NULL);
+	}
+	return 0;
+}
+
+static void virtual_sync_backend_boxes_finish(struct virtual_sync_context *ctx)
+{
+	struct virtual_backend_box *const *bboxes;
+	struct mailbox_transaction_context *trans;
+	unsigned int i, count;
+
+	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+	for (i = 0; i < count; i++) {
+		if (bboxes[i]->sync_mail != NULL) {
+			trans = bboxes[i]->sync_mail->transaction;
+			mail_free(&bboxes[i]->sync_mail);
+			(void)mailbox_transaction_commit(&trans);
+		}
+	}
+}
+
+static int virtual_sync_finish(struct virtual_sync_context *ctx, bool success)
+{
+	int ret = success ? 0 : -1;
+
+	virtual_sync_backend_boxes_finish(ctx);
+	if (success) {
+		if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) {
+			mail_storage_set_index_error(&ctx->mbox->ibox);
+			ret = -1;
+		}
+	} else {
+		mail_index_sync_rollback(&ctx->index_sync_ctx);
+	}
+	i_free(ctx);
+	return 0;
+}
+
+static int virtual_sync(struct virtual_mailbox *mbox,
+			enum mailbox_sync_flags flags)
+{
+	struct virtual_sync_context *ctx;
+	enum mail_index_sync_flags index_sync_flags;
+	int ret;
+
+	ctx = i_new(struct virtual_sync_context, 1);
+	ctx->mbox = mbox;
+	ctx->flags = flags;
+	ctx->index = mbox->ibox.index;
+	/* Removed messages are expunged when
+	   a) EXPUNGE is used
+	   b) Mailbox is being opened (FIX_INCONSISTENT is set) */
+	ctx->expunge_removed =
+		(ctx->flags & (MAILBOX_SYNC_FLAG_EXPUNGE |
+			       MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) != 0;
+
+	index_sync_flags = MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY |
+		MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES;
+	if (!mbox->ibox.keep_recent)
+		index_sync_flags |= MAIL_INDEX_SYNC_FLAG_DROP_RECENT;
+
+	ret = mail_index_sync_begin(ctx->index, &ctx->index_sync_ctx,
+				    &ctx->sync_view, &ctx->trans,
+				    index_sync_flags);
+	if (ret <= 0) {
+		if (ret < 0)
+			mail_storage_set_index_error(&mbox->ibox);
+		i_free(ctx);
+		return ret;
+	}
+
+	/* update list of UIDs in mailboxes */
+	if (virtual_sync_backend_boxes(ctx) < 0)
+		return virtual_sync_finish(ctx, FALSE);
+
+	virtual_sync_index(ctx);
+	virtual_sync_external(ctx);
+	return virtual_sync_finish(ctx, TRUE);
+}
+
+struct mailbox_sync_context *
+virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+	int ret = 0;
+
+	if (!box->opened)
+		index_storage_mailbox_open(&mbox->ibox);
+
+	if (index_mailbox_want_full_sync(&mbox->ibox, flags))
+		ret = virtual_sync(mbox, flags);
+
+	return index_mailbox_sync_init(box, flags, ret < 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/virtual/virtual-transaction.c	Fri Mar 14 12:00:32 2008 +0200
@@ -0,0 +1,114 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "virtual-storage.h"
+
+struct virtual_transaction_context {
+	struct index_transaction_context ictx;
+	union mail_index_transaction_module_context module_ctx;
+
+	ARRAY_DEFINE(backend_transactions,
+		     struct mailbox_transaction_context *);
+};
+
+static void (*next_hook_mail_index_transaction_created)
+	(struct mail_index_transaction *t) = NULL;
+
+struct mailbox_transaction_context *
+virtual_transaction_get(struct mailbox_transaction_context *trans,
+			struct mailbox *backend_box)
+{
+	struct virtual_transaction_context *dt =
+		(struct virtual_transaction_context *)trans;
+	struct mailbox_transaction_context *const *bt, *new_bt;
+	unsigned int i, count;
+
+	bt = array_get(&dt->backend_transactions, &count);
+	for (i = 0; i < count; i++) {
+		if (bt[i]->box == backend_box)
+			return bt[i];
+	}
+
+	new_bt = mailbox_transaction_begin(backend_box, trans->flags);
+	array_append(&dt->backend_transactions, &new_bt, 1);
+	return new_bt;
+}
+
+static int virtual_transaction_commit(struct mail_index_transaction *t,
+				      uint32_t *log_file_seq_r,
+				      uoff_t *log_file_offset_r)
+{
+	struct virtual_transaction_context *dt = MAIL_STORAGE_CONTEXT(t);
+	struct mailbox_transaction_context **bt;
+	unsigned int i, count;
+	int ret = 0;
+
+	bt = array_get_modifiable(&dt->backend_transactions, &count);
+	for (i = 0; i < count; i++) {
+		if (mailbox_transaction_commit(&bt[i]) < 0)
+			ret = -1;
+	}
+	array_free(&dt->backend_transactions);
+
+	if (index_transaction_finish_commit(&dt->ictx, log_file_seq_r,
+					    log_file_offset_r) < 0)
+		ret = -1;
+	return ret;
+}
+
+static void virtual_transaction_rollback(struct mail_index_transaction *t)
+{
+	struct virtual_transaction_context *dt = MAIL_STORAGE_CONTEXT(t);
+	struct mailbox_transaction_context **bt;
+	unsigned int i, count;
+
+	bt = array_get_modifiable(&dt->backend_transactions, &count);
+	for (i = 0; i < count; i++)
+		mailbox_transaction_rollback(&bt[i]);
+	array_free(&dt->backend_transactions);
+
+	index_transaction_finish_rollback(&dt->ictx);
+}
+
+static void virtual_transaction_created(struct mail_index_transaction *t)
+{
+	struct mailbox *box = MAIL_STORAGE_CONTEXT(t->view);
+
+	/* index can be for mailbox list index, in which case box=NULL */
+	if (box != NULL &&
+	    strcmp(box->storage->name, VIRTUAL_STORAGE_NAME) == 0) {
+		struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+		struct virtual_transaction_context *mt;
+
+		mt = i_new(struct virtual_transaction_context, 1);
+		mt->ictx.trans = t;
+		mt->ictx.super = t->v;
+
+		t->v.commit = virtual_transaction_commit;
+		t->v.rollback = virtual_transaction_rollback;
+		MODULE_CONTEXT_SET(t, mail_storage_mail_index_module, mt);
+
+		i_array_init(&mt->backend_transactions,
+			     array_count(&mbox->backend_boxes));
+		index_transaction_init(&mt->ictx, &mbox->ibox);
+	}
+
+	if (next_hook_mail_index_transaction_created != NULL)
+		next_hook_mail_index_transaction_created(t);
+}
+
+void virtual_transaction_class_init(void)
+{
+	next_hook_mail_index_transaction_created =
+		hook_mail_index_transaction_created;
+	hook_mail_index_transaction_created = virtual_transaction_created;
+}
+
+void virtual_transaction_class_deinit(void)
+{
+	i_assert(hook_mail_index_transaction_created ==
+		 virtual_transaction_created);
+	hook_mail_index_transaction_created =
+		next_hook_mail_index_transaction_created;
+}