[dovecot-cvs] dovecot/src/lib-storage/index/dbox .cvsignore, NONE, 1.1 Makefile.am, NONE, 1.1 dbox-file.c, NONE, 1.1 dbox-file.h, NONE, 1.1 dbox-list.c, NONE, 1.1 dbox-mail.c, NONE, 1.1 dbox-save.c, NONE, 1.1 dbox-storage.c, NONE, 1.1 dbox-storage.h, NONE, 1.1 dbox-sync-expunge.c, NONE, 1.1 dbox-sync.c, NONE, 1.1 dbox-sync.h, NONE, 1.1 dbox-transaction.c, NONE, 1.1 dbox-uidlist.c, NONE, 1.1 dbox-uidlist.h, NONE, 1.1

cras at dovecot.org cras at dovecot.org
Mon Nov 28 01:05:31 EET 2005


Update of /var/lib/cvs/dovecot/src/lib-storage/index/dbox
In directory talvi:/tmp/cvs-serv21451/src/lib-storage/index/dbox

Added Files:
	.cvsignore Makefile.am dbox-file.c dbox-file.h dbox-list.c 
	dbox-mail.c dbox-save.c dbox-storage.c dbox-storage.h 
	dbox-sync-expunge.c dbox-sync.c dbox-sync.h dbox-transaction.c 
	dbox-uidlist.c dbox-uidlist.h 
Log Message:
Initial implementation of Dovecot's own high performance file format, named
dbox. Currently relies heavily on index files to work, and isn't able to
rebuild them if they're lost. This will be fixed soon. Not tested much yet in
general.



--- NEW FILE: .cvsignore ---
*.la
*.lo
*.o
.deps
.libs
Makefile
Makefile.in
so_locations

--- NEW FILE: Makefile.am ---
noinst_LIBRARIES = libstorage_dbox.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_dbox_a_SOURCES = \
	dbox-file.c \
	dbox-list.c \
	dbox-mail.c \
	dbox-save.c \
	dbox-sync.c \
	dbox-sync-expunge.c \
	dbox-storage.c \
	dbox-transaction.c \
	dbox-uidlist.c

noinst_HEADERS = \
	dbox-file.h \
	dbox-storage.h \
	dbox-sync.h \
	dbox-uidlist.h

--- NEW FILE: dbox-file.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "hex-dec.h"
#include "istream.h"
#include "ostream.h"
#include "read-full.h"
#include "dbox-storage.h"
#include "dbox-file.h"

int dbox_file_lookup_offset(struct dbox_mailbox *mbox,
			    struct mail_index_view *view, uint32_t seq,
			    uint32_t *file_seq_r, uoff_t *offset_r)
{
	const void *data1, *data2;
	int ret;

	ret = mail_index_lookup_ext(view, seq, mbox->dbox_file_ext_idx, &data1);
	ret = ret <= 0 ? ret :
		mail_index_lookup_ext(view, seq, mbox->dbox_offset_ext_idx,
				      &data2);
	if (ret <= 0) {
		if (ret < 0)
			mail_storage_set_index_error(&mbox->ibox);
		return ret;
	}

	if (data1 == NULL || data2 == NULL) {
		*file_seq_r = 0;
		return 1;
	}

	/* success */
	*file_seq_r = *((uint32_t *)data1);
	*offset_r = *((uint64_t *)data2);
	return 1;
}

void dbox_file_close(struct dbox_file *file)
{
	if (file->input != NULL)
		i_stream_unref(file->input);
	if (file->fd != -1) {
		if (close(file->fd) < 0)
			i_error("close(dbox) failed: %m");
	}
	i_free(file->path);
	i_free(file);
}

static int
dbox_file_read_mail_header(struct dbox_mailbox *mbox, struct dbox_file *file,
			   uoff_t offset)
{
	const struct dbox_mail_header *hdr;
	const unsigned char *data;
	size_t size;

	i_stream_seek(file->input, offset);
	(void)i_stream_read_data(file->input, &data, &size,
				 file->mail_header_size-1);
	if (size < file->mail_header_size) {
		if (file->input->stream_errno == 0)
			return 0;

		errno = file->input->stream_errno;
		mail_storage_set_critical(STORAGE(mbox->storage),
					  "read(%s) failed: %m", file->path);
		return -1;
	}
	memcpy(&file->seeked_mail_header, data,
	       sizeof(file->seeked_mail_header));
	file->seeked_offset = offset;

	hdr = &file->seeked_mail_header;
	file->seeked_mail_size =
		hex2dec(hdr->mail_size_hex, sizeof(hdr->mail_size_hex));
	file->seeked_uid = hex2dec(hdr->uid_hex, sizeof(hdr->uid_hex));

	if (memcmp(hdr->magic, DBOX_MAIL_HEADER_MAGIC,
		   sizeof(hdr->magic)) != 0) {
		mail_storage_set_critical(STORAGE(mbox->storage),
			"Corrupted mail header in dbox file %s", file->path);
		return -1;
	}
	if (file->seeked_mail_size == 0 || file->seeked_uid == 0) {
		/* could be legitimately just not written yet. we're at EOF. */
		return 0;
	}
	return 1;
}

int dbox_file_seek(struct dbox_mailbox *mbox, uint32_t file_seq, uoff_t offset)
{
	if (mbox->file != NULL && mbox->file->file_seq != file_seq) {
		dbox_file_close(mbox->file);
		mbox->file = NULL;
	}

	if (mbox->file == NULL) {
		mbox->file = i_new(struct dbox_file, 1);
		mbox->file->file_seq = file_seq;
		mbox->file->fd = -1;

		mbox->file->path =
			i_strdup_printf("%s/"DBOX_MAILDIR_NAME"/"
					DBOX_MAIL_FILE_PREFIX"%x",
					mbox->path, file_seq);
	}

	if (mbox->file->fd == -1) {
		mbox->file->fd = open(mbox->file->path, O_RDWR);
		if (mbox->file->fd == -1) {
			if (errno == ENOENT)
				return 0;
			mail_storage_set_critical(STORAGE(mbox->storage),
				"open(%s) failed: %m", mbox->file->path);
			return -1;
		}

		mbox->file->input =
			i_stream_create_file(mbox->file->fd, default_pool,
					     65536, FALSE);

		if (dbox_file_read_header(mbox, mbox->file) < 0)
			return -1;
	}

	return dbox_file_read_mail_header(mbox, mbox->file, offset);
}

int dbox_file_seek_next_nonexpunged(struct dbox_mailbox *mbox)
{
	uoff_t offset;
	int ret;

	offset = mbox->file->seeked_offset +
		mbox->file->mail_header_size + mbox->file->seeked_mail_size;

	while ((ret = dbox_file_seek(mbox, mbox->file->file_seq, offset)) > 0) {
		if (mbox->file->seeked_mail_header.expunged != '1')
			break;

		/* marked expunged, get to next mail. */
	}
	return ret;
}

void dbox_file_header_init(struct dbox_file_header *hdr)
{
	uint16_t header_size = sizeof(*hdr);
	uint32_t append_offset = header_size;
	uint16_t mail_header_size = sizeof(struct dbox_mail_header);

	memset(hdr, '0', sizeof(*hdr));
	DEC2HEX(hdr->header_size_hex, header_size);
	DEC2HEX(hdr->append_offset_hex, append_offset);
	DEC2HEX(hdr->mail_header_size_hex, mail_header_size);
	// FIXME: set keyword_count
}

int dbox_file_read_header(struct dbox_mailbox *mbox, struct dbox_file *file)
{
	struct dbox_file_header hdr;
	const unsigned char *data;
	size_t size;

	i_stream_seek(file->input, 0);
	(void)i_stream_read_data(file->input, &data, &size, sizeof(hdr)-1);
	if (size < sizeof(hdr)) {
		if (file->input->stream_errno != 0) {
			errno = file->input->stream_errno;
			mail_storage_set_critical(STORAGE(mbox->storage),
				"read(%s) failed: %m", file->path);
			return -1;
		}

		mail_storage_set_critical(STORAGE(mbox->storage),
			"dbox %s: unexpected end of file", file->path);
		return -1;
	}
	memcpy(&hdr, data, sizeof(hdr));

	/* parse the header */
	file->header_size = hex2dec(hdr.header_size_hex,
				    sizeof(hdr.header_size_hex));
	file->append_offset = hex2dec(hdr.append_offset_hex,
				      sizeof(hdr.append_offset_hex));
	file->mail_header_size = hex2dec(hdr.mail_header_size_hex,
					 sizeof(hdr.mail_header_size_hex));
	file->mail_header_padding =
		hex2dec(hdr.mail_header_padding_hex,
			sizeof(hdr.mail_header_padding_hex));
	file->keyword_count = hex2dec(hdr.keyword_count_hex,
				      sizeof(hdr.keyword_count_hex));

	if (file->header_size == 0 || file->append_offset < sizeof(hdr) ||
	    file->mail_header_size < sizeof(struct dbox_mail_header)) {
		mail_storage_set_critical(STORAGE(mbox->storage),
			"dbox %s: broken file header", file->path);
		return -1;
	}
	return 0;
}

--- NEW FILE: dbox-file.h ---
#ifndef __DBOX_FILE_H
#define __DBOX_FILE_H

struct mail_index_view;
struct dbox_mailbox;
struct dbox_file;
struct dbox_file_header;

/* Returns -1 = error, 0 = expunged, 1 = ok */
int dbox_file_lookup_offset(struct dbox_mailbox *mbox,
			    struct mail_index_view *view, uint32_t seq,
			    uint32_t *file_seq_r, uoff_t *offset_r);

void dbox_file_close(struct dbox_file *file);
/* Returns -1 = error, 0 = EOF (mail was just moved / file broken), 1 = ok */
int dbox_file_seek(struct dbox_mailbox *mbox, uint32_t file_seq, uoff_t offset);
int dbox_file_seek_next_nonexpunged(struct dbox_mailbox *mbox);

void dbox_file_header_init(struct dbox_file_header *hdr);
int dbox_file_read_header(struct dbox_mailbox *mbox, struct dbox_file *file);

#endif

--- NEW FILE: dbox-list.c ---
/* Copyright (C) 2002-2005 Timo Sirainen */

#include "lib.h"
#include "unlink-directory.h"
#include "imap-match.h"
#include "subscription-file/subscription-file.h"
#include "dbox-storage.h"
#include "home-expand.h"

#include <dirent.h>
#include <sys/stat.h>

struct list_dir_context {
	struct list_dir_context *prev;

	DIR *dirp;
	char *real_path, *virtual_path;
};

struct dbox_list_context {
	struct mailbox_list_context mailbox_ctx;
	struct index_storage *istorage;

	enum mailbox_list_flags flags;

	struct imap_match_glob *glob;
	struct subsfile_list_context *subsfile_ctx;

	int failed, inbox_found;

	struct mailbox_list *(*next)(struct dbox_list_context *ctx);

	pool_t list_pool;
	struct mailbox_list list;
        struct list_dir_context *dir;
};

static struct mailbox_list *dbox_list_subs(struct dbox_list_context *ctx);
static struct mailbox_list *dbox_list_path(struct dbox_list_context *ctx);
static struct mailbox_list *dbox_list_next(struct dbox_list_context *ctx);

