view src/lib-storage/mail-storage.c @ 12586:a2780b694b2d

lib-storage: mailbox_alloc() now takes a virtual mailbox name and other related API changes. All storage_name <-> vname conversions now go through the same two mailbox_list methods. This has many benefits, such as: * listescape plugin is now much simpler and bugfree * allows changing lib-storage API to use UTF-8 mailbox names in future * allows creation of "mailbox aliases" plugin
author Timo Sirainen <tss@iki.fi>
date Thu, 20 Jan 2011 20:59:07 +0200
parents b748c622e896
children aa131065b53d
line wrap: on
line source

/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "llist.h"
#include "istream.h"
#include "eacces-error.h"
#include "mkdir-parents.h"
#include "var-expand.h"
#include "mail-index-private.h"
#include "mail-index-alloc-cache.h"
#include "mailbox-list-private.h"
#include "mail-storage-private.h"
#include "mail-storage-settings.h"
#include "mail-namespace.h"
#include "mail-search.h"
#include "mail-search-register.h"
#include "mailbox-search-result-private.h"

#include <stdlib.h>
#include <ctype.h>

#define MAILBOX_DELETE_RETRY_SECS (60*5)

extern struct mail_search_register *mail_search_register_imap;
extern struct mail_search_register *mail_search_register_human;

struct mail_storage_module_register mail_storage_module_register = { 0 };
struct mail_module_register mail_module_register = { 0 };

struct mail_storage_mail_index_module mail_storage_mail_index_module =
	MODULE_CONTEXT_INIT(&mail_index_module_register);
ARRAY_TYPE(mail_storage) mail_storage_classes;

void mail_storage_init(void)
{
	mailbox_lists_init();
	mail_storage_hooks_init();
	i_array_init(&mail_storage_classes, 8);
}

void mail_storage_deinit(void)
{
	if (mail_search_register_human != NULL)
		mail_search_register_deinit(&mail_search_register_human);
	if (mail_search_register_imap != NULL)
		mail_search_register_deinit(&mail_search_register_imap);
	if (array_is_created(&mail_storage_classes))
		array_free(&mail_storage_classes);
	mail_storage_hooks_deinit();
	mailbox_lists_deinit();
}

void mail_storage_class_register(struct mail_storage *storage_class)
{
	i_assert(mail_storage_find_class(storage_class->name) == NULL);

	/* append it after the list, so the autodetection order is correct */
	array_append(&mail_storage_classes, &storage_class, 1);
}

void mail_storage_class_unregister(struct mail_storage *storage_class)
{
	struct mail_storage *const *classes;
	unsigned int i, count;

	classes = array_get(&mail_storage_classes, &count);
	for (i = 0; i < count; i++) {
		if (classes[i] == storage_class) {
			array_delete(&mail_storage_classes, i, 1);
			break;
		}
	}
}

struct mail_storage *mail_storage_find_class(const char *name)
{
	struct mail_storage *const *classes;
	unsigned int i, count;

	i_assert(name != NULL);

	classes = array_get(&mail_storage_classes, &count);
	for (i = 0; i < count; i++) {
		if (strcasecmp(classes[i]->name, name) == 0)
			return classes[i];
	}
	return NULL;
}

static struct mail_storage *
mail_storage_autodetect(const struct mail_namespace *ns,
			struct mailbox_list_settings *set)
{
	struct mail_storage *const *classes;
	unsigned int i, count;

	classes = array_get(&mail_storage_classes, &count);
	for (i = 0; i < count; i++) {
		if (classes[i]->v.autodetect != NULL) {
			if (classes[i]->v.autodetect(ns, set))
				return classes[i];
		}
	}
	return NULL;
}

static void
mail_storage_set_autodetection(const char **data, const char **driver)
{
	const char *p;

	/* check if data is in driver:data format (eg. mbox:~/mail) */
	p = *data;
	while (i_isalnum(*p)) p++;

	if (*p == ':' && p != *data) {
		/* no autodetection if the storage driver is given. */
		*driver = t_strdup_until(*data, p);
		*data = p + 1;
	}
}

static struct mail_storage *
mail_storage_get_class(struct mail_namespace *ns, const char *driver,
		       struct mailbox_list_settings *list_set,
		       enum mail_storage_flags flags, const char **error_r)
{
	struct mail_storage *storage_class = NULL;
	const char *home;

	if (driver != NULL) {
		storage_class = mail_storage_find_class(driver);
		if (storage_class == NULL) {
			*error_r = t_strdup_printf(
				"Unknown mail storage driver %s", driver);
			return NULL;
		}
	}

	if (list_set->root_dir == NULL || *list_set->root_dir == '\0') {
		/* no root directory given. is this allowed? */
		const struct mailbox_list *list;

		list = list_set->layout == NULL ? NULL :
			mailbox_list_find_class(list_set->layout);
		if (storage_class == NULL &&
		    (flags & MAIL_STORAGE_FLAG_NO_AUTODETECTION) == 0) {
			/* autodetection should take care of this */
		} else if (list != NULL &&
			   (list->props & MAILBOX_LIST_PROP_NO_ROOT) != 0) {
			/* root not required for this layout */
		} else {
			*error_r = "Root mail directory not given";
			return NULL;
		}
	}

	if (storage_class != NULL) {
		storage_class->v.get_list_settings(ns, list_set);
		return storage_class;
	}

	storage_class = mail_storage_autodetect(ns, list_set);
	if (storage_class != NULL)
		return storage_class;

	if (ns->set->location == NULL || *ns->set->location == '\0') {
		(void)mail_user_get_home(ns->user, &home);
		if (home == NULL || *home == '\0') home = "(not set)";

		*error_r = t_strdup_printf(
			"Mail storage autodetection failed with home=%s", home);
	} else {
		*error_r = t_strdup_printf(
			"Ambiguous mail location setting, "
			"don't know what to do with it: %s "
			"(try prefixing it with mbox: or maildir:)",
			ns->set->location);
	}
	return NULL;
}

