changeset 5458:daca7ed634c0 HEAD

Added a simple cydir mail storage backend. It trusts index files completely: if the indexes are gone, the mailbox starts completely empty and overwriting the existing mail files. So probably not a good idea to use this in production yet.
author Timo Sirainen <tss@iki.fi>
date Fri, 30 Mar 2007 15:08:59 +0300
parents b0951692c45c
children 78eaf595359c
files configure.in src/lib-storage/index/Makefile.am src/lib-storage/index/cydir/.cvsignore src/lib-storage/index/cydir/Makefile.am src/lib-storage/index/cydir/cydir-mail.c src/lib-storage/index/cydir/cydir-save.c src/lib-storage/index/cydir/cydir-storage.c src/lib-storage/index/cydir/cydir-storage.h src/lib-storage/index/cydir/cydir-sync.c src/lib-storage/index/cydir/cydir-sync.h src/lib-storage/index/cydir/cydir-transaction.c
diffstat 11 files changed, 1302 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Fri Mar 30 14:43:19 2007 +0300
+++ b/configure.in	Fri Mar 30 15:08:59 2007 +0300
@@ -324,7 +324,7 @@
 		AC_MSG_ERROR([--with-storages needs storage list as parameter])
 	fi
 	mail_storages=`echo "$withval"|sed 's/,/ /g'` ],