static const char *mask_get_dir(const char *mask)
{
	const char *p, *last_dir;

	last_dir = NULL;
	for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
		if (*p == '/')
			last_dir = p;
	}

	return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir);
}

static const char *
dbox_get_path(struct index_storage *storage, const char *name)
{
	if ((storage->storage.flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) == 0 ||
	    name == NULL || (*name != '/' && *name != '~' && *name != '\0'))
		return t_strconcat(storage->dir, "/", name, NULL);
	else
		return home_expand(name);
}

static int list_opendir(struct mail_storage *storage,
			const char *path, int root, DIR **dirp)
{
	*dirp = opendir(*path == '\0' ? "/" : path);
	if (*dirp != NULL)
		return 1;

	if (ENOTFOUND(errno)) {
		/* root) user gave invalid hiearchy, ignore
		   sub) probably just race condition with other client
		   deleting the mailbox. */
		return 0;
	}

	if (errno == EACCES) {
		if (!root) {
			/* subfolder, ignore */
			return 0;
		}
		mail_storage_set_error(storage, "Access denied");
		return -1;
	}

	mail_storage_set_critical(storage, "opendir(%s) failed: %m", path);
	return -1;
}

struct mailbox_list_context *
dbox_mailbox_list_init(struct mail_storage *storage,
		       const char *ref, const char *mask,
		       enum mailbox_list_flags flags)
{
	struct index_storage *istorage = (struct index_storage *)storage;
	struct dbox_list_context *ctx;
	const char *path, *virtual_path;
	DIR *dirp;

	ctx = i_new(struct dbox_list_context, 1);
	ctx->mailbox_ctx.storage = storage;
	ctx->istorage = istorage;
	ctx->list_pool = pool_alloconly_create("dbox_list", 1024);
        ctx->next = dbox_list_next;

	mail_storage_clear_error(storage);

	/* check that we're not trying to do any "../../" lists */
	if (!dbox_is_valid_mask(storage, ref) ||
	    !dbox_is_valid_mask(storage, mask)) {
		mail_storage_set_error(storage, "Invalid mask");
		return &ctx->mailbox_ctx;
	}

	if (*mask == '/' || *mask == '~') {
		/* mask overrides reference */
	} else if (*ref != '\0') {
		/* merge reference and mask */
		if (ref[strlen(ref)-1] == '/')
			mask = t_strconcat(ref, mask, NULL);
		else
			mask = t_strconcat(ref, "/", mask, NULL);
	}

	if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
		ctx->mailbox_ctx.storage = storage;
		ctx->istorage = istorage;
		ctx->flags = flags;
		ctx->next = dbox_list_subs;

		path = t_strconcat(istorage->dir,
				   "/" SUBSCRIPTION_FILE_NAME, NULL);
		ctx->subsfile_ctx =
			subsfile_list_init(storage, path);
		if (ctx->subsfile_ctx == NULL) {
			ctx->next = dbox_list_next;
			ctx->failed = TRUE;
			return &ctx->mailbox_ctx;
		}
		ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
		return &ctx->mailbox_ctx;
	}

	/* if we're matching only subdirectories, don't bother scanning the
	   parent directories */
	virtual_path = mask_get_dir(mask);

	path = dbox_get_path(istorage, virtual_path);
	if (list_opendir(storage, path, TRUE, &dirp) < 0)
		return &ctx->mailbox_ctx;
	/* if user gave invalid directory, we just don't show any results. */

	ctx->flags = flags;
	ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');

	if (virtual_path != NULL && dirp != NULL)
		ctx->next = dbox_list_path;

	if (dirp != NULL) {
		ctx->dir = i_new(struct list_dir_context, 1);
		ctx->dir->dirp = dirp;
		ctx->dir->real_path = i_strdup(path);
		ctx->dir->virtual_path = i_strdup(virtual_path);
	}
	return &ctx->mailbox_ctx;
}

static void list_dir_context_free(struct list_dir_context *dir)
{
	(void)closedir(dir->dirp);
	i_free(dir->real_path);
	i_free(dir->virtual_path);
	i_free(dir);
}

int dbox_mailbox_list_deinit(struct mailbox_list_context *_ctx)
{
	struct dbox_list_context *ctx = (struct dbox_list_context *)_ctx;
	int ret = ctx->failed ? -1 : 0;

	if (ctx->subsfile_ctx != NULL) {
		if (subsfile_list_deinit(ctx->subsfile_ctx) < 0)
			ret = -1;
	}

	while (ctx->dir != NULL) {
		struct list_dir_context *dir = ctx->dir;

		ctx->dir = dir->prev;
                list_dir_context_free(dir);
	}

	if (ctx->list_pool != NULL)
		pool_unref(ctx->list_pool);
	if (ctx->glob != NULL)
		imap_match_deinit(ctx->glob);
	i_free(ctx);

	return ret;
}

struct mailbox_list *
dbox_mailbox_list_next(struct mailbox_list_context *_ctx)
{
	struct dbox_list_context *ctx = (struct dbox_list_context *)_ctx;

	return ctx->next(ctx);
}

static int list_file(struct dbox_list_context *ctx, const char *fname)
{
        struct list_dir_context *dir;
	const char *list_path, *real_path, *path, *mail_path;
	struct stat st;
	DIR *dirp;
	size_t len;
	enum imap_match_result match, match2;
	int ret, noselect;

	/* skip all hidden files */
	if (fname[0] == '.')
		return 0;

	/* skip all .lock files */
	len = strlen(fname);
	if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
		return 0;

	/* skip Mails/ dir */
	if (strcmp(fname, DBOX_MAILDIR_NAME) == 0)
		return 0;

	/* check the mask */
	if (ctx->dir->virtual_path == NULL)
		list_path = fname;
	else {
		list_path = t_strconcat(ctx->dir->virtual_path,
					"/", fname, NULL);
	}

	if ((match = imap_match(ctx->glob, list_path)) < 0)
		return 0;

	/* first as an optimization check if it contains Mails/ directory.
	   that means it's a directory and it contains mails. */
	real_path = t_strconcat(ctx->dir->real_path, "/", fname, NULL);
	mail_path = t_strconcat(real_path, "/"DBOX_MAILDIR_NAME, NULL);

	if (stat(mail_path, &st) == 0)
		noselect = FALSE;
	else {
		/* non-selectable, but may contain subdirs */
		noselect = TRUE;
		if (stat(real_path, &st) < 0) {
			if (ENOTFOUND(errno))
				return 0; /* just lost it */

			if (errno != EACCES && errno != ELOOP) {
				mail_storage_set_critical(
					ctx->mailbox_ctx.storage,
					"stat(%s) failed: %m", real_path);
				return -1;
			}
		}
	}

	if (!S_ISDIR(st.st_mode)) {
		/* not a directory - we don't care about it */
		return 0;
	}

	/* make sure we give only one correct INBOX */
	if (strcasecmp(list_path, "INBOX") == 0 &&
	    (ctx->flags & MAILBOX_LIST_INBOX) != 0) {
		if (ctx->inbox_found)
			return 0;

		ctx->inbox_found = TRUE;
	}

	/* scan inside the directory */
	path = t_strconcat(list_path, "/", NULL);
	match2 = imap_match(ctx->glob, path);

	ctx->list.flags = noselect ? MAILBOX_NOSELECT : 0;
	if (match > 0)
		ctx->list.name = p_strdup(ctx->list_pool, list_path);
	else if (match2 > 0)
		ctx->list.name = p_strdup(ctx->list_pool, path);
	else
		ctx->list.name = NULL;

	ret = match2 < 0 ? 0 :
		list_opendir(ctx->mailbox_ctx.storage,
			     real_path, FALSE, &dirp);
	if (ret > 0) {
		dir = i_new(struct list_dir_context, 1);
		dir->dirp = dirp;
		dir->real_path = i_strdup(real_path);
		dir->virtual_path = i_strdup(list_path);

		dir->prev = ctx->dir;
		ctx->dir = dir;
	} else if (ret < 0)
		return -1;
	return match > 0 || match2 > 0;
}

static struct mailbox_list *dbox_list_subs(struct dbox_list_context *ctx)
{
	struct stat st;
	const char *name, *path, *p;
	enum imap_match_result match = IMAP_MATCH_NO;

	while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
		match = imap_match(ctx->glob, name);
		if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
			break;
	}

	if (name == NULL)
		return NULL;

	ctx->list.flags = 0;
	ctx->list.name = name;

	if (match == IMAP_MATCH_PARENT) {
		/* placeholder */
		ctx->list.flags = MAILBOX_PLACEHOLDER;
		while ((p = strrchr(name, '/')) != NULL) {
			name = t_strdup_until(name, p);
			if (imap_match(ctx->glob, name) > 0) {
				p_clear(ctx->list_pool);
				ctx->list.name = p_strdup(ctx->list_pool, name);
				return &ctx->list;
			}
		}
		i_unreached();
	}

	if ((ctx->flags & MAILBOX_LIST_FAST_FLAGS) != 0)
		return &ctx->list;

	t_push();
	path = dbox_get_path(ctx->istorage, ctx->list.name);
	if (stat(path, &st) == 0) {
		if (S_ISDIR(st.st_mode))
			ctx->list.flags = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
		else {
			ctx->list.flags = MAILBOX_NOINFERIORS;
		}
	} else {
		ctx->list.flags = MAILBOX_NONEXISTENT;
	}
	t_pop();
	return &ctx->list;
}

static struct mailbox_list *dbox_list_path(struct dbox_list_context *ctx)
{
	ctx->next = dbox_list_next;

	ctx->list.flags = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
	ctx->list.name =
		p_strconcat(ctx->list_pool, ctx->dir->virtual_path, "/", NULL);

	if (imap_match(ctx->glob, ctx->list.name) > 0)
		return &ctx->list;
	else
		return ctx->next(ctx);
}

static struct mailbox_list *dbox_list_inbox(struct dbox_list_context *ctx)
{
	ctx->list.flags = MAILBOX_UNMARKED | MAILBOX_NOCHILDREN;
	ctx->list.name = "INBOX";
	return &ctx->list;
}

static struct mailbox_list *dbox_list_next(struct dbox_list_context *ctx)
{
	struct list_dir_context *dir;
	struct dirent *d;
	int ret;

	p_clear(ctx->list_pool);

	while (ctx->dir != NULL) {
		/* NOTE: list_file() may change ctx->dir */
		while ((d = readdir(ctx->dir->dirp)) != NULL) {
			t_push();
			ret = list_file(ctx, d->d_name);
			t_pop();

			if (ret > 0)
				return &ctx->list;
			if (ret < 0) {
				ctx->failed = TRUE;
				return NULL;
			}
		}

		dir = ctx->dir;
		ctx->dir = dir->prev;
		list_dir_context_free(dir);
	}

	if (!ctx->inbox_found && (ctx->flags & MAILBOX_LIST_INBOX) != 0 &&
	    ctx->glob != NULL && imap_match(ctx->glob, "INBOX")  > 0) {
		/* show inbox */
		ctx->inbox_found = TRUE;
		return dbox_list_inbox(ctx);
	}

	/* finished */
	return NULL;
}