static int
mail_storage_verify_root(const char *root_dir, bool autocreate,
			 const char **error_r)
{
	struct stat st;

	if (stat(root_dir, &st) == 0) {
		/* exists */
		return 1;
	} else if (errno == EACCES) {
		*error_r = mail_error_eacces_msg("stat", root_dir);
		return -1;
	} else if (errno != ENOENT && errno != ENOTDIR) {
		*error_r = t_strdup_printf("stat(%s) failed: %m", root_dir);
		return -1;
	} else if (!autocreate) {
		*error_r = t_strdup_printf(
			"Root mail directory doesn't exist: %s", root_dir);
		return -1;
	} else {
		/* doesn't exist */
		return 0;
	}
}

static int
mail_storage_create_root(struct mailbox_list *list,
			 enum mail_storage_flags flags, const char **error_r)
{
	const char *root_dir, *origin, *error;
	mode_t mode;
	gid_t gid;
	bool autocreate;
	int ret;

	root_dir = mailbox_list_get_path(list, NULL,
					 MAILBOX_LIST_PATH_TYPE_MAILBOX);
	if (root_dir == NULL) {
		/* storage doesn't use directories (e.g. shared root) */
		return 0;
	}

	if ((flags & MAIL_STORAGE_FLAG_NO_AUTOVERIFY) != 0) {
		if (!list->mail_set->mail_debug)
			return 0;

		/* we don't need to verify, but since debugging is
		   enabled, check and log if the root doesn't exist */
		if (mail_storage_verify_root(root_dir, FALSE, &error) < 0) {
			i_debug("Namespace %s: Creating storage despite: %s",
				list->ns->prefix, error);
		}
		return 0;
	}

	autocreate = (flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) == 0;
	ret = mail_storage_verify_root(root_dir, autocreate, error_r);
	if (ret != 0)
		return ret;

	/* we need to create the root directory. */
	mailbox_list_get_root_dir_permissions(list, &mode, &gid, &origin);
	if (mkdir_parents_chgrp(root_dir, mode, gid, origin) < 0 &&
	    errno != EEXIST) {
		*error_r = mail_error_create_eacces_msg("mkdir", root_dir);
		return -1;
	} else {
		/* created */
		return 0;
	}
}

static struct mail_storage *
mail_storage_find(struct mail_user *user,
		  const struct mail_storage *storage_class,
		  const struct mailbox_list_settings *set)
{
	struct mail_storage *storage = user->storages;

	for (; storage != NULL; storage = storage->next) {
		if (strcmp(storage->name, storage_class->name) == 0 &&
		    ((storage->class_flags &
		      MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) == 0 ||
		     strcmp(storage->unique_root_dir, set->root_dir) == 0))
			return storage;
	}
	return NULL;
}

int mail_storage_create(struct mail_namespace *ns, const char *driver,
			enum mail_storage_flags flags, const char **error_r)
{
	struct mail_storage *storage_class, *storage = NULL;
	struct mailbox_list_settings list_set;
	enum mailbox_list_flags list_flags = 0;
	const char *data = ns->set->location;
	const char *p;

	if ((flags & MAIL_STORAGE_FLAG_KEEP_HEADER_MD5) == 0 &&
	    ns->mail_set->pop3_uidl_format != NULL) {
		/* if pop3_uidl_format contains %m, we want to keep the
		   header MD5 sums stored even if we're not running POP3
		   right now. */
		p = ns->mail_set->pop3_uidl_format;
		while ((p = strchr(p, '%')) != NULL) {
			if (p[1] == '%')
				p += 2;
			else if (var_get_key(++p) == 'm') {
				flags |= MAIL_STORAGE_FLAG_KEEP_HEADER_MD5;
				break;
			}
		}
	}

	memset(&list_set, 0, sizeof(list_set));
	if (data == NULL) {
		/* autodetect */
	} else if (driver != NULL && strcmp(driver, "shared") == 0) {
		/* internal shared namespace */
		list_set.root_dir = ns->user->set->base_dir;
	} else {
		if (driver == NULL)
			mail_storage_set_autodetection(&data, &driver);
		if (mailbox_list_settings_parse(ns->user, data, &list_set,
						error_r) < 0)
			return -1;
	}

	storage_class = mail_storage_get_class(ns, driver, &list_set, flags,
					       error_r);
	if (storage_class == NULL)
		return -1;
	i_assert(list_set.layout != NULL);

	if (ns->list == NULL) {
		/* first storage for namespace */
		if (mail_storage_is_mailbox_file(storage_class))
			list_flags |= MAILBOX_LIST_FLAG_MAILBOX_FILES;
		if (mailbox_list_create(list_set.layout, ns, &list_set,
					list_flags, error_r) < 0) {
			*error_r = t_strdup_printf("Mailbox list driver %s: %s",
						   list_set.layout, *error_r);
			return -1;
		}
		if (mail_storage_create_root(ns->list, flags, error_r) < 0)
			return -1;
	}

	storage = mail_storage_find(ns->user, storage_class, &list_set);
	if (storage != NULL) {
		/* using an existing storage */
		storage->refcount++;
		mail_namespace_add_storage(ns, storage);
		return 0;
	}

	storage = storage_class->v.alloc();
	storage->refcount = 1;
	storage->storage_class = storage_class;
	storage->user = ns->user;
	storage->set = ns->mail_set;
	storage->flags = flags;
	p_array_init(&storage->module_contexts, storage->pool, 5);

	if (storage->v.create != NULL &&
	    storage->v.create(storage, ns, error_r) < 0) {
		*error_r = t_strdup_printf("%s: %s", storage->name, *error_r);
		pool_unref(&storage->pool);
		return -1;
	}