-	mail_storages="maildir mbox dbox")
+	mail_storages="maildir mbox dbox cydir")
 AC_SUBST(mail_storages)
 
 AC_ARG_WITH(sql-drivers,
@@ -1842,6 +1842,7 @@
 maildir_libs='$(top_builddir)/src/lib-storage/index/maildir/libstorage_maildir.a'
 mbox_libs='$(top_builddir)/src/lib-storage/index/mbox/libstorage_mbox.a'
 dbox_libs='$(top_builddir)/src/lib-storage/index/dbox/libstorage_dbox.a'
+cydir_libs='$(top_builddir)/src/lib-storage/index/cydir/libstorage_cydir.a'
 index_libs='$(top_builddir)/src/lib-storage/index/libstorage_index.a $(top_builddir)/src/lib-index/libindex.a'
 
 deliver_storage="mbox"
@@ -1917,6 +1918,7 @@
 src/lib-storage/index/maildir/Makefile
 src/lib-storage/index/mbox/Makefile
 src/lib-storage/index/dbox/Makefile
+src/lib-storage/index/cydir/Makefile
 src/lib-storage/register/Makefile
 src/auth/Makefile
 src/deliver/Makefile
--- a/src/lib-storage/index/Makefile.am	Fri Mar 30 14:43:19 2007 +0300
+++ b/src/lib-storage/index/Makefile.am	Fri Mar 30 15:08:59 2007 +0300
@@ -1,4 +1,4 @@
-SUBDIRS = maildir mbox dbox
+SUBDIRS = maildir mbox dbox cydir
 
 noinst_LIBRARIES = libstorage_index.a
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/.cvsignore	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/Makefile.am	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,27 @@
+noinst_LIBRARIES = libstorage_cydir.a
+
+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
+
+libstorage_cydir_a_SOURCES = \
+	cydir-mail.c \
+	cydir-save.c \
+	cydir-sync.c \
+	cydir-storage.c \
+	cydir-transaction.c
+
+headers = \
+	cydir-storage.h \
+	cydir-sync.h
+
+if INSTALL_HEADERS
+  pkginc_libdir=$(pkgincludedir)/src/lib-storage/index/cydir
+  pkginc_lib_HEADERS = $(headers)
+else
+  noinst_HEADERS = $(headers)
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-mail.c	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,143 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "istream.h"
+#include "index-mail.h"
+#include "cydir-storage.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+static const char *cydir_mail_get_path(struct mail *mail)
+{
+	const char *dir;
+
+	dir = mailbox_list_get_path(mail->box->storage->list, mail->box->name,
+				    MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	return t_strdup_printf("%s/%u.", dir, mail->uid);
+}
+
+static int cydir_mail_stat(struct mail *mail, struct stat *st_r)
+{
+	const char *path;
+
+	path = cydir_mail_get_path(mail);
+	if (stat(path, st_r) < 0) {
+		if (errno == ENOENT)
+			mail->expunged = TRUE;
+		else {
+			mail_storage_set_critical(mail->box->storage,
+						  "stat(%s) failed: %m", path);
+		}
+		return -1;
+	}
+	return 0;
+}
+
+static time_t cydir_mail_get_received_date(struct mail *_mail)
+{
+	struct index_mail *mail = (struct index_mail *)_mail;
+	struct index_mail_data *data = &mail->data;
+	struct stat st;
+	uint32_t t;
+
+	(void)index_mail_get_received_date(_mail);
+	if (data->received_date != (time_t)-1)
+		return data->received_date;
+
+	if (cydir_mail_stat(_mail, &st) < 0)
+		return (time_t)-1;
+
+	data->received_date = t = st.st_mtime;
+	index_mail_cache_add(mail, MAIL_CACHE_RECEIVED_DATE, &t, sizeof(t));
+	return data->received_date;
+}
+
+static time_t cydir_mail_get_save_date(struct mail *_mail)
+{
+	struct index_mail *mail = (struct index_mail *)_mail;
+	struct index_mail_data *data = &mail->data;
+	struct stat st;
+	uint32_t t;
+
+	(void)index_mail_get_save_date(_mail);
+	if (data->save_date != (time_t)-1)
+		return data->save_date;
+
+	if (cydir_mail_stat(_mail, &st) < 0)
+		return (time_t)-1;
+
+	data->save_date = t = st.st_ctime;
+	index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t));
+	return data->save_date;
+}
+
+static uoff_t cydir_mail_get_physical_size(struct mail *_mail)
+{
+	struct index_mail *mail = (struct index_mail *)_mail;
+	struct index_mail_data *data = &mail->data;
+	struct stat st;
+
+	(void)index_mail_get_physical_size(_mail);
+	if (data->physical_size != (uoff_t)-1)
+		return data->physical_size;
+
+	if (cydir_mail_stat(_mail, &st) < 0)
+		return (time_t)-1;
+
+	index_mail_cache_add(mail, MAIL_CACHE_PHYSICAL_FULL_SIZE,
+			     &data->physical_size, sizeof(data->physical_size));
+	return data->physical_size;
+}
+
+static struct istream *
+cydir_mail_get_stream(struct mail *_mail, struct message_size *hdr_size,
+		      struct message_size *body_size)
+{
+	struct index_mail *mail = (struct index_mail *)_mail;
+	const char *path;
+	int fd;
+
+	if (mail->data.stream == NULL) {
+		path = cydir_mail_get_path(_mail);
+		fd = open(path, O_RDONLY);
+		if (fd == -1) {
+			if (errno == ENOENT)
+				_mail->expunged = TRUE;
+			else {
+				mail_storage_set_critical(_mail->box->storage,
+					"open(%s) failed: %m", path);
+			}
+			return NULL;
+		}
+		mail->data.stream =
+			i_stream_create_file(fd, default_pool,
+					     MAIL_READ_BLOCK_SIZE, TRUE);
+	}
+
+	return index_mail_init_stream(mail, hdr_size, body_size);
+}
+
+struct mail_vfuncs cydir_mail_vfuncs = {
+	index_mail_free,
+	index_mail_set_seq,
+	index_mail_set_uid,
+
+	index_mail_get_flags,
+	index_mail_get_keywords,
+	index_mail_get_parts,
+	index_mail_get_date,
+	cydir_mail_get_received_date,
+	cydir_mail_get_save_date,
+	cydir_mail_get_physical_size, /* physical = virtual in our case */
+	cydir_mail_get_physical_size,
+	index_mail_get_first_header,
+	index_mail_get_headers,
+	index_mail_get_header_stream,
+	cydir_mail_get_stream,
+	index_mail_get_special,
+	index_mail_update_flags,
+	index_mail_update_keywords,
+	index_mail_expunge
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-save.c	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,240 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "istream.h"
+#include "ostream.h"
+#include "ostream-crlf.h"
+#include "str.h"
+#include "index-mail.h"
+#include "cydir-storage.h"
+#include "cydir-sync.h"
+
+#include <stdio.h>
+
+struct cydir_save_context {
+	struct mail_save_context ctx;
+
+	struct cydir_mailbox *mbox;
+	struct mail_index_transaction *trans;
+
+	char *tmp_basename;
+	unsigned int mail_count;
+
+	struct cydir_sync_context *sync_ctx;
+
+	/* updated for each appended mail: */
+	uint32_t seq;
+	struct istream *input;
+	struct ostream *output;
+	struct mail *mail;
+
+	unsigned int failed:1;
+	unsigned int finished:1;
+};
+
+static char *cydir_generate_tmp_filename(void)
+{
+	static unsigned int create_count;
+
+	return i_strdup_printf("%s.P%sQ%uM%s.%s",
+			       dec2str(ioloop_timeval.tv_sec), my_pid,
+			       create_count++,
+			       dec2str(ioloop_timeval.tv_usec), my_hostname);
+}
+
+static const char *
+cydir_get_save_path(struct cydir_save_context *ctx, unsigned int num)
+{
+	const char *dir;
+
+	dir = mailbox_list_get_path(STORAGE(ctx->mbox->storage)->list,
+				    ctx->mbox->ibox.box.name,
+				    MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	return t_strdup_printf("%s/%s.%u", dir, ctx->tmp_basename, num);
+}
+
+int cydir_save_init(struct mailbox_transaction_context *_t,
+		    enum mail_flags flags, struct mail_keywords *keywords,
+		    time_t received_date, int timezone_offset __attr_unused__,
+		    const char *from_envelope __attr_unused__,
+		    struct istream *input, struct mail *dest_mail,
+		    struct mail_save_context **ctx_r)
+{
+	struct cydir_transaction_context *t =
+		(struct cydir_transaction_context *)_t;
+	struct cydir_mailbox *mbox = (struct cydir_mailbox *)t->ictx.ibox;
+	struct cydir_save_context *ctx = t->save_ctx;
+	enum mail_flags save_flags;
+	struct ostream *output;
+	const char *path;
+	int fd;
+
+	i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+	if (received_date == (time_t)-1)
+		received_date = ioloop_time;
+
+	if (ctx == NULL) {
+		ctx = t->save_ctx = i_new(struct cydir_save_context, 1);
+		ctx->ctx.transaction = &t->ictx.mailbox_ctx;
+		ctx->mbox = mbox;
+		ctx->trans = t->ictx.trans;
+		ctx->tmp_basename = cydir_generate_tmp_filename();
+	}
+	ctx->input = input;
+
+	t_push();
+	path = cydir_get_save_path(ctx, ctx->mail_count);
+	fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0660);
+	if (fd != -1) {
+		output = o_stream_create_file(fd, default_pool, 0, TRUE);
+		ctx->output = o_stream_create_crlf(default_pool, output);
+		o_stream_unref(&output);
+	} else {
+		mail_storage_set_critical(_t->box->storage,
+					  "open(%s) failed: %m", path);
+		ctx->failed = TRUE;
+		t_pop();
+		return -1;
+	}
+	t_pop();
+
+	/* add to index */
+	save_flags = (flags & ~MAIL_RECENT) | MAIL_RECENT;
+	mail_index_append(ctx->trans, 0, &ctx->seq);
+	mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_REPLACE,
+				save_flags);
+	if (keywords != NULL) {
+		mail_index_update_keywords(ctx->trans, ctx->seq,
+					   MODIFY_REPLACE, keywords);
+	}
+
+	if (dest_mail == NULL) {
+		if (ctx->mail == NULL)
+			ctx->mail = index_mail_alloc(_t, 0, NULL);
+		dest_mail = ctx->mail;
+	}
+	if (mail_set_seq(dest_mail, ctx->seq) < 0)
+		i_unreached();
+
+	if (t->first_saved_mail_seq == 0)
+		t->first_saved_mail_seq = ctx->seq;
+
+	*ctx_r = &ctx->ctx;
+	return ctx->failed ? -1 : 0;
+}
+
+int cydir_save_continue(struct mail_save_context *_ctx)
+{
+	struct cydir_save_context *ctx = (struct cydir_save_context *)_ctx;
+
+	if (ctx->failed)
+		return -1;
+
+	if (o_stream_send_istream(ctx->output, ctx->input) < 0) {
+		if (ENOSPACE(ctx->output->stream_errno)) {
+			mail_storage_set_error(STORAGE(ctx->mbox->storage),
+					       "Not enough disk space");
+		} else {
+			mail_storage_set_critical(STORAGE(ctx->mbox->storage),
+				"o_stream_send_istream(%s) failed: %m",
+				cydir_get_save_path(ctx, ctx->mail_count));
+		}
+		ctx->failed = TRUE;
+		return -1;
+	}
+	return 0;
+}
+
+int cydir_save_finish(struct mail_save_context *_ctx)
+{
+	struct cydir_save_context *ctx = (struct cydir_save_context *)_ctx;
+
+	ctx->finished = TRUE;
+
+	if (!ctx->failed)
+		ctx->mail_count++;
+
+	return ctx->failed ? -1 : 0;
+}
+
+void cydir_save_cancel(struct mail_save_context *_ctx)
+{
+	struct cydir_save_context *ctx = (struct cydir_save_context *)_ctx;
+
+	ctx->failed = TRUE;
+	(void)cydir_save_finish(_ctx);
+}
+
+int cydir_transaction_save_commit_pre(struct cydir_save_context *ctx)
+{
+	const struct mail_index_header *hdr;
+	uint32_t i, uid, next_uid;
+	const char *dir;
+	string_t *src_path, *dest_path;
+	unsigned int src_prefixlen, dest_prefixlen;
+
+	i_assert(ctx->finished);
+
+	if (cydir_sync_begin(ctx->mbox, &ctx->sync_ctx) < 0) {
+		ctx->failed = TRUE;
+		cydir_transaction_save_rollback(ctx);
+		return -1;
+	}
+
+	hdr = mail_index_get_header(ctx->sync_ctx->sync_view);
+	uid = hdr->next_uid;
+	mail_index_append_assign_uids(ctx->trans, uid, &next_uid);
+
+	dir = mailbox_list_get_path(STORAGE(ctx->mbox->storage)->list,
+				    ctx->mbox->ibox.box.name,
+				    MAILBOX_LIST_PATH_TYPE_MAILBOX);
+
+	src_path = t_str_new(256);
+	str_printfa(src_path, "%s/%s.", dir, ctx->tmp_basename);
+	src_prefixlen = str_len(src_path);
+
+	dest_path = t_str_new(256);
+	str_append(dest_path, dir);
+	str_append_c(dest_path, '/');
+	dest_prefixlen = str_len(dest_path);
+
+	for (i = 0; i < ctx->mail_count; i++, uid++) {
+		str_truncate(src_path, src_prefixlen);
+		str_truncate(dest_path, dest_prefixlen);
+		str_printfa(src_path, "%u", i);
+		str_printfa(dest_path, "%u.", uid);
+
+		if (rename(str_c(src_path), str_c(dest_path)) < 0) {
+			mail_storage_set_critical(STORAGE(ctx->mbox->storage),
+				"rename(%s, %s) failed: %m",
+				str_c(src_path), str_c(dest_path));
+			ctx->failed = TRUE;
+			cydir_transaction_save_rollback(ctx);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+void cydir_transaction_save_commit_post(struct cydir_save_context *ctx)
+{
+	(void)cydir_sync_finish(&ctx->sync_ctx, TRUE);
+	cydir_transaction_save_rollback(ctx);
+}
+
+void cydir_transaction_save_rollback(struct cydir_save_context *ctx)
+{
+	if (!ctx->finished)
+		cydir_save_cancel(&ctx->ctx);
+
+	if (ctx->sync_ctx != NULL)
+		(void)cydir_sync_finish(&ctx->sync_ctx, FALSE);
+
+	if (ctx->mail != NULL)
+		index_mail_free(ctx->mail);
+	i_free(ctx->tmp_basename);
+	i_free(ctx);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-storage.c	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,532 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#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 "cydir-sync.h"
+#include "cydir-storage.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define CREATE_MODE 0770 /* umask() should limit it more */
+
+#define CYDIR_LIST_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, cydir_mailbox_list_module)
+
+extern struct mail_storage cydir_storage;
+extern struct mailbox cydir_mailbox;
+
+static MODULE_CONTEXT_DEFINE_INIT(cydir_mailbox_list_module,
+				  &mailbox_list_module_register);
+
+static int
+cydir_list_delete_mailbox(struct mailbox_list *list, const char *name);
+static int cydir_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
+cydir_get_list_settings(struct mailbox_list_settings *list_set,
+			const char *data, enum mail_storage_flags flags)
+{
+	bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
+	const char *p;
+	size_t len;
+
+	memset(list_set, 0, sizeof(*list_set));
+	list_set->subscription_fname = CYDIR_SUBSCRIPTION_FILE_NAME;
+	list_set->maildir_name = "";
+
+	if (data == NULL || *data == '\0') {
+		/* we won't do any guessing for this format. */
+		if (debug)
+			i_info("cydir: mailbox location not given");
+		return -1;
+	}
+
+	/* <root dir> [:INDEX=<dir>] */
+	if (debug)
+		i_info("cydir: data=%s", data);
+	p = strchr(data, ':');
+	if (p == NULL)
+		list_set->root_dir = data;
+	else {
+		list_set->root_dir = t_strdup_until(data, p);
+
+		do {
+			p++;
+			if (strncmp(p, "INDEX=", 6) == 0)
+				list_set->index_dir = t_strcut(p+6, ':');
+			p = strchr(p, ':');
+		} while (p != NULL);
+	}
+
+	/* strip trailing '/' */
+	len = strlen(list_set->root_dir);
+	if (list_set->root_dir[len-1] == '/')
+		list_set->root_dir = t_strndup(list_set->root_dir, len-1);
+
+	if (list_set->index_dir != NULL &&
+	    strcmp(list_set->index_dir, "MEMORY") == 0)
+		list_set->index_dir = "";
+	return 0;
+}
+
+static struct mail_storage *
+cydir_create(const char *data, const char *user,
+	     enum mail_storage_flags flags,
+	     enum file_lock_method lock_method)
+{
+	struct cydir_storage *storage;
+	struct index_storage *istorage;
+	struct mailbox_list_settings list_set;
+	struct mailbox_list *list;
+	const char *error;
+	struct stat st;
+	pool_t pool;
+
+	if (cydir_get_list_settings(&list_set, data, flags) < 0)
+		return NULL;
+	list_set.mail_storage_flags = &flags;
+	list_set.lock_method = &lock_method;
+
+	if ((flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) != 0) {
+		if (stat(list_set.root_dir, &st) < 0) {
+			if (errno != ENOENT) {
+				i_error("stat(%s) failed: %m",
+					list_set.root_dir);
+			}
+			return NULL;
+		}
+	}
+
+	if (mkdir_parents(list_set.root_dir, CREATE_MODE) < 0 &&
+	    errno != EEXIST) {
+		i_error("mkdir_parents(%s) failed: %m", list_set.root_dir);
+		return NULL;
+	}
+
+	pool = pool_alloconly_create("storage", 512+256);
+	storage = p_new(pool, struct cydir_storage, 1);
+
+	if (mailbox_list_init("fs", &list_set,
+			      mail_storage_get_list_flags(flags),
+			      &list, &error) < 0) {
+		i_error("cydir fs: %s", error);
+		pool_unref(pool);
+		return NULL;
+	}
+	storage->list_module_ctx.super = list->v;
+	list->v.iter_is_mailbox = cydir_list_iter_is_mailbox;
+	list->v.delete_mailbox = cydir_list_delete_mailbox;
+
+	MODULE_CONTEXT_SET_FULL(list, cydir_mailbox_list_module,
+				storage, &storage->list_module_ctx);
+
+	istorage = INDEX_STORAGE(storage);
+	istorage->storage = cydir_storage;
+	istorage->storage.pool = pool;
+
+	istorage->user = p_strdup(pool, user);
+	index_storage_init(istorage, list, flags, lock_method);
+
+	return STORAGE(storage);
+}
+
+static void cydir_free(struct mail_storage *_storage)
+{
+	struct index_storage *storage = (struct index_storage *) _storage;
+
+	index_storage_deinit(storage);
+	pool_unref(storage->storage.pool);
+}
+
+static bool cydir_autodetect(const char *data __attr_unused__,
+			     enum mail_storage_flags flags __attr_unused__)
+{
+	return FALSE;
+}
+
+static int create_cydir(struct mail_storage *storage, const char *path)
+{
+	const char *error;
+
+	if (mkdir_parents(path, CREATE_MODE) < 0 && errno != EEXIST) {
+		if (mail_storage_errno2str(&error)) {
+			mail_storage_set_error(storage, "%s", error);
+			return -1;
+		}
+
+		mail_storage_set_critical(storage, "mkdir(%s) failed: %m",
+					  path);
+		return -1;
+	}
+	return 0;
+}
+
+static int create_index_dir(struct mail_storage *storage, const char *name)
+{
+	const char *root_dir, *index_dir;
+
+	root_dir = mailbox_list_get_path(storage->list, name,
+					 MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	index_dir = mailbox_list_get_path(storage->list, name,
+					  MAILBOX_LIST_PATH_TYPE_INDEX);
+	if (strcmp(index_dir, root_dir) == 0)
+		return 0;
+
+	if (mkdir_parents(index_dir, CREATE_MODE) < 0 && errno != EEXIST) {
+		mail_storage_set_critical(storage, "mkdir(%s) failed: %m",
+					  index_dir);
+		return -1;
+	}
+
+	return 0;
+}
+
+static bool cydir_is_recent(struct index_mailbox *ibox __attr_unused__,
+			    uint32_t uid __attr_unused__)
+{
+	return FALSE;
+}
+
+static struct mailbox *
+cydir_open(struct cydir_storage *storage, const char *name,
+	   enum mailbox_open_flags flags)
+{
+	struct index_storage *istorage = INDEX_STORAGE(storage);
+	struct mail_storage *_storage = STORAGE(storage);
+	struct cydir_mailbox *mbox;
+	struct mail_index *index;
+	const char *path, *index_dir;
+	pool_t pool;
+
+	path = mailbox_list_get_path(_storage->list, name,
+				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	index_dir = mailbox_list_get_path(_storage->list, name,
+					  MAILBOX_LIST_PATH_TYPE_INDEX);
+
+	if (create_cydir(_storage, path) < 0)
+		return NULL;
+	if (create_index_dir(_storage, name) < 0)
+		return NULL;
+
+	index = index_storage_alloc(index_dir, path, CYDIR_INDEX_PREFIX);
+
+	pool = pool_alloconly_create("cydir mailbox", 1024+512);
+	mbox = p_new(pool, struct cydir_mailbox, 1);
+	mbox->ibox.box = cydir_mailbox;
+	mbox->ibox.box.pool = pool;
+	mbox->ibox.storage = istorage;
+	mbox->ibox.mail_vfuncs = &cydir_mail_vfuncs;
+	mbox->ibox.is_recent = cydir_is_recent;
+	mbox->ibox.index = index;
+
+	mbox->storage = storage;
+	mbox->path = p_strdup(pool, path);
+
+	index_storage_mailbox_init(&mbox->ibox, name, flags, FALSE);
+	return &mbox->ibox.box;
+}
+
+static struct mailbox *
+cydir_mailbox_open(struct mail_storage *_storage, const char *name,
+		   struct istream *input, enum mailbox_open_flags flags)
+{
+	struct cydir_storage *storage = (struct cydir_storage *)_storage;
+	const char *path;
+	struct stat st;
+
+	mail_storage_clear_error(_storage);
+
+	if (input != NULL) {
+		mail_storage_set_critical(_storage,
+			"cydir doesn't support streamed mailboxes");
+		return NULL;
+	}
+
+	if (strcmp(name, "INBOX") == 0)
+		return cydir_open(storage, "INBOX", flags);
+
+	if (!mailbox_list_is_valid_existing_name(_storage->list, name)) {
+		mail_storage_set_error(_storage, "Invalid mailbox name");
+		return NULL;
+	}
+
+	path = mailbox_list_get_path(_storage->list, name,
+				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	if (stat(path, &st) == 0) {
+		return cydir_open(storage, name, flags);
+	} else if (errno == ENOENT) {
+		mail_storage_set_error(_storage,
+			MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name);
+		return NULL;
+	} else {
+		mail_storage_set_critical(_storage, "stat(%s) failed: %m",
+					  path);
+		return NULL;
+	}
+}
+
+static int cydir_mailbox_create(struct mail_storage *_storage,
+				const char *name,
+				bool directory __attr_unused__)
+{
+	const char *path;
+	struct stat st;
+
+	mail_storage_clear_error(_storage);
+
+	if (!mailbox_list_is_valid_create_name(_storage->list, name)) {
+		mail_storage_set_error(_storage, "Invalid mailbox name");
+		return -1;
+	}
+
+	path = mailbox_list_get_path(_storage->list, name,
+				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	if (stat(path, &st) == 0) {
+		mail_storage_set_error(_storage, "Mailbox already exists");
+		return -1;
+	}
+
+	return create_cydir(_storage, path);
+}
+
+static int
+cydir_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 (errno == ENOENT) {
+			mailbox_list_set_error(list, t_strdup_printf(
+				MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name));
+		} else {
+			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_directory(%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, t_strdup_printf(
+			"Directory %s isn't empty, can't delete it.", name));
+		return -1;
+	}
+	return 0;
+}
+
+static int
+cydir_list_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+	struct cydir_storage *storage = CYDIR_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, t_strdup_printf(
+			MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name));
+		return -1;
+	}
+
+	return cydir_delete_nonrecursive(list, src, name);
+}
+
+static int cydir_storage_close(struct mailbox *box)
+{
+        index_storage_mailbox_free(box);
+	return 0;
+}
+
+static void
+cydir_notify_changes(struct mailbox *box, unsigned int min_interval,
+		     mailbox_notify_callback_t *callback, void *context)
+{
+	struct cydir_mailbox *mbox = (struct cydir_mailbox *)box;
+
+	mbox->ibox.min_notify_interval = min_interval;
+	mbox->ibox.notify_callback = callback;
+	mbox->ibox.notify_context = context;
+
+	if (callback == NULL) {
+		index_mailbox_check_remove_all(&mbox->ibox);
+		return;
+	}
+
+	index_mailbox_check_add(&mbox->ibox, mbox->path);
+}
+
+static int cydir_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)
+{
+	const char *mail_path;
+	struct stat st;
+	int ret = 1;
+
+	if (strchr(fname, '.') != NULL) {
+		*flags = MAILBOX_NOSELECT;
+		return 0;
+	}
+
+	/* 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 &&
+	    (ctx->flags & MAILBOX_LIST_ITER_FAST_FLAGS) != 0) {
+		/* it's a file */
+		*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+		return 0;
+	}
+
+	/* need to stat() then */
+	t_push();
+	mail_path = t_strconcat(dir, "/", fname, NULL);
+
+	if (stat(mail_path, &st) == 0) {
+		if (!S_ISDIR(st.st_mode)) {
+			/* non-directory */
+			*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+			ret = 0;
+		}
+	} else {
+		/* non-selectable, but may contain subdirs */
+		*flags |= MAILBOX_NOSELECT;
+	}
+	t_pop();
+
+	return ret;
+}
+
+static void cydir_class_init(void)
+{
+	cydir_transaction_class_init();
+}
+
+static void cydir_class_deinit(void)
+{
+	cydir_transaction_class_deinit();
+}
+
+struct mail_storage cydir_storage = {
+	MEMBER(name) CYDIR_STORAGE_NAME,
+	MEMBER(mailbox_is_file) FALSE,
+
+	{
+		cydir_class_init,
+		cydir_class_deinit,
+		cydir_create,
+		cydir_free,
+		cydir_autodetect,
+		index_storage_set_callbacks,
+		cydir_mailbox_open,
+		cydir_mailbox_create,
+		index_storage_get_last_error
+	}
+};
+
+struct mailbox cydir_mailbox = {
+	MEMBER(name) NULL, 
+	MEMBER(storage) NULL, 
+
+	{
+		index_storage_is_readonly,
+		index_storage_allow_new_keywords,
+		cydir_storage_close,
+		index_storage_get_status,
+		cydir_storage_sync_init,
+		index_mailbox_sync_next,
+		index_mailbox_sync_deinit,
+		cydir_notify_changes,
+		index_transaction_begin,
+		index_transaction_commit,
+		index_transaction_rollback,
+		index_keywords_create,
+		index_keywords_free,
+		index_storage_get_uids,
+		index_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,
+		cydir_save_init,
+		cydir_save_continue,
+		cydir_save_finish,
+		cydir_save_cancel,
+		mail_storage_copy,
+		index_storage_is_inconsistent
+	}
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-storage.h	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,55 @@
+#ifndef __CYDIR_STORAGE_H
+#define __CYDIR_STORAGE_H
+
+#include "index-storage.h"
+#include "mailbox-list-private.h"
+
+#define CYDIR_STORAGE_NAME "cydir"
+#define CYDIR_SUBSCRIPTION_FILE_NAME "subscriptions."
+#define CYDIR_INDEX_PREFIX "dovecot.index"
+
+#define STORAGE(mbox_storage) \
+	(&(mbox_storage)->storage.storage)
+#define INDEX_STORAGE(mbox_storage) \
+	(&(mbox_storage)->storage)
+
+struct cydir_storage {
+	struct index_storage storage;
+	union mailbox_list_module_context list_module_ctx;
+};
+
+struct cydir_mailbox {
+	struct index_mailbox ibox;
+	struct cydir_storage *storage;
+
+	const char *path;
+};
+
+struct cydir_transaction_context {
+	struct index_transaction_context ictx;
+	union mail_index_transaction_module_context module_ctx;
+
+	uint32_t first_saved_mail_seq;
+	struct cydir_save_context *save_ctx;
+};
+
+extern struct mail_vfuncs cydir_mail_vfuncs;
+
+void cydir_transaction_created(struct mail_index_transaction *t);
+void cydir_transaction_class_init(void);
+void cydir_transaction_class_deinit(void);
+
+int cydir_save_init(struct mailbox_transaction_context *_t,
+		    enum mail_flags flags, struct mail_keywords *keywords,
+		    time_t received_date, int timezone_offset,
+		    const char *from_envelope, struct istream *input,
+		    struct mail *dest_mail, struct mail_save_context **ctx_r);
+int cydir_save_continue(struct mail_save_context *ctx);
+int cydir_save_finish(struct mail_save_context *ctx);
+void cydir_save_cancel(struct mail_save_context *ctx);
+
+int cydir_transaction_save_commit_pre(struct cydir_save_context *ctx);
+void cydir_transaction_save_commit_post(struct cydir_save_context *ctx);
+void cydir_transaction_save_rollback(struct cydir_save_context *ctx);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-sync.c	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,169 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "cydir-storage.h"
+#include "cydir-sync.h"
+
+static int cydir_sync_set_uidvalidity(struct cydir_sync_context *ctx)
+{
+	struct mail_index_transaction *trans;
+	uint32_t uid_validity = ioloop_time;
+	uint32_t seq;
+	uoff_t offset;
+
+	trans = mail_index_transaction_begin(ctx->sync_view, FALSE, TRUE);
+	mail_index_update_header(trans,
+		offsetof(struct mail_index_header, uid_validity),
+		&uid_validity, sizeof(uid_validity), TRUE);
+
+	if (mail_index_transaction_commit(&trans, &seq, &offset) < 0) {
+		mail_storage_set_index_error(&ctx->mbox->ibox);
+		return -1;
+	}
+	return 0;
+}
+
+static string_t *cydir_get_path_prefix(struct cydir_mailbox *mbox)
+{
+	string_t *path = t_str_new(256);
+	const char *dir;
+
+	dir = mailbox_list_get_path(STORAGE(mbox->storage)->list,
+				    mbox->ibox.box.name,
+				    MAILBOX_LIST_PATH_TYPE_MAILBOX);
+	str_append(path, dir);
+	str_append_c(path, '/');
+	return path;
+}
+
+static int cydir_sync_index(struct cydir_sync_context *ctx)
+{
+	const struct mail_index_header *hdr;
+	struct mail_index_sync_rec sync_rec;
+	string_t *path = NULL;
+	unsigned int prefix_len = 0;
+	uint32_t seq1, seq2, uid;
+	int ret;
+
+	hdr = mail_index_get_header(ctx->sync_view);
+	if (hdr->uid_validity == 0) {
+		if (cydir_sync_set_uidvalidity(ctx) < 0)
+			return -1;
+	}
+
+	/* unlink expunged messages */
+	while ((ret = mail_index_sync_next(ctx->index_sync_ctx,
+					   &sync_rec)) > 0) {
+		if (sync_rec.type != MAIL_INDEX_SYNC_TYPE_EXPUNGE)
+			continue;
+
+		if (mail_index_lookup_uid_range(ctx->sync_view,
+						sync_rec.uid1, sync_rec.uid2,
+						&seq1, &seq2) < 0) {
+			mail_storage_set_index_error(&ctx->mbox->ibox);
+			return -1;
+		}
+		if (seq1 == 0) {
+			/* already expunged everything. nothing to do. */
+			continue;
+		}
+
+		if (path == NULL) {
+			path = cydir_get_path_prefix(ctx->mbox);
+			prefix_len = str_len(path);
+		}
+
+		for (; seq1 <= seq2; seq1++) {
+			if (mail_index_lookup_uid(ctx->sync_view, seq1,
+						  &uid) < 0) {
+				mail_storage_set_index_error(&ctx->mbox->ibox);
+				return -1;
+			}
+
+			str_truncate(path, prefix_len);
+			str_printfa(path, "%u.", uid);
+			if (unlink(str_c(path)) < 0 && errno != ENOENT) {
+				mail_storage_set_critical(
+					STORAGE(ctx->mbox->storage),
+					"unlink(%s) failed: %m", str_c(path));
+				/* continue anyway */
+			}
+		}
+	}
+	return 0;
+}
+
+int cydir_sync_begin(struct cydir_mailbox *mbox,
+		     struct cydir_sync_context **ctx_r)
+{
+	struct cydir_sync_context *ctx;
+	int ret;
+
+	ctx = i_new(struct cydir_sync_context, 1);
+	ctx->mbox = mbox;
+	ret = mail_index_sync_begin(mbox->ibox.index, &ctx->index_sync_ctx,
+				    &ctx->sync_view, (uint32_t)-1, (uoff_t)-1,
+				    !mbox->ibox.keep_recent, TRUE);
+	if (ret <= 0) {
+		if (ret < 0)
+			mail_storage_set_index_error(&mbox->ibox);
+		i_free(ctx);
+		return ret;
+	}
+
+	if (cydir_sync_index(ctx) < 0) {
+		mail_index_sync_rollback(&ctx->index_sync_ctx);
+		i_free(ctx);
+		return -1;
+	}
+
+	*ctx_r = ctx;
+	return 0;
+}
+
+int cydir_sync_finish(struct cydir_sync_context **_ctx, bool success)
+{
+	struct cydir_sync_context *ctx = *_ctx;
+	int ret = success ? 0 : -1;
+
+	*_ctx = NULL;
+	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;
+}
+
+int cydir_sync(struct cydir_mailbox *mbox)
+{
+	struct cydir_sync_context *sync_ctx;
+
+	if (cydir_sync_begin(mbox, &sync_ctx) < 0)
+		return -1;
+
+	return cydir_sync_finish(&sync_ctx, TRUE);
+}
+
+struct mailbox_sync_context *
+cydir_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+	struct cydir_mailbox *mbox = (struct cydir_mailbox *)box;
+	int ret = 0;
+
+	if (!box->opened)
+		index_storage_mailbox_open(&mbox->ibox);
+
+	if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 ||
+	    mbox->ibox.sync_last_check + MAILBOX_FULL_SYNC_INTERVAL <=
+	    ioloop_time)
+		ret = cydir_sync(mbox);
+
+	return index_mailbox_sync_init(box, flags, ret < 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-sync.h	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,21 @@
+#ifndef __CYDIR_SYNC_H
+#define __CYDIR_SYNC_H
+
+enum mailbox_sync_flags;
+struct mailbox;
+
+struct cydir_sync_context {
+	struct cydir_mailbox *mbox;
+        struct mail_index_sync_ctx *index_sync_ctx;
+	struct mail_index_view *sync_view;
+};
+
+int cydir_sync_begin(struct cydir_mailbox *mbox,
+		     struct cydir_sync_context **ctx_r);
+int cydir_sync_finish(struct cydir_sync_context **ctx, bool success);
+int cydir_sync(struct cydir_mailbox *mbox);
+
+struct mailbox_sync_context *
+cydir_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/cydir/cydir-transaction.c	Fri Mar 30 15:08:59 2007 +0300
@@ -0,0 +1,103 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "cydir-sync.h"
+#include "cydir-storage.h"
+
+static void (*next_hook_mail_index_transaction_created)
+	(struct mail_index_transaction *t) = NULL;
+
+static int cydir_transaction_commit(struct mail_index_transaction *t,
+				    uint32_t *log_file_seq_r,
+				    uoff_t *log_file_offset_r)
+{
+	struct cydir_transaction_context *dt = MAIL_STORAGE_CONTEXT(t);
+	struct cydir_mailbox *mbox = (struct cydir_mailbox *)dt->ictx.ibox;
+	struct cydir_save_context *save_ctx;
+	bool syncing = t->sync_transaction;
+	int ret = 0;
+
+	if (dt->save_ctx != NULL) {
+		if (cydir_transaction_save_commit_pre(dt->save_ctx) < 0) {
+			dt->save_ctx = NULL;
+			ret = -1;
+		}
+	}
+
+	save_ctx = dt->save_ctx;
+
+	if (ret < 0)
+		index_transaction_finish_rollback(&dt->ictx);
+	else {
+		if (index_transaction_finish_commit(&dt->ictx, log_file_seq_r,
+						    log_file_offset_r) < 0)
+			ret = -1;
+	}
+
+	/* transaction is destroyed now. */
+	dt = NULL;
+
+	if (save_ctx != NULL) {
+		/* unlock uidlist file after writing to transaction log,
+		   to make sure we don't write uids in wrong order. */
+		cydir_transaction_save_commit_post(save_ctx);
+	}
+
+	if (ret == 0 && !syncing) {
+		if (cydir_sync(mbox) < 0)
+			ret = -1;
+	}
+
+	return ret;
+}
+
+static void cydir_transaction_rollback(struct mail_index_transaction *t)
+{
+	struct cydir_transaction_context *dt = MAIL_STORAGE_CONTEXT(t);
+
+	if (dt->save_ctx != NULL)
+		cydir_transaction_save_rollback(dt->save_ctx);
+
+	index_transaction_finish_rollback(&dt->ictx);
+}
+
+void cydir_transaction_created(struct mail_index_transaction *t)
+{
+	struct mailbox *box = MAIL_STORAGE_CONTEXT(t->view->index);
+
+	/* index can be for mailbox list index, in which case box=NULL */
+	if (box != NULL &&
+	    strcmp(box->storage->name, CYDIR_STORAGE_NAME) == 0) {
+		struct cydir_mailbox *cydir = (struct cydir_mailbox *)box;
+		struct cydir_transaction_context *mt;
+
+		mt = i_new(struct cydir_transaction_context, 1);
+		mt->ictx.trans = t;
+		mt->ictx.super = t->v;
+
+		t->v.commit = cydir_transaction_commit;
+		t->v.rollback = cydir_transaction_rollback;
+		MODULE_CONTEXT_SET(t, mail_storage_mail_index_module, mt);
+
+		index_transaction_init(&mt->ictx, &cydir->ibox);
+	}
+
+	if (next_hook_mail_index_transaction_created != NULL)
+		next_hook_mail_index_transaction_created(t);
+}
+
+void cydir_transaction_class_init(void)
+{
+	next_hook_mail_index_transaction_created =
+		hook_mail_index_transaction_created;
+	hook_mail_index_transaction_created = cydir_transaction_created;
+}
+
+void cydir_transaction_class_deinit(void)
+{
+	i_assert(hook_mail_index_transaction_created ==
+		 cydir_transaction_created);
+	hook_mail_index_transaction_created =
+		next_hook_mail_index_transaction_created;
+}