--- NEW FILE: dbox-mail.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "hex-dec.h"
#include "read-full.h"
#include "istream.h"
#include "index-mail.h"
#include "dbox-file.h"
#include "dbox-sync.h"
#include "dbox-storage.h"

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

static int dbox_mail_parse_mail_header(struct index_mail *mail,
				       struct dbox_file *file)
{
	struct dbox_mailbox *mbox =
		(struct dbox_mailbox *)mail->mail.mail.box;
	const struct dbox_mail_header *hdr = &file->seeked_mail_header;
	uint32_t hdr_uid = hex2dec(hdr->uid_hex, sizeof(hdr->uid_hex));

	if (hdr_uid != mail->mail.mail.uid ||
	    memcmp(hdr->magic, DBOX_MAIL_HEADER_MAGIC,
		   sizeof(hdr->magic)) != 0) {
		mail_storage_set_critical(STORAGE(mbox->storage),
			"dbox %s: Cached file offset broken",
			mbox->file->path);

		/* make sure we get it fixed */
		(void)dbox_sync(mbox, TRUE);
		return -1;
	}

	if (hdr->expunged == '1') {
		mail->mail.mail.expunged = TRUE;
		return 0;
	}

	mail->data.physical_size =
		hex2dec(hdr->mail_size_hex, sizeof(hdr->mail_size_hex));
	mail->data.received_date =
		hex2dec(hdr->received_time_hex, sizeof(hdr->received_time_hex));
	return 1;
}

int dbox_mail_lookup_offset(struct index_transaction_context *trans,
			    uint32_t seq, uint32_t *file_seq_r,
			    uoff_t *offset_r)
{
	struct dbox_mailbox *mbox =
		(struct dbox_mailbox *)trans->ibox;
	int synced = FALSE, ret;

	for (;;) {
		ret = dbox_file_lookup_offset(mbox, trans->trans_view, seq,
					      file_seq_r, offset_r);
		if (ret <= 0)
			return ret;
		if (*file_seq_r != 0)
			return 1;

		/* lost file sequence/offset */
		if (synced)
			return -1;

		mail_storage_set_critical(STORAGE(mbox->storage),
			"Cached message offset lost for seq %u in "
			"dbox file %s", seq, mbox->path);

		/* resync and try again */
		if (dbox_sync(mbox, TRUE) < 0)
			return -1;
		synced = TRUE;
	}
}

static int dbox_mail_open(struct index_mail *mail, uoff_t *offset_r)
{
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)mail->ibox;
	uint32_t seq = mail->mail.mail.seq;
	uint32_t file_seq;
	uoff_t offset;
	int i, ret;

	if (mail->mail.mail.expunged)
		return 0;

	for (i = 0; i < 3; i++) {
		ret = dbox_mail_lookup_offset(mail->trans, seq,
					      &file_seq, &offset);
		if (ret <= 0) {
			if (ret == 0)
				mail->mail.mail.expunged = TRUE;
			return ret;
		}

		if ((ret = dbox_file_seek(mbox, file_seq, offset)) < 0)
			return -1;
		if (ret > 0) {
			/* ok */
			*offset_r = offset;
			return dbox_mail_parse_mail_header(mail, mbox->file);
		}

		/* mail was moved. resync index file to find out the new offset
		   and try again. */
		if (mail_index_refresh(mbox->ibox.index) < 0) {
			mail_storage_set_index_error(&mbox->ibox);
			return -1;
		}
	}

	/* FIXME: index just might not be updated yet.. */
	mail_storage_set_critical(STORAGE(mbox->storage),
				  "Cached message offset lost for seq %u in "
				  "dbox file %s", seq, mbox->path);
	return -1;
}

static time_t dbox_mail_get_received_date(struct mail *_mail)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	uoff_t offset;

	(void)index_mail_get_received_date(_mail);
	if (data->received_date != (time_t)-1)
		return data->received_date;

	if (dbox_mail_open(mail, &offset) <= 0)
		return (time_t)-1;
	if (data->received_date == (time_t)-1) {
		/* it's broken and conflicts with our "not found"
		   return value. change it. */
		data->received_date = 0;
	}

	mail_cache_add(mail->trans->cache_trans, mail->data.seq,
		       MAIL_CACHE_RECEIVED_DATE,
		       &data->received_date, sizeof(data->received_date));
	return data->received_date;
}

static uoff_t dbox_mail_get_physical_size(struct mail *_mail)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	uoff_t offset;

	(void)index_mail_get_physical_size(_mail);
	if (data->physical_size != (uoff_t)-1)
		return data->physical_size;

	if (dbox_mail_open(mail, &offset) <= 0)
		return (uoff_t)-1;

	mail_cache_add(mail->trans->cache_trans, mail->data.seq,
		       MAIL_CACHE_PHYSICAL_FULL_SIZE,
		       &data->physical_size, sizeof(data->physical_size));
	return data->physical_size;

}

static struct istream *
dbox_mail_get_stream(struct mail *_mail,
		     struct message_size *hdr_size,
		     struct message_size *body_size)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)mail->ibox;
	uoff_t offset;

	if (mail->data.stream == NULL) {
		if (dbox_mail_open(mail, &offset) <= 0)
			return NULL;

		offset += mbox->file->mail_header_size;
		mail->data.stream =
			i_stream_create_limit(default_pool, mbox->file->input,
					      offset,
					      mbox->file->seeked_mail_size);
	}

	return index_mail_init_stream(mail, hdr_size, body_size);
}

struct mail_vfuncs dbox_mail_vfuncs = {
	index_mail_free,
	index_mail_set_seq,

	index_mail_get_flags,
	index_mail_get_keywords,
	index_mail_get_parts,
	dbox_mail_get_received_date,
	index_mail_get_date,
	index_mail_get_virtual_size,
	dbox_mail_get_physical_size,
	index_mail_get_first_header,
	index_mail_get_headers,
	index_mail_get_header_stream,
	dbox_mail_get_stream,
	index_mail_get_special,
	index_mail_update_flags,
	index_mail_update_keywords,
	index_mail_expunge
};

--- NEW FILE: dbox-save.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "hex-dec.h"
#include "write-full.h"
#include "ostream.h"
#include "dbox-uidlist.h"
#include "dbox-sync.h"
#include "dbox-storage.h"

#include <stddef.h>

struct dbox_save_context {
	struct mail_save_context ctx;

	struct dbox_mailbox *mbox;
	struct mail_index_transaction *trans;
	struct dbox_uidlist_append_ctx *append_ctx;

	uint32_t first_append_seq;
	struct mail_index_sync_ctx *index_sync_ctx;

	/* updated for each appended mail: */
	uint32_t seq;
	struct istream *input;
	struct ostream *output;
        struct dbox_file *file;
	uint64_t hdr_offset;
	uint64_t mail_offset;

	int failed;
};

struct mail_save_context *
dbox_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, int want_mail __attr_unused__)
{
	struct dbox_transaction_context *t =
		(struct dbox_transaction_context *)_t;
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)t->ictx.ibox;
	struct dbox_save_context *ctx = t->save_ctx;
	struct dbox_mail_header hdr;
	enum mail_flags save_flags;
	unsigned int left;
	char buf[128];
	int ret;

	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 dbox_save_context, 1);
		ctx->ctx.transaction = &t->ictx.mailbox_ctx;
		ctx->mbox = mbox;
		ctx->trans = t->ictx.trans;
		ctx->append_ctx = dbox_uidlist_append_init(mbox->uidlist);

		if ((ret = dbox_sync_if_changed(mbox)) < 0) {
			ctx->failed = TRUE;
			return &ctx->ctx;
		}
		if (ret > 0) {
			if (dbox_sync(mbox, FALSE) < 0) {
				ctx->failed = TRUE;
				return &ctx->ctx;
			}
		}
	}
	ctx->input = input;

	if (dbox_uidlist_append_locked(ctx->append_ctx, &ctx->file) < 0) {
		ctx->failed = TRUE;
		return &ctx->ctx;
	}
	ctx->hdr_offset = ctx->file->output->offset;

	/* append mail header. UID and mail size are written later. */
	memset(&hdr, '0', sizeof(hdr));
	memcpy(hdr.magic, DBOX_MAIL_HEADER_MAGIC, sizeof(hdr.magic));
	DEC2HEX(hdr.received_time_hex, received_date);
	hdr.answered = (flags & MAIL_ANSWERED) != 0 ? '1' : '0';
	hdr.flagged = (flags & MAIL_FLAGGED) != 0 ? '1' : '0';
	hdr.deleted = (flags & MAIL_DELETED) != 0 ? '1' : '0';
	hdr.seen = (flags & MAIL_SEEN) != 0 ? '1' : '0';
	hdr.draft = (flags & MAIL_DRAFT) != 0 ? '1' : '0';
	hdr.expunged = '0';
	// FIXME: keywords
	o_stream_send(ctx->file->output, &hdr, sizeof(hdr));

	/* write rest of the header with '0' characters */
	left = ctx->file->mail_header_size - sizeof(hdr);
	memset(buf, '0', sizeof(buf));
	while (left > sizeof(buf)) {
		o_stream_send(ctx->file->output, buf, sizeof(buf));
		left -= sizeof(buf);
	}
	o_stream_send(ctx->file->output, buf, left);
	ctx->mail_offset = ctx->file->output->offset;

	/* 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);
	}
	mail_index_update_ext(ctx->trans, ctx->seq, mbox->dbox_file_ext_idx,
			      &ctx->file->file_seq, NULL);
	mail_index_update_ext(ctx->trans, ctx->seq,
			      mbox->dbox_offset_ext_idx, &ctx->hdr_offset,
			      NULL);

	if (ctx->first_append_seq == 0)
		ctx->first_append_seq = ctx->seq;
	return &ctx->ctx;
}

int dbox_save_continue(struct mail_save_context *_ctx)
{
	struct dbox_save_context *ctx = (struct dbox_save_context *)_ctx;

	if (ctx->failed)
		return -1;

	if (o_stream_send_istream(ctx->file->output, ctx->input) < 0) {
		if (ENOSPACE(ctx->file->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",
				ctx->file->path);
		}
		ctx->failed = TRUE;
		return -1;
	}
	return 0;
}

int dbox_save_finish(struct mail_save_context *_ctx, struct mail *dest_mail)
{
	struct dbox_save_context *ctx = (struct dbox_save_context *)_ctx;
	struct dbox_mail_header hdr;

	if (!ctx->failed) {
		/* write mail size to header */
		DEC2HEX(hdr.mail_size_hex,
			ctx->file->output->offset - ctx->mail_offset);

		if (pwrite_full(ctx->file->fd, hdr.mail_size_hex,
				sizeof(hdr.mail_size_hex), ctx->hdr_offset +
				offsetof(struct dbox_mail_header,
					 mail_size_hex)) < 0) {
			mail_storage_set_critical(STORAGE(ctx->mbox->storage),
						  "pwrite_full(%s) failed: %m",
						  ctx->file->path);
			ctx->failed = TRUE;
		}
	}

	if (ctx->failed)
		return -1;

	dbox_uidlist_append_finish_mail(ctx->append_ctx, ctx->file);

	if (dest_mail != NULL) {
		i_assert(ctx->seq != 0);

		if (mail_set_seq(dest_mail, ctx->seq) < 0)
			return -1;
	}
	return 0;
}