	T_BEGIN {
		hook_mail_storage_created(storage);
	} T_END;

	DLLIST_PREPEND(&ns->user->storages, storage);
        mail_namespace_add_storage(ns, storage);
	return 0;
}

void mail_storage_unref(struct mail_storage **_storage)
{
	struct mail_storage *storage = *_storage;

	i_assert(storage->refcount > 0);

	/* set *_storage=NULL only after calling destroy() callback.
	   for example mdbox wants to access ns->storage */
	if (--storage->refcount > 0) {
		*_storage = NULL;
		return;
	}

	if (storage->obj_refcount != 0)
		i_panic("Trying to deinit storage before freeing its objects");

	DLLIST_REMOVE(&storage->user->storages, storage);

	if (storage->v.destroy != NULL)
		storage->v.destroy(storage);
	i_free(storage->error_string);

	*_storage = NULL;
	pool_unref(&storage->pool);

	mail_index_alloc_cache_destroy_unrefed();
}

void mail_storage_obj_ref(struct mail_storage *storage)
{
	i_assert(storage->refcount > 0);

	storage->obj_refcount++;
}

void mail_storage_obj_unref(struct mail_storage *storage)
{
	i_assert(storage->refcount > 0);
	i_assert(storage->obj_refcount > 0);

	storage->obj_refcount--;
}

void mail_storage_clear_error(struct mail_storage *storage)
{
	i_free_and_null(storage->error_string);

	storage->error = MAIL_ERROR_NONE;
}

void mail_storage_set_error(struct mail_storage *storage,
			    enum mail_error error, const char *string)
{
	i_free(storage->error_string);
	storage->error_string = i_strdup(string);
	storage->error = error;
}

void mail_storage_set_internal_error(struct mail_storage *storage)
{
	struct tm *tm;
	char str[256];

	tm = localtime(&ioloop_time);

	i_free(storage->error_string);
	storage->error_string =
		strftime(str, sizeof(str),
			 MAIL_ERRSTR_CRITICAL_MSG_STAMP, tm) > 0 ?
		i_strdup(str) : i_strdup(MAIL_ERRSTR_CRITICAL_MSG);
	storage->error = MAIL_ERROR_TEMP;
}

void mail_storage_set_critical(struct mail_storage *storage,
			       const char *fmt, ...)
{
	va_list va;

	mail_storage_clear_error(storage);
	if (fmt != NULL) {
		va_start(va, fmt);
		i_error("%s", t_strdup_vprintf(fmt, va));
		va_end(va);

		/* critical errors may contain sensitive data, so let user
		   see only "Internal error" with a timestamp to make it
		   easier to look from log files the actual error message. */
		mail_storage_set_internal_error(storage);
	}
}

void mail_storage_copy_list_error(struct mail_storage *storage,
				  struct mailbox_list *list)
{
	const char *str;
	enum mail_error error;

	str = mailbox_list_get_last_error(list, &error);
	mail_storage_set_error(storage, error, str);
}

void mail_storage_set_index_error(struct mailbox *box)
{
	if (mail_index_is_deleted(box->index))
		mailbox_set_deleted(box);
	else
		mail_storage_set_internal_error(box->storage);
	mail_index_reset_error(box->index);
}

const struct mail_storage_settings *
mail_storage_get_settings(struct mail_storage *storage)
{
	return storage->set;
}

struct mail_user *mail_storage_get_user(struct mail_storage *storage)
{
	return storage->user;
}

void mail_storage_set_callbacks(struct mail_storage *storage,
				struct mail_storage_callbacks *callbacks,
				void *context)
{
	storage->callbacks = *callbacks;
	storage->callback_context = context;
}

int mail_storage_purge(struct mail_storage *storage)
{
	return storage->v.purge == NULL ? 0 :
		storage->v.purge(storage);
}

const char *mail_storage_get_last_error(struct mail_storage *storage,
					enum mail_error *error_r)
{
	/* We get here only in error situations, so we have to return some
	   error. If storage->error is NONE, it means we forgot to set it at
	   some point.. */
	if (storage->error == MAIL_ERROR_NONE) {
		if (error_r != NULL)
			*error_r = MAIL_ERROR_TEMP;
		return storage->error_string != NULL ? storage->error_string :
			"BUG: Unknown internal error";
	}

	if (storage->error_string == NULL) {
		/* This shouldn't happen.. */
		storage->error_string =
			i_strdup_printf("BUG: Unknown 0x%x error",
					storage->error);
	}

	if (error_r != NULL)
		*error_r = storage->error;
	return storage->error_string;
}

const char *mailbox_get_last_error(struct mailbox *box,
				   enum mail_error *error_r)
{
	return mail_storage_get_last_error(box->storage, error_r);
}

enum mail_error mailbox_get_last_mail_error(struct mailbox *box)
{
	enum mail_error error;

	(void)mail_storage_get_last_error(box->storage, &error);
	return error;
}

bool mail_storage_is_mailbox_file(struct mail_storage *storage)
{
	return (storage->class_flags &
		MAIL_STORAGE_CLASS_FLAG_MAILBOX_IS_FILE) != 0;
}

bool mail_storage_set_error_from_errno(struct mail_storage *storage)
{
	const char *error_string;
	enum mail_error error;

	if (!mail_error_from_errno(&error, &error_string))
		return FALSE;
	if (storage->set->mail_debug && error != MAIL_ERROR_NOTFOUND) {
		/* debugging is enabled - admin may be debugging a
		   (permission) problem, so return FALSE to get the caller to
		   log the full error message. */
		return FALSE;
	}

	mail_storage_set_error(storage, error, error_string);
	return TRUE;
}