void dbox_save_cancel(struct mail_save_context *_ctx)
{
	struct dbox_save_context *ctx = (struct dbox_save_context *)_ctx;

	ctx->failed = TRUE;
	(void)dbox_save_finish(_ctx, NULL);
}

int dbox_transaction_save_commit_pre(struct dbox_save_context *ctx)
{
	struct index_transaction_context *idx_trans =
		(struct index_transaction_context *)ctx->ctx.transaction;
	struct dbox_mail_header hdr;
	struct dbox_file *file;
	struct mail_index_view *view;
	uint32_t seq, uid, last_uid, file_seq;
	uoff_t offset;
	int ret;

	/* we want the index file to be locked from here until the appends
	   have been written to transaction log */
	if (mail_index_sync_begin(ctx->mbox->ibox.index, &ctx->index_sync_ctx,
				  &view, (uint32_t)-1, (uoff_t)-1,
				  FALSE, FALSE) < 0) {
		ctx->failed = TRUE;
		dbox_transaction_save_rollback(ctx);
		return -1;
	}

	/* uidlist gets locked here. do it after starting index syncing to
	   avoid deadlocks */
	if (dbox_uidlist_append_get_first_uid(ctx->append_ctx, &uid) < 0) {
		ctx->failed = TRUE;
		dbox_transaction_save_rollback(ctx);
		return -1;
	}
	mail_index_append_assign_uids(ctx->trans, uid, &last_uid);

	/* update UIDs */
	for (seq = ctx->first_append_seq; seq <= ctx->seq; seq++, uid++) {
		ret = dbox_mail_lookup_offset(idx_trans, seq,
					      &file_seq, &offset);
		i_assert(ret > 0); /* it's in memory, shouldn't fail! */

		DEC2HEX(hdr.uid_hex, uid);

		file = dbox_uidlist_append_lookup_file(ctx->append_ctx,
						       file_seq);
		if (pwrite_full(ctx->file->fd, hdr.uid_hex,
				sizeof(hdr.uid_hex), offset +
				offsetof(struct dbox_mail_header,
					 uid_hex)) < 0) {
			mail_storage_set_critical(STORAGE(ctx->mbox->storage),
						  "pwrite_full(%s) failed: %m",
						  ctx->file->path);
			ctx->failed = TRUE;
                        dbox_transaction_save_rollback(ctx);
			return -1;
		}
	}

	if (dbox_uidlist_append_commit(ctx->append_ctx) < 0) {
		mail_index_sync_rollback(ctx->index_sync_ctx);
		i_free(ctx);
		return -1;
	}
	return 0;
}

void dbox_transaction_save_commit_post(struct dbox_save_context *ctx)
{
	mail_index_sync_rollback(ctx->index_sync_ctx);
	i_free(ctx);
}

void dbox_transaction_save_rollback(struct dbox_save_context *ctx)
{
	if (ctx->index_sync_ctx != NULL)
		mail_index_sync_rollback(ctx->index_sync_ctx);

        dbox_uidlist_append_rollback(ctx->append_ctx);
	i_free(ctx);
}

--- NEW FILE: dbox-storage.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "home-expand.h"
#include "mkdir-parents.h"
#include "unlink-directory.h"
#include "subscription-file/subscription-file.h"
#include "mail-copy.h"
#include "index-mail.h"
#include "dbox-uidlist.h"
#include "dbox-sync.h"
#include "dbox-storage.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

#define CREATE_MODE 0770 /* umask() should limit it more */

struct rename_context {
	int found;
	size_t oldnamelen;
	const char *newname;
};

extern struct mail_storage dbox_storage;
extern struct mailbox dbox_mailbox;

static int dbox_handle_errors(struct index_storage *istorage)
{
	struct mail_storage *storage = &istorage->storage;

	if (ENOACCESS(errno))
		mail_storage_set_error(storage, "Permission denied");
	else if (ENOSPACE(errno))
		mail_storage_set_error(storage, "Not enough disk space");
	else if (ENOTFOUND(errno))
		mail_storage_set_error(storage, "Directory structure is broken");
	else
		return FALSE;
	return TRUE;
}

static struct mail_storage *
dbox_create(const char *data, const char *user,
	    enum mail_storage_flags flags,
	    enum mail_storage_lock_method lock_method)
{
	int debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
	struct dbox_storage *storage;
	struct index_storage *istorage;
	const char *root_dir, *index_dir, *p;
	size_t len;
	pool_t pool;

	root_dir = index_dir = NULL;

	if (data == NULL || *data == '\0') {
		/* we won't do any guessing for this format. */
		if (debug)
			i_info("dbox: mailbox location not given");
		return NULL;
	}

	/* <root dir> [:INDEX=<dir>] */
	if (debug)
		i_info("dbox: data=%s", data);
	p = strchr(data, ':');
	if (p == NULL)
		root_dir = data;
	else {
		root_dir = t_strdup_until(data, p);

		do {
			p++;
			if (strncmp(p, "INDEX=", 6) == 0)
				index_dir = t_strcut(p+6, ':');
			p = strchr(p, ':');
		} while (p != NULL);
	}

	/* strip trailing '/' */
	len = strlen(root_dir);
	if (root_dir[len-1] == '/')
		root_dir = t_strndup(root_dir, len-1);

	if (index_dir == NULL)
		index_dir = root_dir;
	else if (strcmp(index_dir, "MEMORY") == 0)
		index_dir = NULL;

	if (debug) {
		i_info("dbox: root=%s, index=%s",
		       root_dir, index_dir == NULL ? "" : index_dir);
	}

        root_dir = home_expand(root_dir);
	if (mkdir_parents(root_dir, CREATE_MODE) < 0 && errno != EEXIST) {
		i_error("mkdir_parents(%s) failed: %m", root_dir);
		return NULL;
	}

	pool = pool_alloconly_create("storage", 512);
	storage = p_new(pool, struct dbox_storage, 1);

	istorage = INDEX_STORAGE(storage);
	istorage->storage = dbox_storage;
	istorage->storage.pool = pool;

	istorage->dir = p_strdup(pool, root_dir);
	istorage->index_dir = p_strdup(pool, home_expand(index_dir));
	istorage->user = p_strdup(pool, user);
	istorage->callbacks = p_new(pool, struct mail_storage_callbacks, 1);
	index_storage_init(istorage, flags, lock_method);

	return STORAGE(storage);
}

static void dbox_free(struct mail_storage *_storage)
{
	struct index_storage *storage = (struct index_storage *) _storage;

	index_storage_deinit(storage);
	pool_unref(storage->storage.pool);
}

static int dbox_autodetect(const char *data, enum mail_storage_flags flags)
{
	int debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
	struct stat st;
	const char *path;

	data = t_strcut(data, ':');

	path = t_strconcat(data, "/cur", NULL);
	if (stat(path, &st) < 0) {
		if (debug)
			i_info("dbox autodetect: stat(%s) failed: %m", path);
		return FALSE;
	}

	if (!S_ISDIR(st.st_mode)) {
		if (debug)
			i_info("dbox autodetect: %s not a directory", path);
		return FALSE;
	}
	return TRUE;
}

int dbox_is_valid_mask(struct mail_storage *storage, const char *mask)
{
	const char *p;
	int newdir;

	if ((storage->flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0)
		return TRUE;

	/* make sure it's not absolute path */
	if (*mask == '/' || *mask == '~')
		return FALSE;

	/* make sure there's no "../" stuff */
	newdir = TRUE;
	for (p = mask; *p != '\0'; p++) {
		if (newdir && p[0] == '.' && p[1] == '.' && p[2] == '/')
			return FALSE;
		newdir = p[0] == '/';
	}

	return TRUE;
}

static int dbox_is_valid_create_name(struct mail_storage *storage,
				     const char *name)
{
	size_t len;

	len = strlen(name);
	if (name[0] == '\0' || name[len-1] == '/' ||
	    strchr(name, '*') != NULL || strchr(name, '%') != NULL)
		return FALSE;

	return dbox_is_valid_mask(storage, name);
}

static int dbox_is_valid_existing_name(struct mail_storage *storage,
				       const char *name)
{
	size_t len;

	len = strlen(name);
	if (name[0] == '\0' || name[len-1] == '/')
		return FALSE;

	return dbox_is_valid_mask(storage, name);
}

static const char *
dbox_get_path(struct index_storage *storage, const char *name)
{
	if ((storage->storage.flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0 &&
	    (*name == '/' || *name == '~'))
		return home_expand(name);

	return t_strconcat(storage->dir, "/", name, NULL);
}

static int create_dbox(struct index_storage *storage, const char *dir)
{
	const char *path;

	path = t_strconcat(dir, "/", DBOX_MAILDIR_NAME, NULL);
	if (mkdir_parents(path, CREATE_MODE) < 0 && errno != EEXIST) {
		if (dbox_handle_errors(storage))
			return -1;

		mail_storage_set_critical(&storage->storage,
					  "mkdir(%s) failed: %m", dir);
		return -1;
	}
	return 0;
}

static int create_index_dir(struct index_storage *storage, const char *name)
{
	const char *dir;

	if (storage->index_dir == NULL)
		return 0;

	if (strcmp(storage->index_dir, storage->dir) == 0)
		return 0;

	dir = t_strconcat(storage->index_dir, "/", name, NULL);
	if (mkdir_parents(dir, CREATE_MODE) < 0 && errno != EEXIST) {
		mail_storage_set_critical(&storage->storage,
					  "mkdir(%s) failed: %m", dir);
		return -1;
	}

	return 0;
}

static int dbox_is_recent(struct index_mailbox *ibox __attr_unused__,
			  uint32_t uid __attr_unused__)
{
	return FALSE;
}

static const char *
dbox_get_index_dir(struct index_storage *storage, const char *name)
{
	const char *p;

	if (storage->index_dir == NULL)
		return NULL;

	if ((storage->storage.flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0 &&
	    (*name == '/' || *name == '~')) {
		name = home_expand(name);
		p = strrchr(name, '/');
		return t_strconcat(t_strdup_until(name, p),
				   "/"DBOX_MAILDIR_NAME"/", p+1, NULL);
	}

	return t_strconcat(storage->index_dir,
			   "/", name, "/"DBOX_MAILDIR_NAME, NULL);
}

static struct mailbox *
dbox_open(struct dbox_storage *storage, const char *name,
	  enum mailbox_open_flags flags)
{
	struct index_storage *istorage = INDEX_STORAGE(storage);
	struct dbox_mailbox *mbox;
	struct mail_index *index;
	const char *path, *index_dir;
	pool_t pool;

	path = dbox_get_path(istorage, name);
	index_dir = dbox_get_index_dir(istorage, name);

	if (create_dbox(istorage, path) < 0)
		return NULL;
	if (create_index_dir(istorage, name) < 0)
		return NULL;

	index = index_storage_alloc(index_dir, path, DBOX_INDEX_PREFIX);

	pool = pool_alloconly_create("mailbox", 1024);
	mbox = p_new(pool, struct dbox_mailbox, 1);
	mbox->ibox.box = dbox_mailbox;
	mbox->ibox.box.pool = pool;
	mbox->ibox.storage = istorage;
	mbox->ibox.mail_vfuncs = &dbox_mail_vfuncs;
	mbox->ibox.is_recent = dbox_is_recent;

	if (index_storage_mailbox_init(&mbox->ibox, index, name, flags) < 0) {
		/* the memory was already freed */
		return NULL;
	}

	mbox->storage = storage;
	mbox->path = p_strdup(pool, path);
	mbox->dbox_file_ext_idx =
		mail_index_ext_register(index, "dbox-seq", 0,
					sizeof(uint32_t), sizeof(uint32_t));
	mbox->dbox_offset_ext_idx =
		mail_index_ext_register(index, "dbox-off", 0,
					sizeof(uint64_t), sizeof(uint64_t));

	mbox->uidlist = dbox_uidlist_init(mbox);
	return &mbox->ibox.box;
}

static struct mailbox *
dbox_mailbox_open(struct mail_storage *_storage, const char *name,
		  struct istream *input, enum mailbox_open_flags flags)
{
	struct dbox_storage *storage = (struct dbox_storage *)_storage;
	struct index_storage *istorage = INDEX_STORAGE(storage);
	const char *path;
	struct stat st;

	mail_storage_clear_error(_storage);

	if (input != NULL) {
		mail_storage_set_critical(_storage,
			"Maildir doesn't support streamed mailboxes");
		return NULL;
	}

	if (strcmp(name, "INBOX") == 0)
		return dbox_open(storage, "INBOX", flags);

	if (!dbox_is_valid_existing_name(_storage, name)) {
		mail_storage_set_error(_storage, "Invalid mailbox name");
		return NULL;
	}

	path = dbox_get_path(istorage, name);
	if (stat(path, &st) == 0) {
		return dbox_open(storage, name, flags);
	} else if (errno == ENOENT) {
		mail_storage_set_error(_storage, "Mailbox doesn't exist: %s",
				       name);
		return NULL;
	} else {
		mail_storage_set_critical(_storage, "stat(%s) failed: %m",
					  path);
		return NULL;
	}
}

static int dbox_mailbox_create(struct mail_storage *_storage,
			       const char *name,
			       int directory __attr_unused__)
{
	struct dbox_storage *storage = (struct dbox_storage *)_storage;
	struct index_storage *istorage = INDEX_STORAGE(storage);
	const char *path, *mail_path;
	struct stat st;

	mail_storage_clear_error(_storage);

	if (!dbox_is_valid_create_name(_storage, name)) {
		mail_storage_set_error(_storage, "Invalid mailbox name");
		return -1;
	}

	path = dbox_get_path(istorage, name);
	mail_path = t_strconcat(path, "/", DBOX_MAILDIR_NAME, NULL);

	if (stat(mail_path, &st) == 0) {
		mail_storage_set_error(_storage, "Mailbox already exists");
		return -1;
	}

	return create_dbox(istorage, path);
}

static int dbox_mailbox_delete(struct mail_storage *_storage,
			       const char *name)
{
	struct dbox_storage *storage = (struct dbox_storage *)_storage;
	struct index_storage *istorage = INDEX_STORAGE(storage);
	const char *path, *mail_path;
	struct stat st;

	mail_storage_clear_error(_storage);

	if (strcmp(name, "INBOX") == 0) {
		mail_storage_set_error(_storage, "INBOX can't be deleted.");
		return -1;
	}

	if (!dbox_is_valid_existing_name(_storage, name)) {
		mail_storage_set_error(_storage, "Invalid mailbox name");
		return -1;
	}

	path = dbox_get_path(istorage, name);
	mail_path = t_strconcat(path, "/", DBOX_MAILDIR_NAME, NULL);

	if (stat(mail_path, &st) < 0 && ENOTFOUND(errno)) {
		if (stat(path, &st) < 0) {
			mail_storage_set_error(_storage,
				"Mailbox doesn't exist: %s", name);
			return -1;
		}

		/* exists as a \NoSelect mailbox */
		if (rmdir(path) == 0)
			return 0;

		if (errno == ENOTEMPTY) {
			mail_storage_set_error(_storage,
				"Mailbox has only submailboxes: %s", name);
		} else {
			mail_storage_set_critical(_storage,
				"rmdir() failed for %s: %m", path);
		}

		return -1;
	}

	if (unlink_directory(mail_path, TRUE) < 0) {
		if (!dbox_handle_errors(istorage)) {
			mail_storage_set_critical(_storage,
				"unlink_directory() failed for %s: %m",
				mail_path);
		}
		return -1;
	}
	/* try also removing the root directory. it can fail if the deleted
	   mailbox had submailboxes. do it as long as we can. */
	while (rmdir(path) == 0) {
		const char *p = strrchr(name, '/');

		if (p == NULL)
			break;

		name = t_strdup_until(name, p);
		path = dbox_get_path(istorage, name);
	}
	return 0;
}

static int dbox_mailbox_rename(struct mail_storage *_storage,
			       const char *oldname, const char *newname)
{
	struct index_storage *storage = (struct index_storage *)_storage;
	const char *oldpath, *newpath, *p;
	struct stat st;

	mail_storage_clear_error(_storage);

	if (!dbox_is_valid_existing_name(_storage, oldname) ||
	    !dbox_is_valid_create_name(_storage, newname)) {
		mail_storage_set_error(_storage, "Invalid mailbox name");
		return -1;
	}

	oldpath = dbox_get_path(storage, oldname);
	newpath = dbox_get_path(storage, newname);

	/* create the hierarchy */
	p = strrchr(newpath, '/');
	if (p != NULL) {
		p = t_strdup_until(newpath, p);
		if (mkdir_parents(p, CREATE_MODE) < 0) {
			if (dbox_handle_errors(storage))
				return -1;

			mail_storage_set_critical(_storage,
				"mkdir_parents(%s) failed: %m", p);
			return -1;
		}
	}

	/* first check that the destination mailbox doesn't exist.
	   this is racy, but we need to be atomic and there's hardly any
	   possibility that someone actually tries to rename two mailboxes
	   to same new one */
	if (lstat(newpath, &st) == 0) {
		mail_storage_set_error(_storage,
				       "Target mailbox already exists");
		return -1;
	} else if (errno == ENOTDIR) {
		mail_storage_set_error(_storage,
			"Target mailbox doesn't allow inferior mailboxes");
		return -1;
	} else if (errno != ENOENT && errno != EACCES) {
		mail_storage_set_critical(_storage, "lstat(%s) failed: %m",
					  newpath);
		return -1;
	}

	/* NOTE: renaming INBOX works just fine with us, it's simply recreated
	   the next time it's needed. */
	if (rename(oldpath, newpath) < 0) {
		if (ENOTFOUND(errno)) {
			mail_storage_set_error(_storage,
				"Mailbox doesn't exist: %s", oldname);
		} else if (!dbox_handle_errors(storage)) {
			mail_storage_set_critical(_storage,
				"rename(%s, %s) failed: %m", oldpath, newpath);
		}
		return -1;
	}

	return 0;
}

static int dbox_set_subscribed(struct mail_storage *_storage,
			       const char *name, int set)
{
	struct dbox_storage *storage = (struct dbox_storage *)_storage;
	const char *path;

	path = t_strconcat(INDEX_STORAGE(storage)->dir,
			   "/" SUBSCRIPTION_FILE_NAME, NULL);

	return subsfile_set_subscribed(_storage, path,
				       INDEX_STORAGE(storage)->temp_prefix,
				       name, set);
}

static int dbox_get_mailbox_name_status(struct mail_storage *_storage,
					const char *name,
					enum mailbox_name_status *status)
{
	struct index_storage *storage = (struct index_storage *)_storage;
	struct stat st;
	const char *path, *mail_path;

	mail_storage_clear_error(_storage);

	if (!dbox_is_valid_existing_name(_storage, name)) {
		*status = MAILBOX_NAME_INVALID;
		return 0;
	}

	path = dbox_get_path(storage, name);
	mail_path = t_strconcat(path, "/", DBOX_MAILDIR_NAME, NULL);

	if (strcmp(name, "INBOX") == 0 || stat(mail_path, &st) == 0) {
		*status = MAILBOX_NAME_EXISTS;
		return 0;
	}

	if (!dbox_is_valid_create_name(_storage, name)) {
		*status = MAILBOX_NAME_INVALID;
		return 0;
	}

	if (errno == ENOENT) {
		*status = MAILBOX_NAME_VALID;
		return 0;
	} else {
		mail_storage_set_critical(_storage, "stat(%s) failed: %m",
					  path);
		return -1;
	}
}

static int dbox_storage_close(struct mailbox *box)
{
        index_storage_mailbox_free(box);
	return 0;
}

static void
dbox_notify_changes(struct mailbox *box, unsigned int min_interval,
		    mailbox_notify_callback_t *callback, void *context)
{
	struct dbox_mailbox *mbox = (struct dbox_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,
		t_strconcat(mbox->path, "/Mails", NULL));
}

struct mail_storage dbox_storage = {
	MEMBER(name) "dbox",
	MEMBER(hierarchy_sep) '/',

	{
		dbox_create,
		dbox_free,
		dbox_autodetect,
		index_storage_set_callbacks,
		dbox_mailbox_open,
		dbox_mailbox_create,
		dbox_mailbox_delete,
		dbox_mailbox_rename,
		dbox_mailbox_list_init,
		dbox_mailbox_list_next,
		dbox_mailbox_list_deinit,
		dbox_set_subscribed,
		dbox_get_mailbox_name_status,
		index_storage_get_last_error
	}
};

struct mailbox dbox_mailbox = {
	MEMBER(name) NULL, 
	MEMBER(storage) NULL, 

	{
		index_storage_is_readonly,
		index_storage_allow_new_keywords,
		dbox_storage_close,
		index_storage_get_status,
		dbox_storage_sync_init,
		index_mailbox_sync_next,
		index_mailbox_sync_deinit,
		dbox_notify_changes,
		dbox_transaction_begin,
		dbox_transaction_commit,
		dbox_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_get_sorting,
		index_storage_search_init,
		index_storage_search_deinit,
		index_storage_search_next,
		dbox_save_init,
		dbox_save_continue,
		dbox_save_finish,
		dbox_save_cancel,
		mail_storage_copy,
		index_storage_is_inconsistent
	}
};

--- NEW FILE: dbox-storage.h ---
#ifndef __DBOX_STORAGE_H
#define __DBOX_STORAGE_H

#define SUBSCRIPTION_FILE_NAME "dovecot.subscriptions"
#define DBOX_INDEX_PREFIX "dovecot.index"
#define DBOX_MAILDIR_NAME "Mails"
#define DBOX_MAIL_FILE_PREFIX "msg."

#include "index-storage.h"

#define STORAGE(mbox_storage) \
	(&(mbox_storage)->storage.storage)
#define INDEX_STORAGE(mbox_storage) \
	(&(mbox_storage)->storage)

struct dbox_uidlist;

struct dbox_file_header {
	unsigned char header_size_hex[8];
	unsigned char append_offset_hex[16];
	unsigned char mail_header_size_hex[4];
	unsigned char mail_header_padding_hex[4];
	unsigned char keyword_count_hex[4];
	/* possible padding to fill header_size */
};

#define DBOX_MAIL_HEADER_MAGIC "\001\003"
struct dbox_mail_header {
	unsigned char magic[2];
	unsigned char uid_hex[8];
	unsigned char mail_size_hex[16];
	unsigned char received_time_hex[8];
	unsigned char answered;
	unsigned char flagged;
	unsigned char deleted;
	unsigned char seen;
	unsigned char draft;
	unsigned char expunged;
	/* unsigned char keywords[]; */
};

struct dbox_storage {
	struct index_storage storage;
};

struct dbox_file {
	uint32_t file_seq;
	char *path;

	int fd;
	struct istream *input;
	struct ostream *output; /* while appending mails */

	uint16_t header_size;
	uint64_t append_offset;
	uint16_t mail_header_size;
	uint16_t mail_header_padding;
	uint16_t keyword_count;

	uoff_t seeked_offset;
	uoff_t seeked_mail_size;
	uint32_t seeked_uid;
        struct dbox_mail_header seeked_mail_header;
};

struct dbox_mailbox {
	struct index_mailbox ibox;
	struct dbox_storage *storage;
	struct dbox_uidlist *uidlist;

	const char *path;

        struct dbox_file *file;
	uint32_t dbox_file_ext_idx;
	uint32_t dbox_offset_ext_idx;
};

struct dbox_transaction_context {
	struct index_transaction_context ictx;

	struct dbox_save_context *save_ctx;
};

extern struct mail_vfuncs dbox_mail_vfuncs;

struct mailbox_list_context *
dbox_mailbox_list_init(struct mail_storage *storage,
		       const char *ref, const char *mask,
		       enum mailbox_list_flags flags);
int dbox_mailbox_list_deinit(struct mailbox_list_context *ctx);
struct mailbox_list *dbox_mailbox_list_next(struct mailbox_list_context *ctx);

struct mailbox_transaction_context *
dbox_transaction_begin(struct mailbox *box,
		       enum mailbox_transaction_flags flags);
int dbox_transaction_commit(struct mailbox_transaction_context *t,
			    enum mailbox_sync_flags flags);
void dbox_transaction_rollback(struct mailbox_transaction_context *t);

struct mail_save_context *
dbox_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,
	       int want_mail);
int dbox_save_continue(struct mail_save_context *ctx);
int dbox_save_finish(struct mail_save_context *ctx, struct mail *dest_mail);
void dbox_save_cancel(struct mail_save_context *ctx);

int dbox_transaction_save_commit_pre(struct dbox_save_context *ctx);
void dbox_transaction_save_commit_post(struct dbox_save_context *ctx);
void dbox_transaction_save_rollback(struct dbox_save_context *ctx);

int dbox_is_valid_mask(struct mail_storage *storage, const char *mask);

int dbox_mail_lookup_offset(struct index_transaction_context *trans,
			    uint32_t seq, uint32_t *file_seq_r,
			    uoff_t *offset_r);

#endif

--- NEW FILE: dbox-sync-expunge.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "istream.h"
#include "ostream.h"
#include "write-full.h"
#include "seq-range-array.h"
#include "dbox-storage.h"
#include "dbox-uidlist.h"
#include "dbox-file.h"
#include "dbox-sync.h"

#include <stddef.h>

static const struct dotlock_settings new_file_dotlock_set = {
	NULL,
	NULL,

	30, 5, 5,

	NULL,
	NULL,

	FALSE
};

static int
dbox_sync_rec_get_uids(struct dbox_sync_context *ctx,
		       const struct dbox_sync_rec *sync_rec,
		       uint32_t *uid1_r, uint32_t *uid2_r)
{
	if (mail_index_lookup_uid(ctx->sync_view, sync_rec->seq1, uid1_r) < 0) {
		mail_storage_set_index_error(&ctx->mbox->ibox);
		return -1;
	}
	if (mail_index_lookup_uid(ctx->sync_view, sync_rec->seq2, uid2_r) < 0) {
		mail_storage_set_index_error(&ctx->mbox->ibox);
		return -1;
	}
	return 0;
}

static int
dbox_next_expunge(struct dbox_sync_context *ctx,
                  const struct dbox_sync_file_entry *sync_entry,
		  unsigned int *sync_idx, uint32_t *uid1_r, uint32_t *uid2_r)
{
	const struct dbox_sync_rec *sync_recs;
	unsigned int count;

	sync_recs = array_get(&sync_entry->sync_recs, &count);

	while (*sync_idx < count) {
		*sync_idx += 1;

		if (sync_recs[*sync_idx].type != MAIL_INDEX_SYNC_TYPE_EXPUNGE)
			continue;

		if (dbox_sync_rec_get_uids(ctx, &sync_recs[*sync_idx],
					   uid1_r, uid2_r) < 0)
			return -1;
		return 1;
	}

	*uid1_r = *uid2_r = 0;
	return 0;
}

static int dbox_sync_expunge_copy(struct dbox_sync_context *ctx,
				  const struct dbox_sync_file_entry *sync_entry,
				  unsigned int sync_idx,
				  uint32_t first_nonexpunged_uid,
                                  struct dbox_uidlist_entry *orig_entry,
				  uoff_t orig_offset)
{
	struct dbox_mailbox *mbox = ctx->mbox;
	struct dotlock *dotlock;
	struct istream *input;
	struct ostream *output;
	struct dbox_file_header hdr;
        struct dbox_uidlist_entry dest_entry;
	const struct dbox_sync_rec *sync_recs;
	const char *path;
	uint32_t file_seq, seq, uid1, uid2;
	unsigned int sync_count;
	int ret, fd;

	ret = dbox_file_seek(mbox, orig_entry->file_seq, orig_offset);
	while (ret > 0) {
		ret = dbox_file_seek_next_nonexpunged(mbox);
		if (ret <= 0)
			break;

		if (mbox->file->seeked_uid >= first_nonexpunged_uid)
			break;
	}
	if (ret < 0)
		return -1;

	sync_recs = array_get(&sync_entry->sync_recs, &sync_count);
	if (sync_idx == sync_count)
		uid1 = uid2 = 0;
	else {
		if (dbox_sync_rec_get_uids(ctx, &sync_recs[sync_idx],
					   &uid1, &uid2) < 0)
			return -1;
	}

	file_seq = dbox_uidlist_get_new_file_seq(mbox->uidlist);

	path = t_strdup_printf("%s/"DBOX_MAILDIR_NAME"/msg.%u",
			       mbox->path, file_seq);
	fd = file_dotlock_open(&new_file_dotlock_set, path, 0, &dotlock);
	output = o_stream_create_file(fd, default_pool, 0, FALSE);

	memset(&dest_entry, 0, sizeof(dest_entry));
	ARRAY_CREATE(&dest_entry.uid_list, pool_datastack_create(),
		     struct seq_range, array_count(&orig_entry->uid_list));
	dest_entry.file_seq = file_seq;

	/* write file header */
	dbox_file_header_init(&hdr);
	ret = o_stream_send(output, &hdr, sizeof(hdr));

	while (ret > 0) {
		/* update mail's location in index */
		uint32_t uid = mbox->file->seeked_uid;
		uint64_t hdr_offset = output->offset;

		if (mail_index_lookup_uid_range(ctx->sync_view, uid, uid,
						&seq, &seq) < 0) {
			mail_storage_set_index_error(&ctx->mbox->ibox);
			ret = -1;
			break;
		}

		if (seq == 0) {
			mail_storage_set_critical(STORAGE(mbox->storage),
				"Expunged UID %u reappeared in file %s",
				uid, path);
			ret = -1;
			break;
		}

		mail_index_update_ext(ctx->trans, seq, mbox->dbox_file_ext_idx,
				      &file_seq, NULL);
		mail_index_update_ext(ctx->trans, seq,
				      mbox->dbox_offset_ext_idx,
				      &hdr_offset, NULL);

		/* copy the mail */
		input = i_stream_create_limit(default_pool, mbox->file->input,
					      mbox->file->seeked_offset,
					      mbox->file->mail_header_size +
					      mbox->file->seeked_mail_size);
		ret = o_stream_send_istream(output, input);
		i_stream_unref(input);

		if (ret < 0) {
			mail_storage_set_critical(STORAGE(mbox->storage),
				"o_stream_send_istream(%s) failed: %m", path);
			break;
		}

		seq_range_array_add(&dest_entry.uid_list, 0,
				    mbox->file->seeked_uid);

		/* seek to next non-expunged mail */
		for (;;) {
			ret = dbox_file_seek_next_nonexpunged(mbox);
			if (ret <= 0)
				break;

			while (mbox->file->seeked_uid > uid2 && uid2 != 0) {
				ret = dbox_next_expunge(ctx, sync_entry,
							&sync_idx,
							&uid1, &uid2);
				if (ret <= 0)
					break;
			}
			if (ret <= 0)
				break;

			if (mbox->file->seeked_uid < uid1 || uid1 == 0)
				break;
		}
	}
	o_stream_unref(output);

	if (ret < 0) {
		file_dotlock_delete(&dotlock);
		return -1;
	} else {
		if (file_dotlock_replace(&dotlock, 0) < 0)
			return -1;

		/* new file created successfully. append it to uidlist. */
		dbox_uidlist_sync_append(ctx->uidlist_sync_ctx, &dest_entry);
		return 0;
	}
}

static int dbox_sync_expunge_file(struct dbox_sync_context *ctx,
				  const struct dbox_sync_file_entry *sync_entry,
				  unsigned int sync_idx)
{
	struct dbox_mailbox *mbox = ctx->mbox;
	const struct dbox_sync_rec *sync_recs;
	struct dbox_uidlist_entry *entry;
        struct seq_range *range;
	unsigned int i, count, sync_count;
	uint32_t file_seq, uid, uid1, uid2, first_expunged_uid;
	uoff_t offset;
	int ret, seen_expunges, skipped_expunges;

	sync_recs = array_get(&sync_entry->sync_recs, &sync_count);
	if (dbox_sync_get_file_offset(ctx, sync_recs[sync_idx].seq1,
				      &file_seq, &offset) < 0)
		return -1;

	entry = dbox_uidlist_entry_lookup(mbox->uidlist, sync_entry->file_seq);
	if (entry == NULL) {
		/* file is already unlinked. just remove from index. */
		return 0;
	}

	if (dbox_sync_rec_get_uids(ctx, &sync_recs[sync_idx], &uid1, &uid2) < 0)
		return -1;

	/* find the first non-expunged mail */
	first_expunged_uid = uid1;
	seen_expunges = FALSE; skipped_expunges = FALSE; uid = 0;
	range = array_get_modifyable(&entry->uid_list, &count);
	for (i = 0; i < count; i++) {
		uid = range[i].seq1;

		if (!seen_expunges) {
			if (uid != first_expunged_uid) {
				/* range begins with non-expunged messages */
				i_assert(uid < first_expunged_uid);
				uid = first_expunged_uid;
				skipped_expunges = TRUE;
			}
		}

		while (uid <= range[i].seq2) {
			if (uid < uid1) {
				/* non-expunged mails exist in this file */
				break;
			}
			seen_expunges = TRUE;

			if (range[i].seq2 < uid2) {
				/* fully used up this uid range */
				uid = range[i].seq2 + 1;
				break;
			}

			/* this sync_rec was fully used. look up the next.
			   range[] doesn't contain non-existing UIDs, so
			   uid2+1 should exist in it. */
			uid = uid2 + 1;

			ret = dbox_next_expunge(ctx, sync_entry, &sync_idx,
						&uid1, &uid2);
			if (ret <= 0) {
				if (ret < 0)
					return -1;
				/* end of sync records */
				break;
			}
		}
		if (uid <= range[i].seq2) {
			/* non-expunged mails exist in this file */
			break;
		}
	}

	if (i != count) {
		/* mails expunged from the middle. have to copy everything
		   after the first expunged mail to new file. after copying
		   we'll truncate/unlink the old file. */
		if (dbox_sync_expunge_copy(ctx, sync_entry, sync_idx,
					   uid, entry, offset) < 0)
			return -1;
		i++;
	}

	if (!skipped_expunges) {
		/* all mails expunged from file, unlink it. */
		return dbox_uidlist_sync_unlink(ctx->uidlist_sync_ctx,
						entry->file_seq);
	}

	/* mails expunged from the end of file, ftruncate() it */

	ret = dbox_file_seek(mbox, entry->file_seq, offset);
	if (ret <= 0) {
		if (ret < 0)
			return -1;

		/* unexpected EOF -> already truncated */
	} else {
		if (ftruncate(mbox->file->fd, offset) < 0) {
			mail_storage_set_critical(STORAGE(mbox->storage),
				"ftruncate(%s) failed: %m", mbox->path);
			return -1;
		}
	}

	/* remove from uidlist entry */
	for (; i > 0; i--) {
		if (range[i-1].seq1 < first_expunged_uid)
			break;
	}
	array_delete(&entry->uid_list, i, count-i);
	if (i > 0)
		range[i-1].seq2 = first_expunged_uid-1;

	dbox_uidlist_sync_set_modified(ctx->uidlist_sync_ctx);
	return 0;
}

static int
dbox_sync_expunge_mark_flags(struct dbox_sync_context *ctx,
			     const struct dbox_sync_file_entry *entry,
			     unsigned int sync_idx)
{
	struct dbox_mailbox *mbox = ctx->mbox;
	const struct dbox_sync_rec *sync_rec;
	unsigned char expunged_flag = '1';
	uint32_t file_seq, uid2;
	uoff_t offset;
	int ret;

	sync_rec = array_idx(&entry->sync_recs, sync_idx);
	if (dbox_sync_get_file_offset(ctx, sync_rec->seq1,
				      &file_seq, &offset) < 0)
		return -1;

	if (mail_index_lookup_uid(ctx->sync_view, sync_rec->seq2, &uid2) < 0) {
		mail_storage_set_index_error(&ctx->mbox->ibox);
		return -1;
	}

	while (mbox->file->seeked_uid <= uid2) {
		ret = pwrite_full(ctx->mbox->file->fd, &expunged_flag, 1,
				  offset + offsetof(struct dbox_mail_header,
						    expunged));
		if (ret < 0) {
			mail_storage_set_critical(STORAGE(mbox->storage),
				"pwrite(%s) failed: %m", mbox->path);
			return -1;
		}

		ret = dbox_file_seek_next_nonexpunged(mbox);
		if (ret <= 0) {
			if (ret == 0)
				break;
			return -1;
		}
	}
	return 0;
}

int dbox_sync_expunge(struct dbox_sync_context *ctx,
		      const struct dbox_sync_file_entry *entry,
		      unsigned int sync_idx)
{
	struct dotlock *dotlock;
	const char *path;
	int ret;

	if (ctx->dotlock_failed_file_seq != entry->file_seq) {
		/* we need to have the file locked in case another process is
		   appending there already. */
		path = t_strdup_printf("%s/"DBOX_MAILDIR_NAME"/msg.%u",
				       ctx->mbox->path, entry->file_seq);
		ret = file_dotlock_create(&new_file_dotlock_set, path,
					  DOTLOCK_CREATE_FLAG_NONBLOCK,
					  &dotlock);
		if (ret < 0)
			return -1;

		if (ret > 0) {
			/* locked - copy the non-expunged mails after the
			   expunged mail to new file */
			ret = dbox_sync_expunge_file(ctx, entry, sync_idx);
			file_dotlock_delete(&dotlock);
			return ret < 0 ? -1 : 1;
		}

		/* remember that we failed, so we don't waste time trying to
		   lock the file multiple times within same sync. */
		ctx->dotlock_failed_file_seq = entry->file_seq;
	}

	/* couldn't lock it, someone's appending. we have no other
	   choice but to just mark the mail expunged. otherwise we'd
	   deadlock (appending process waits for uidlist lock which
	   we have, we wait for file lock which append process has) */
	return dbox_sync_expunge_mark_flags(ctx, entry, sync_idx);
}

--- NEW FILE: dbox-sync.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "hash.h"
#include "dbox-file.h"
#include "dbox-sync.h"
#include "dbox-uidlist.h"
#include "dbox-storage.h"

#include <stddef.h>

int dbox_sync_get_file_offset(struct dbox_sync_context *ctx, uint32_t seq,
			      uint32_t *file_seq_r, uoff_t *offset_r)
{
	int ret;

	ret = dbox_file_lookup_offset(ctx->mbox, ctx->sync_view, seq,
				      file_seq_r, offset_r);
	if (ret <= 0) {
		if (ret == 0) {
			mail_storage_set_critical(STORAGE(ctx->mbox->storage),
				"Unexpectedly lost seq %u in "
				"dbox file %s", seq, ctx->mbox->path);
		}
		return -1;
	}
	return 0;
}

static int dbox_sync_add_seq(struct dbox_sync_context *ctx, uint32_t seq,
                             const struct dbox_sync_rec *sync_rec)
{
        struct dbox_sync_file_entry *entry;
	uint32_t file_seq;
	uoff_t offset;

	if (dbox_sync_get_file_offset(ctx, seq, &file_seq, &offset) < 0)
		return -1;

	if (ctx->prev_file_seq == file_seq)
		return 0; /* already added in last sequence */
	ctx->prev_file_seq = file_seq;

	entry = hash_lookup(ctx->syncs, POINTER_CAST(file_seq));
	if (entry != NULL) {
		/* check if it's already added */
		const struct dbox_sync_rec *sync_recs;
		unsigned int count;

		sync_recs = array_get(&entry->sync_recs, &count);
		i_assert(count > 0);
		if (memcmp(&sync_recs[count-1],
			   sync_rec, sizeof(*sync_rec)) == 0)
			return 0; /* already added */
	} else {
		entry = p_new(ctx->pool, struct dbox_sync_file_entry, 1);
		entry->file_seq = file_seq;
		ARRAY_CREATE(&entry->sync_recs, ctx->pool,
			     struct dbox_sync_rec, 3);
		hash_insert(ctx->syncs, POINTER_CAST(file_seq), entry);
	}

	array_append(&entry->sync_recs, sync_rec, 1);
	return 0;
}

static int dbox_sync_add(struct dbox_sync_context *ctx,
			 const struct mail_index_sync_rec *sync_rec)
{
        struct dbox_sync_rec dbox_sync_rec;
	uint32_t seq, seq1, seq2;

	if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_APPEND) {
		/* don't care about appends */
		return 0;
	}

	if (mail_index_lookup_uid_range(ctx->sync_view,
					sync_rec->uid1, sync_rec->uid2,
					&seq1, &seq2) < 0)
		return -1;

	if (seq1 == 0) {
		/* already expunged everything. nothing to do. */
		return 0;
	}

	/* convert to dbox_sync_rec, which takes a bit less space and has
	   sequences instead of UIDs. */
	memset(&dbox_sync_rec, 0, sizeof(dbox_sync_rec));
	dbox_sync_rec.type = sync_rec->type;
	dbox_sync_rec.seq1 = seq1;
	dbox_sync_rec.seq2 = seq2;
	switch (sync_rec->type) {
	case MAIL_INDEX_SYNC_TYPE_FLAGS:
		dbox_sync_rec.value.flags.add = sync_rec->add_flags;
		dbox_sync_rec.value.flags.remove = sync_rec->remove_flags;
		break;
	case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
	case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
	case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
		dbox_sync_rec.value.keyword_idx = sync_rec->keyword_idx;
		break;
	case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
	case MAIL_INDEX_SYNC_TYPE_APPEND:
		break;
	}

	/* now, add the same sync_rec to each file_seq's entry */
	ctx->prev_file_seq = 0;
	for (seq = seq1; seq <= seq2; seq++) {
		if (dbox_sync_add_seq(ctx, seq, &dbox_sync_rec) < 0)
			return -1;
	}
	return 0;
}

static int dbox_sync_file(struct dbox_sync_context *ctx,
                          const struct dbox_sync_file_entry *entry)
{
	const struct dbox_sync_rec *sync_recs;
	unsigned int i, count;
	int ret;

	sync_recs = array_get(&entry->sync_recs, &count);
	for (i = 0; i < count; i++) {
		switch (sync_recs[i].type) {
		case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
			ret = dbox_sync_expunge(ctx, entry, i);
			if (ret > 0) {
				/* handled expunging by copying the file.
				   while at it, also wrote all the other sync
				   changes to the file. */
				return 0;
			}
			if (ret < 0)
				return -1;
			/* handled expunging by writing expunge flags */
			break;
		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:
			/* FIXME */
			break;
		case MAIL_INDEX_SYNC_TYPE_APPEND:
			i_unreached();
		}
	}
	return 0;
}

static int dbox_sync_index(struct dbox_mailbox *mbox)
{
	struct dbox_sync_context ctx;
	struct mail_index_sync_rec sync_rec;
        struct hash_iterate_context *iter;
	const struct mail_index_header *hdr;
	void *key, *value;
	uint32_t seq, uid_validity, next_uid;
	uoff_t offset;
	time_t mtime;
	int ret;

	memset(&ctx, 0, sizeof(ctx));
	ctx.mbox = mbox;

	/* always start index syncing before uidlist, so we don't get
	   deadlocks */
	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);
		return ret;
	}
	if (dbox_uidlist_sync_init(mbox->uidlist, &ctx.uidlist_sync_ctx) < 0) {
		mail_index_sync_rollback(ctx.index_sync_ctx);
		return -1;
	}

	ctx.trans = mail_index_transaction_begin(ctx.sync_view, FALSE, TRUE);

	/* read all changes and sort them to file_seq order */
	ctx.pool = pool_alloconly_create("dbox sync pool", 10240);
	ctx.syncs = hash_create(default_pool, ctx.pool, 0, NULL, NULL);
	while ((ret = mail_index_sync_next(ctx.index_sync_ctx,
					   &sync_rec)) > 0) {
		if (dbox_sync_add(&ctx, &sync_rec) < 0) {
			ret = -1;
			break;
		}
	}

	iter = hash_iterate_init(ctx.syncs);
	while (hash_iterate(iter, &key, &value)) {
                const struct dbox_sync_file_entry *entry = value;

		if (dbox_sync_file(&ctx, entry) < 0) {
			ret = -1;
			break;
		}
	}
	hash_iterate_deinit(iter);

	hash_destroy(ctx.syncs);
	pool_unref(ctx.pool);

	if (ret < 0) {
		mail_storage_set_index_error(&mbox->ibox);
		mail_index_sync_rollback(ctx.index_sync_ctx);
		dbox_uidlist_sync_rollback(ctx.uidlist_sync_ctx);
		return -1;
	}

	uid_validity = dbox_uidlist_sync_get_uid_validity(ctx.uidlist_sync_ctx);
	next_uid = dbox_uidlist_sync_get_next_uid(ctx.uidlist_sync_ctx);

	hdr = mail_index_get_header(ctx.sync_view);
	if (hdr->uid_validity != uid_validity) {
		mail_index_update_header(ctx.trans,
			offsetof(struct mail_index_header, uid_validity),
			&uid_validity, sizeof(uid_validity), TRUE);
	}
	if (hdr->next_uid != next_uid) {
		mail_index_update_header(ctx.trans,
			offsetof(struct mail_index_header, next_uid),
			&next_uid, sizeof(next_uid), TRUE);
	}

	if (dbox_uidlist_sync_commit(ctx.uidlist_sync_ctx, &mtime) < 0) {
		mail_index_sync_rollback(ctx.index_sync_ctx);
		return -1;
	}

	if ((uint32_t)mtime != hdr->sync_stamp) {
		uint32_t sync_stamp = mtime;

		mail_index_update_header(ctx.trans,
			offsetof(struct mail_index_header, sync_stamp),
			&sync_stamp, sizeof(sync_stamp), TRUE);
	}

	if (mail_index_transaction_commit(ctx.trans, &seq, &offset) < 0) {
		mail_storage_set_index_error(&mbox->ibox);
		mail_index_sync_rollback(ctx.index_sync_ctx);
		return -1;
	}

	if (mail_index_sync_commit(ctx.index_sync_ctx) < 0) {
		mail_storage_set_index_error(&mbox->ibox);
		return -1;
	}
	return 0;
}