struct mailbox *mailbox_alloc(struct mailbox_list *list, const char *vname,
			      enum mailbox_flags flags)
{
	struct mailbox_list *new_list = list;
	struct mail_storage *storage;
	struct mailbox *box;

	if (mailbox_list_get_storage(&new_list, vname, &storage) < 0) {
		/* just use the first storage. FIXME: does this break? */
		storage = list->ns->storage;
	}

	T_BEGIN {
		box = storage->v.mailbox_alloc(storage, new_list, vname, flags);
		hook_mailbox_allocated(box);
	} T_END;

	mail_storage_obj_ref(box->storage);
	return box;
}

static bool have_listable_namespace_prefix(struct mail_namespace *ns,
					   const char *name)
{
	unsigned int name_len = strlen(name);

	for (; ns != NULL; ns = ns->next) {
		if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX |
				  NAMESPACE_FLAG_LIST_CHILDREN)) == 0)
			continue;

		if (ns->prefix_len <= name_len)
			continue;

		/* if prefix has multiple hierarchies, match
		   any of the hierarchies */
		if (strncmp(ns->prefix, name, name_len) == 0 &&
		    ns->prefix[name_len] == mail_namespace_get_sep(ns))
			return TRUE;
	}
	return FALSE;
}

int mailbox_exists(struct mailbox *box)
{
	if (!mailbox_list_is_valid_existing_name(box->list, box->name)) {
		mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}

	if (have_listable_namespace_prefix(box->storage->user->namespaces,
					   box->vname)) {
		/* listable namespace prefix always exists */
		return 1;
	}

	return box->v.exists(box);
}

static int mailbox_check_mismatching_separators(struct mailbox *box)
{
	struct mail_namespace *ns = box->list->ns;
	const char *p, *vname = box->vname;
	char list_sep, ns_sep;

	list_sep = mailbox_list_get_hierarchy_sep(box->list);
	ns_sep = mail_namespace_get_sep(ns);

	if (ns_sep == list_sep)
		return 0;

	if (ns->prefix_len > 0) {
		/* vname is prefix with or without separator */
		i_assert(strncmp(vname, ns->prefix, ns->prefix_len-1) == 0);
		vname += ns->prefix_len - 1;
		if (vname[0] != '\0') {
			i_assert(vname[0] == ns->prefix[ns->prefix_len-1]);
			vname++;
		}
	}

	for (p = vname; *p != '\0'; p++) {
		if (*p == list_sep) {
			mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				t_strdup_printf("NO Character not allowed "
						"in mailbox name: '%c'",
						list_sep));
			return -1;
		}
	}
	return 0;
}

static int mailbox_open_full(struct mailbox *box, struct istream *input)
{
	int ret;

	if (box->opened)
		return 0;

	if (mailbox_check_mismatching_separators(box) < 0)
		return -1;
	if (!mailbox_list_is_valid_existing_name(box->list, box->name)) {
		mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}

	if (input != NULL) {
		if ((box->storage->class_flags &
		     MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0) {
			mail_storage_set_critical(box->storage,
				"Storage doesn't support streamed mailboxes");
			return -1;
		}
		box->input = input;
		box->flags |= MAILBOX_FLAG_READONLY;
		i_stream_ref(box->input);
	}

	T_BEGIN {
		ret = box->v.open(box);
	} T_END;

	if (ret < 0 && box->storage->error == MAIL_ERROR_NOTFOUND &&
	    box->input == NULL && box->inbox_user) T_BEGIN {
		/* INBOX should always exist. try to create it and retry. */
		(void)mailbox_create(box, NULL, FALSE);
		mailbox_close(box);
		ret = box->v.open(box);
		if (ret < 0 && !box->storage->user->inbox_open_error_logged) {
			box->storage->user->inbox_open_error_logged = TRUE;
			i_error("Opening INBOX failed: %s",
				mailbox_get_last_error(box, NULL));
		}
	} T_END;

	if (ret < 0) {
		if (box->input != NULL)
			i_stream_unref(&box->input);
		return -1;
	}

	box->list->ns->flags |= NAMESPACE_FLAG_USABLE;
	return 0;
}

int mailbox_open(struct mailbox *box)
{
	return mailbox_open_full(box, NULL);
}

int mailbox_open_stream(struct mailbox *box, struct istream *input)
{
	return mailbox_open_full(box, input);
}

int mailbox_enable(struct mailbox *box, enum mailbox_feature features)
{
	return box->v.enable(box, features);
}

enum mailbox_feature mailbox_get_enabled_features(struct mailbox *box)
{
	return box->enabled_features;
}

void mailbox_close(struct mailbox *box)
{
	if (!box->opened)
		return;

	if (box->transaction_count != 0) {
		i_panic("Trying to close mailbox %s with open transactions",
			box->name);
	}
	box->v.close(box);

	box->opened = FALSE;
	box->mailbox_deleted = FALSE;
	array_clear(&box->search_results);
}

void mailbox_free(struct mailbox **_box)
{
	struct mailbox *box = *_box;

	*_box = NULL;

	mailbox_close(box);
	box->v.free(box);
	mail_storage_obj_unref(box->storage);
	pool_unref(&box->pool);
}

int mailbox_create(struct mailbox *box, const struct mailbox_update *update,
		   bool directory)
{
	enum mailbox_dir_create_type type;

	if (!mailbox_list_is_valid_create_name(box->list, box->name)) {
		mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}

	type = directory ? MAILBOX_DIR_CREATE_TYPE_TRY_NOSELECT :
		MAILBOX_DIR_CREATE_TYPE_MAILBOX;
	if (box->list->v.create_mailbox_dir(box->list, box->name, type) < 0) {
		mail_storage_copy_list_error(box->storage, box->list);
		return -1;
	}
	mailbox_refresh_permissions(box);

	return box->v.create(box, update, directory);
}

int mailbox_update(struct mailbox *box, const struct mailbox_update *update)
{
	return box->v.update(box, update);
}

int mailbox_mark_index_deleted(struct mailbox *box, bool del)
{
	struct mail_index_transaction *trans;
	enum mail_index_transaction_flags trans_flags = 0;
	enum mailbox_flags old_flag;
	int ret;

	if (box->marked_deleted && del) {
		/* we already marked it deleted. this allows plugins to
		   "lock" the deletion earlier. */
		return 0;
	}

	old_flag = box->flags & MAILBOX_FLAG_OPEN_DELETED;
	box->flags |= MAILBOX_FLAG_OPEN_DELETED;
	ret = mailbox_open(box);
	box->flags = (box->flags & ~MAILBOX_FLAG_OPEN_DELETED) | old_flag;
	if (ret < 0)
		return -1;

	trans_flags = del ? 0 : MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL;
	trans = mail_index_transaction_begin(box->view, trans_flags);
	if (del)
		mail_index_set_deleted(trans);
	else
		mail_index_set_undeleted(trans);
	if (mail_index_transaction_commit(&trans) < 0) {
		mail_storage_set_index_error(box);
		return -1;
	}

	/* sync the mailbox. this finishes the index deletion and it can
	   succeed only for a single session. we do it here, so the rest of
	   the deletion code doesn't have to worry about race conditions. */
	if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0)
		return -1;

	box->marked_deleted = del;
	return 0;
}