int dbox_sync(struct dbox_mailbox *mbox, int force)
{
	if (!force) {
		/* just sync index */
		return dbox_sync_index(mbox);
	}

	return -1;
}

struct mailbox_sync_context *
dbox_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
{
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)box;
	int ret = 0;

	if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 ||
	    mbox->ibox.sync_last_check + MAILBOX_FULL_SYNC_INTERVAL <=
	    ioloop_time)
		ret = dbox_sync(mbox, FALSE);

	return index_mailbox_sync_init(box, flags, ret < 0);
}

int dbox_sync_if_changed(struct dbox_mailbox *mbox)
{
	const struct mail_index_header *hdr;
	time_t mtime;

	hdr = mail_index_get_header(mbox->ibox.view);
	if (hdr->sync_stamp == 0)
		return 1;

	if (dbox_uidlist_get_mtime(mbox->uidlist, &mtime) < 0)
		return -1;

	return (uint32_t)mtime == hdr->sync_stamp;
}

--- NEW FILE: dbox-sync.h ---
#ifndef __DBOX_SYNC_H
#define __DBOX_SYNC_H

#include "mail-index.h"

struct mailbox;
struct dbox_mailbox;
enum mailbox_sync_flags;

struct dbox_sync_rec {
	uint32_t seq1, seq2;
	enum mail_index_sync_type type;

	union {
		/* MAIL_INDEX_SYNC_TYPE_FLAGS: */
		struct {
			uint8_t add;
			uint8_t remove;
		} flags;

		/* MAIL_INDEX_SYNC_TYPE_KEYWORD_*: */
		unsigned int keyword_idx;
	} value;
};

struct dbox_sync_file_entry {
	uint32_t file_seq;

	array_t ARRAY_DEFINE(sync_recs, struct dbox_sync_rec);
};

struct dbox_sync_context {
	struct dbox_mailbox *mbox;
	struct dbox_uidlist_sync_ctx *uidlist_sync_ctx;
        struct mail_index_sync_ctx *index_sync_ctx;
	struct mail_index_view *sync_view;
	struct mail_index_transaction *trans;

	pool_t pool;
	struct hash_table *syncs; /* struct dbox_sync_file_entry */
	uint32_t prev_file_seq;

	uint32_t dotlock_failed_file_seq;
};

int dbox_sync(struct dbox_mailbox *mbox, int force);
int dbox_sync_if_changed(struct dbox_mailbox *mbox);

struct mailbox_sync_context *
dbox_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);

int dbox_sync_get_file_offset(struct dbox_sync_context *ctx, uint32_t seq,
			      uint32_t *file_seq_r, uoff_t *offset_r);