static bool mailbox_try_undelete(struct mailbox *box)
{
	time_t mtime;

	if (mail_index_get_modification_time(box->index, &mtime) < 0)
		return FALSE;
	if (mtime + MAILBOX_DELETE_RETRY_SECS > time(NULL))
		return FALSE;

	if (mailbox_mark_index_deleted(box, FALSE) < 0)
		return FALSE;
	box->mailbox_deleted = FALSE;
	return TRUE;
}

int mailbox_delete(struct mailbox *box)
{
	int ret;

	if (*box->name == '\0') {
		mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				       "Storage root can't be deleted");
		return -1;
	}
	if (box->inbox_any) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
				       "INBOX can't be deleted.");
		return -1;
	}

	box->deleting = TRUE;
	if (mailbox_open(box) < 0) {
		if (mailbox_get_last_mail_error(box) != MAIL_ERROR_NOTFOUND)
			return -1;
		if (!box->mailbox_deleted) {
			/* \noselect mailbox */
		} else {
			/* if deletion happened a long time ago, it means it
			   crashed while doing it. undelete the mailbox in
			   that case. */
			if (!mailbox_try_undelete(box))
				return -1;

			/* retry */
			if (mailbox_open(box) < 0)
				return -1;
		}
	}

	ret = box->v.delete(box);
	if (ret < 0 && box->marked_deleted) {
		/* deletion failed. revert the mark so it can maybe be
		   tried again later. */
		if (mailbox_mark_index_deleted(box, FALSE) < 0)
			return -1;
	}

	box->deleting = FALSE;
	mailbox_close(box);
	return ret;
}

static bool
mail_storages_rename_compatible(struct mail_storage *storage1,
				struct mail_storage *storage2)
{
	if (storage1 == storage2)
		return TRUE;

	if (strcmp(storage1->name, storage2->name) != 0)
		return FALSE;
	if ((storage1->class_flags & MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) != 0)
		return FALSE;
	return TRUE;
}

static bool nullequals(const void *p1, const void *p2)
{
	return (p1 == NULL && p2 == NULL) || (p1 != NULL && p2 != NULL);
}

static bool
mailbox_lists_rename_compatible(struct mailbox_list *list1,
				struct mailbox_list *list2)
{
	return nullequals(list1->set.alt_dir, list2->set.alt_dir) &&
		nullequals(list1->set.index_dir, list2->set.index_dir) &&
		nullequals(list1->set.control_dir, list2->set.control_dir);
}

int mailbox_rename(struct mailbox *src, struct mailbox *dest,
		   bool rename_children)
{
	if (!mailbox_list_is_valid_existing_name(src->list, src->name) ||
	    *src->name == '\0' ||
	    !mailbox_list_is_valid_create_name(dest->list, dest->name)) {
		mail_storage_set_error(src->storage, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}
	if (!mail_storages_rename_compatible(src->storage, dest->storage) ||
	    !mailbox_lists_rename_compatible(src->list, dest->list)) {
		mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
			"Can't rename mailboxes across specified storages.");
		return -1;
	}
	if (src->list->ns->type != NAMESPACE_PRIVATE ||
	    dest->list->ns->type != NAMESPACE_PRIVATE) {
		mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
			"Renaming not supported across non-private namespaces.");
		return -1;
	}

	return src->v.rename(src, dest, rename_children);
}

int mailbox_set_subscribed(struct mailbox *box, bool set)
{
	struct mail_namespace *ns;
	struct mailbox_list *list = box->list;
	const char *subs_name;

	if (!mailbox_list_is_valid_existing_name(list, box->name)) {
		mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}

	if ((list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0)
		subs_name = box->name;
	else {
		/* subscriptions=no namespace, find another one where we can
		   add the subscription to */
		ns = mail_namespace_find_subscribable(list->ns->user->namespaces,
						      box->vname);
		if (ns == NULL) {
			mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
				"This namespace has no subscriptions");
			return -1;
		}
		/* use <orig ns prefix><orig storage name> as the
		   subscription name */
		subs_name = t_strconcat(list->ns->prefix, box->name, NULL);
		/* drop the common prefix (typically there isn't one) */
		i_assert(strncmp(ns->prefix, subs_name, strlen(ns->prefix)) == 0);
		subs_name += strlen(ns->prefix);

		list = ns->list;
	}
	return mailbox_list_set_subscribed(list, subs_name, set);
}