int dbox_sync_expunge(struct dbox_sync_context *ctx,
		      const struct dbox_sync_file_entry *entry,
                      unsigned int sync_idx);

#endif

--- NEW FILE: dbox-transaction.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "dbox-sync.h"
#include "dbox-storage.h"

struct mailbox_transaction_context *
dbox_transaction_begin(struct mailbox *box,
		       enum mailbox_transaction_flags flags)
{
	struct dbox_mailbox *dbox = (struct dbox_mailbox *)box;
	struct dbox_transaction_context *t;

	t = i_new(struct dbox_transaction_context, 1);
	index_transaction_init(&t->ictx, &dbox->ibox, flags);
	return &t->ictx.mailbox_ctx;
}

int dbox_transaction_commit(struct mailbox_transaction_context *_t,
			    enum mailbox_sync_flags flags)
{
	struct dbox_transaction_context *t =
		(struct dbox_transaction_context *)_t;
	struct dbox_mailbox *dbox = (struct dbox_mailbox *)t->ictx.ibox;
	struct dbox_save_context *save_ctx;
	int ret = 0;

	if (t->save_ctx != NULL) {
		if (dbox_transaction_save_commit_pre(t->save_ctx) < 0) {
			t->save_ctx = NULL;
			ret = -1;
		}
	}

	save_ctx = t->save_ctx;

	if (ret == 0) {
		if (index_transaction_commit(_t) < 0)
			ret = -1;
	} else {
		index_transaction_rollback(_t);
	}
	/* transaction is destroyed. */
	t = NULL; _t = NULL;

	if (save_ctx != NULL) {
		/* unlock uidlist file after writing to transaction log,
		   to make sure we don't write uids in wrong order. */
		dbox_transaction_save_commit_post(save_ctx);
	}

#if 0
	if (lock_id != 0 && dbox->dbox_lock_type != F_WRLCK) {
		/* unlock before writing any changes */
		(void)dbox_unlock(dbox, lock_id);
		lock_id = 0;
	}
#endif
	if (ret == 0) {
		if (dbox_sync(dbox, FALSE) < 0)
			ret = -1;
	}

#if 0
	if (lock_id != 0) {
		if (dbox_unlock(dbox, lock_id) < 0)
			ret = -1;
	}
#endif
	return ret;
}

void dbox_transaction_rollback(struct mailbox_transaction_context *_t)
{
	struct dbox_transaction_context *t =
		(struct dbox_transaction_context *)_t;
	struct dbox_mailbox *dbox = (struct dbox_mailbox *)t->ictx.ibox;

	if (t->save_ctx != NULL)
		dbox_transaction_save_rollback(t->save_ctx);

	/*if (t->dbox_lock_id != 0)
		(void)dbox_unlock(dbox, t->dbox_lock_id);*/
	index_transaction_rollback(_t);
}

--- NEW FILE: dbox-uidlist.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "hex-dec.h"
#include "array.h"
#include "seq-range-array.h"
#include "str.h"
#include "istream.h"
#include "ostream.h"
#include "write-full.h"
#include "dbox-file.h"
#include "dbox-storage.h"
#include "dbox-uidlist.h"

#include <stddef.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <utime.h>
[...1101 lines suppressed...]
}

int dbox_uidlist_get_mtime(struct dbox_uidlist *uidlist, time_t *mtime_r)
{
	struct stat st;

	if (stat(uidlist->path, &st) < 0) {
		if (errno != ENOENT) {
			mail_storage_set_critical(
				STORAGE(uidlist->mbox->storage),
				"stat(%s) failed: %m", uidlist->path);
			return -1;
		}

		*mtime_r = 0;
	} else {
		*mtime_r = st.st_mtime;
	}
	return 0;
}

--- NEW FILE: dbox-uidlist.h ---
#ifndef __DBOX_UIDLIST_H
#define __DBOX_UIDLIST_H

struct dbox_file;
struct dbox_mailbox;
struct dbox_uidlist_sync_ctx;

struct dbox_uidlist_entry {
	array_t ARRAY_DEFINE(uid_list, struct seq_range);

	uint32_t file_seq;
	time_t last_write;
	uoff_t file_size;
};

struct dbox_uidlist *dbox_uidlist_init(struct dbox_mailbox *mbox);
void dbox_uidlist_deinit(struct dbox_uidlist *uidlist);

struct dbox_uidlist_entry *
dbox_uidlist_entry_lookup(struct dbox_uidlist *uidlist, uint32_t file_seq);

struct dbox_uidlist_append_ctx *
dbox_uidlist_append_init(struct dbox_uidlist *uidlist);
int dbox_uidlist_append_commit(struct dbox_uidlist_append_ctx *ctx);
void dbox_uidlist_append_rollback(struct dbox_uidlist_append_ctx *ctx);

/* Open/create a file for appending a new message and lock it.
   Returns -1 if failed, 0 if ok. If new file is created, the file's header is
   already appended. */
int dbox_uidlist_append_locked(struct dbox_uidlist_append_ctx *ctx,
			       struct dbox_file **file_r);
void dbox_uidlist_append_finish_mail(struct dbox_uidlist_append_ctx *ctx,
				     struct dbox_file *file);

struct dbox_file *
dbox_uidlist_append_lookup_file(struct dbox_uidlist_append_ctx *ctx,
				uint32_t file_seq);

uint32_t dbox_uidlist_get_new_file_seq(struct dbox_uidlist *uidlist);
int dbox_uidlist_append_get_first_uid(struct dbox_uidlist_append_ctx *ctx,
				      uint32_t *uid_r);

int dbox_uidlist_sync_init(struct dbox_uidlist *uidlist,
			   struct dbox_uidlist_sync_ctx **ctx_r);
int dbox_uidlist_sync_commit(struct dbox_uidlist_sync_ctx *ctx,
			     time_t *mtime_r);
void dbox_uidlist_sync_rollback(struct dbox_uidlist_sync_ctx *ctx);

void dbox_uidlist_sync_set_modified(struct dbox_uidlist_sync_ctx *ctx);
void dbox_uidlist_sync_append(struct dbox_uidlist_sync_ctx *ctx,
			      const struct dbox_uidlist_entry *entry);
int dbox_uidlist_sync_unlink(struct dbox_uidlist_sync_ctx *ctx,
			     uint32_t file_seq);

uint32_t dbox_uidlist_sync_get_uid_validity(struct dbox_uidlist_sync_ctx *ctx);
uint32_t dbox_uidlist_sync_get_next_uid(struct dbox_uidlist_sync_ctx *ctx);

int dbox_uidlist_get_mtime(struct dbox_uidlist *uidlist, time_t *mtime_r);

#endif



More information about the dovecot-cvs mailing list