struct mail_storage *mailbox_get_storage(const struct mailbox *box)
{
	return box->storage;
}

struct mail_namespace *
mailbox_get_namespace(const struct mailbox *box)
{
	return box->list->ns;
}

const struct mail_storage_settings *mailbox_get_settings(struct mailbox *box)
{
	return box->storage->set;
}

const char *mailbox_get_name(const struct mailbox *box)
{
	return box->name;
}

const char *mailbox_get_vname(const struct mailbox *box)
{
	return box->vname;
}

bool mailbox_is_readonly(struct mailbox *box)
{
	return box->v.is_readonly(box);
}

bool mailbox_allow_new_keywords(struct mailbox *box)
{
	return box->v.allow_new_keywords(box);
}

bool mailbox_backends_equal(const struct mailbox *box1,
			    const struct mailbox *box2)
{
	struct mail_namespace *ns1 = box1->list->ns, *ns2 = box2->list->ns;

	if (strcmp(box1->name, box2->name) != 0)
		return FALSE;

	while (ns1->alias_for != NULL)
		ns1 = ns1->alias_for;
	while (ns2->alias_for != NULL)
		ns2 = ns2->alias_for;
	return ns1 == ns2;
}

int mailbox_get_status(struct mailbox *box,
		       enum mailbox_status_items items,
		       struct mailbox_status *status_r)
{
	return box->v.get_status(box, items, status_r);
}

void mailbox_get_open_status(struct mailbox *box,
			     enum mailbox_status_items items,
			     struct mailbox_status *status_r)
{
	i_assert(box->opened);
	if (box->v.get_status(box, items, status_r) < 0)
		i_unreached();
}

int mailbox_get_metadata(struct mailbox *box, enum mailbox_metadata_items items,
			 struct mailbox_metadata *metadata_r)
{
	if (!box->opened) {
		if (mailbox_open(box) < 0)
			return -1;
	}
	if (box->v.get_metadata(box, items, metadata_r) < 0)
		return -1;

	i_assert((items & MAILBOX_METADATA_GUID) == 0 ||
		 !mail_guid_128_is_empty(metadata_r->guid));
	return 0;
}

enum mail_flags mailbox_get_private_flags_mask(struct mailbox *box)
{
	if (box->v.get_private_flags_mask == NULL)
		return 0;
	else
		return box->v.get_private_flags_mask(box);
}

struct mailbox_sync_context *
mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
{
	struct mailbox_sync_context *ctx;

	if (box->transaction_count != 0) {
		i_panic("Trying to sync mailbox %s with open transactions",
			box->name);
	}
	T_BEGIN {
		ctx = box->v.sync_init(box, flags);
	} T_END;
	return ctx;
}

bool mailbox_sync_next(struct mailbox_sync_context *ctx,
		       struct mailbox_sync_rec *sync_rec_r)
{
	return ctx->box->v.sync_next(ctx, sync_rec_r);
}

int mailbox_sync_deinit(struct mailbox_sync_context **_ctx,
			struct mailbox_sync_status *status_r)
{
	struct mailbox_sync_context *ctx = *_ctx;
	struct mailbox *box = ctx->box;
	const char *errormsg;
	enum mail_error error;
	int ret;

	*_ctx = NULL;

	memset(status_r, 0, sizeof(*status_r));
	ret = box->v.sync_deinit(ctx, status_r);
	if (ret < 0 && box->inbox_user &&
	    !box->storage->user->inbox_open_error_logged) {
		errormsg = mailbox_get_last_error(box, &error);
		if (error == MAIL_ERROR_NOTPOSSIBLE) {
			box->storage->user->inbox_open_error_logged = TRUE;
			i_error("Syncing INBOX failed: %s", errormsg);
		}
	}
	return ret;
}

int mailbox_sync(struct mailbox *box, enum mailbox_sync_flags flags)
{
	struct mailbox_sync_context *ctx;
	struct mailbox_sync_status status;

	if (array_count(&box->search_results) == 0) {
		/* we don't care about mailbox's current state, so we might
		   as well fix inconsistency state */
		flags |= MAILBOX_SYNC_FLAG_FIX_INCONSISTENT;
	}

	ctx = mailbox_sync_init(box, flags);
	return mailbox_sync_deinit(&ctx, &status);
}

#undef mailbox_notify_changes
void mailbox_notify_changes(struct mailbox *box, unsigned int min_interval,
			    mailbox_notify_callback_t *callback, void *context)
{
	box->notify_min_interval = min_interval;
	box->notify_callback = callback;
	box->notify_context = context;

	box->v.notify_changes(box);
}

void mailbox_notify_changes_stop(struct mailbox *box)
{
	mailbox_notify_changes(box, 0, NULL, NULL);
}

struct mail_search_context *
mailbox_search_init(struct mailbox_transaction_context *t,
		    struct mail_search_args *args,
		    const enum mail_sort_type *sort_program)
{
	mail_search_args_ref(args);
	if (!args->simplified)
		mail_search_args_simplify(args);
	return t->box->v.search_init(t, args, sort_program);
}

int mailbox_search_deinit(struct mail_search_context **_ctx)
{
	struct mail_search_context *ctx = *_ctx;
	struct mail_search_args *args = ctx->args;
	int ret;

	*_ctx = NULL;
	mailbox_search_results_initial_done(ctx);
	ret = ctx->transaction->box->v.search_deinit(ctx);
	mail_search_args_unref(&args);
	return ret;
}

bool mailbox_search_next(struct mail_search_context *ctx, struct mail *mail)
{
	bool tryagain;

	while (!mailbox_search_next_nonblock(ctx, mail, &tryagain)) {
		if (!tryagain)
			return FALSE;
	}
	return TRUE;
}

bool mailbox_search_next_nonblock(struct mail_search_context *ctx,
				  struct mail *mail, bool *tryagain_r)
{
	struct mailbox *box = ctx->transaction->box;

	if (!box->v.search_next_nonblock(ctx, mail, tryagain_r))
		return FALSE;
	else {
		mailbox_search_results_add(ctx, mail->uid);
		return TRUE;
	}
}

bool mailbox_search_seen_lost_data(struct mail_search_context *ctx)
{
	return ctx->seen_lost_data;
}

int mailbox_search_result_build(struct mailbox_transaction_context *t,
				struct mail_search_args *args,
				enum mailbox_search_result_flags flags,
				struct mail_search_result **result_r)
{
	struct mail_search_context *ctx;
	struct mail *mail;
	int ret;

	ctx = mailbox_search_init(t, args, NULL);
	*result_r = mailbox_search_result_save(ctx, flags);
	mail = mail_alloc(t, 0, NULL);
	while (mailbox_search_next(ctx, mail)) ;
	mail_free(&mail);

	ret = mailbox_search_deinit(&ctx);
	if (ret < 0)
		mailbox_search_result_free(result_r);
	return ret;
}

struct mailbox_transaction_context *
mailbox_transaction_begin(struct mailbox *box,
			  enum mailbox_transaction_flags flags)
{
	struct mailbox_transaction_context *trans;

	box->transaction_count++;
	trans = box->v.transaction_begin(box, flags);
	trans->flags = flags;
	return trans;
}

int mailbox_transaction_commit(struct mailbox_transaction_context **t)
{
	struct mail_transaction_commit_changes changes;
	int ret;

	/* Store changes temporarily so that plugins overriding
	   transaction_commit() can look at them. */
	changes.pool = NULL;
	ret = mailbox_transaction_commit_get_changes(t, &changes);
	if (changes.pool != NULL)
		pool_unref(&changes.pool);
	return ret;
}

int mailbox_transaction_commit_get_changes(
	struct mailbox_transaction_context **_t,
	struct mail_transaction_commit_changes *changes_r)
{
	struct mailbox_transaction_context *t = *_t;
	int ret;

	t->box->transaction_count--;

	*_t = NULL;
	T_BEGIN {
		ret = t->box->v.transaction_commit(t, changes_r);
	} T_END;
	return ret;
}

void mailbox_transaction_rollback(struct mailbox_transaction_context **_t)
{
	struct mailbox_transaction_context *t = *_t;

	t->box->transaction_count--;

	*_t = NULL;
	t->box->v.transaction_rollback(t);
}

unsigned int mailbox_transaction_get_count(const struct mailbox *box)
{
	return box->transaction_count;
}

void mailbox_transaction_set_max_modseq(struct mailbox_transaction_context *t,
					uint64_t max_modseq,
					ARRAY_TYPE(seq_range) *seqs)
{
	mail_index_transaction_set_max_modseq(t->itrans, max_modseq, seqs);
}

struct mailbox *
mailbox_transaction_get_mailbox(const struct mailbox_transaction_context *t)
{
	return t->box;
}

struct mail_save_context *
mailbox_save_alloc(struct mailbox_transaction_context *t)
{
	struct mail_save_context *ctx;

	ctx = t->box->v.save_alloc(t);
	ctx->received_date = (time_t)-1;
	ctx->save_date = (time_t)-1;
	return ctx;
}

void mailbox_save_set_flags(struct mail_save_context *ctx,
			    enum mail_flags flags,
			    struct mail_keywords *keywords)
{
	ctx->flags = flags;
	ctx->keywords = keywords;
	if (keywords != NULL)
		mailbox_keywords_ref(keywords);
}

void mailbox_save_copy_flags(struct mail_save_context *ctx, struct mail *mail)
{
	const char *const *keywords_list;

	keywords_list = mail_get_keywords(mail);
	ctx->keywords = str_array_length(keywords_list) == 0 ? NULL :
		mailbox_keywords_create_valid(ctx->transaction->box,
					      keywords_list);
	ctx->flags = mail_get_flags(mail);
}

void mailbox_save_set_min_modseq(struct mail_save_context *ctx,
				 uint64_t min_modseq)
{
	ctx->min_modseq = min_modseq;
}

void mailbox_save_set_received_date(struct mail_save_context *ctx,
				    time_t received_date, int timezone_offset)
{
	ctx->received_date = received_date;
	ctx->received_tz_offset = timezone_offset;
}

void mailbox_save_set_save_date(struct mail_save_context *ctx,
				time_t save_date)
{
	ctx->save_date = save_date;
}

void mailbox_save_set_from_envelope(struct mail_save_context *ctx,
				    const char *envelope)
{
	i_free(ctx->from_envelope);
	ctx->from_envelope = i_strdup(envelope);
}

void mailbox_save_set_uid(struct mail_save_context *ctx, uint32_t uid)
{
	ctx->uid = uid;
}

void mailbox_save_set_guid(struct mail_save_context *ctx, const char *guid)
{
	i_assert(guid == NULL || *guid != '\0');

	i_free(ctx->guid);
	ctx->guid = i_strdup(guid);
}

void mailbox_save_set_pop3_uidl(struct mail_save_context *ctx, const char *uidl)
{
	i_assert(*uidl != '\0');
	i_assert(strchr(uidl, '\n') == NULL);

	i_free(ctx->pop3_uidl);
	ctx->pop3_uidl = i_strdup(uidl);
}

void mailbox_save_set_dest_mail(struct mail_save_context *ctx,
				struct mail *mail)
{
	ctx->dest_mail = mail;
}

int mailbox_save_begin(struct mail_save_context **ctx, struct istream *input)
{
	struct mailbox *box = (*ctx)->transaction->box;
	int ret;

	if (mail_index_is_deleted(box->index)) {
		mailbox_set_deleted(box);
		return -1;
	}

	if (box->v.save_begin == NULL) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
				       "Saving messages not supported");
		ret = -1;
	} else {
		ret = box->v.save_begin(*ctx, input);
	}

	if (ret < 0) {
		mailbox_save_cancel(ctx);
		return -1;
	}
	return 0;
}

int mailbox_save_continue(struct mail_save_context *ctx)
{
	return ctx->transaction->box->v.save_continue(ctx);
}

int mailbox_save_finish(struct mail_save_context **_ctx)
{
	struct mail_save_context *ctx = *_ctx;
	struct mailbox *box = ctx->transaction->box;
	struct mail_keywords *keywords = ctx->keywords;
	int ret;

	*_ctx = NULL;
	ret = box->v.save_finish(ctx);
	if (keywords != NULL)
		mailbox_keywords_unref(&keywords);
	return ret;
}

void mailbox_save_cancel(struct mail_save_context **_ctx)
{
	struct mail_save_context *ctx = *_ctx;
	struct mail_keywords *keywords = ctx->keywords;

	*_ctx = NULL;
	ctx->transaction->box->v.save_cancel(ctx);
	if (keywords != NULL)
		mailbox_keywords_unref(&keywords);
}

int mailbox_copy(struct mail_save_context **_ctx, struct mail *mail)
{
	struct mail_save_context *ctx = *_ctx;
	struct mailbox *box = ctx->transaction->box;
	struct mail_keywords *keywords = ctx->keywords;
	int ret;

	*_ctx = NULL;

	if (mail_index_is_deleted(box->index)) {
		mailbox_set_deleted(box);
		mailbox_save_cancel(_ctx);
		return -1;
	}

	ret = ctx->transaction->box->v.copy(ctx, mail);
	if (keywords != NULL)
		mailbox_keywords_unref(&keywords);
	return ret;
}

bool mailbox_is_inconsistent(struct mailbox *box)
{
	return box->mailbox_deleted || box->v.is_inconsistent(box);
}

void mailbox_set_deleted(struct mailbox *box)
{
	mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
			       "Mailbox was deleted under us");
	box->mailbox_deleted = TRUE;
}

const char *mailbox_get_path(struct mailbox *box)
{
	const char *path;

	if (box->_path == NULL) {
		path = mailbox_list_get_path(box->list, box->name,
					     MAILBOX_LIST_PATH_TYPE_MAILBOX);
		box->_path = p_strdup(box->pool, path);
	}
	return box->_path;
}

static void mailbox_get_permissions_if_not_set(struct mailbox *box)
{
	const char *origin, *dir_origin;
	gid_t dir_gid;

	if (box->_perm.file_create_mode != 0)
		return;

	if (box->input != NULL) {
		box->_perm.file_create_mode = 0600;
		box->_perm.dir_create_mode = 0700;
		box->_perm.file_create_gid = (gid_t)-1;
		box->_perm.file_create_gid_origin = "defaults";
		return;
	}

	mailbox_list_get_permissions(box->list, box->name,
				     &box->_perm.file_create_mode,
				     &box->_perm.file_create_gid, &origin);
	box->_perm.file_create_gid_origin = p_strdup(box->pool, origin);
	mailbox_list_get_dir_permissions(box->list, box->name,
					 &box->_perm.dir_create_mode,
					 &dir_gid, &dir_origin);
}

const struct mailbox_permissions *mailbox_get_permissions(struct mailbox *box)
{
	mailbox_get_permissions_if_not_set(box);

	if (!box->_perm.mail_index_permissions_set && box->index != NULL) {
		box->_perm.mail_index_permissions_set = TRUE;
		mail_index_set_permissions(box->index,
					   box->_perm.file_create_mode,
					   box->_perm.file_create_gid,
					   box->_perm.file_create_gid_origin);
	}
	return &box->_perm;
}

void mailbox_refresh_permissions(struct mailbox *box)
{
	memset(&box->_perm, 0, sizeof(box->_perm));
	(void)mailbox_get_permissions(box);
}

int mailbox_create_fd(struct mailbox *box, const char *path, int flags,
		      int *fd_r)
{
	const struct mailbox_permissions *perm = mailbox_get_permissions(box);
	mode_t old_mask;
	int fd;

	i_assert((flags & O_CREAT) != 0);

	*fd_r = -1;

	old_mask = umask(0);
	fd = open(path, flags, perm->file_create_mode);
	umask(old_mask);

	if (fd != -1) {
		/* ok */
	} else if (errno == EEXIST) {
		/* O_EXCL used, caller will handle this error */
		return 0;
	} else if (errno == ENOENT) {
		mailbox_set_deleted(box);
		return -1;
	} else if (errno == ENOTDIR) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			"Mailbox doesn't allow inferior mailboxes");
		return -1;
	} else if (mail_storage_set_error_from_errno(box->storage)) {
		return -1;
	} else {
		mail_storage_set_critical(box->storage,
			"open(%s, O_CREAT) failed: %m", path);
		return -1;
	}

	if (perm->file_create_gid != (gid_t)-1) {
		if (fchown(fd, (uid_t)-1, perm->file_create_gid) == 0) {
			/* ok */
		} else if (errno == EPERM) {
			mail_storage_set_critical(box->storage, "%s",
				eperm_error_get_chgrp("fchown", path,
					perm->file_create_gid,
					perm->file_create_gid_origin));
		} else {
			mail_storage_set_critical(box->storage,
				"fchown(%s) failed: %m", path);
		}
	}
	*fd_r = fd;
	return 1;
}

unsigned int mail_storage_get_lock_timeout(struct mail_storage *storage,
					   unsigned int secs)
{
	return storage->set->mail_max_lock_timeout == 0 ? secs :
		I_MIN(secs, storage->set->mail_max_lock_timeout);
}