view src/plugins/virtual/virtual-storage.c @ 12583:4073ccc8123f

lib-storage: Replaced mailbox_list_get_name_status() with mailbox_exists().
author Timo Sirainen <tss@iki.fi>
date Thu, 20 Jan 2011 18:14:53 +0200
parents 2c299c0e3bc8
children b748c622e896
line wrap: on
line source

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

#include "lib.h"
#include "array.h"
#include "ioloop.h"
#include "str.h"
#include "mkdir-parents.h"
#include "unlink-directory.h"
#include "index-mail.h"
#include "mail-copy.h"
#include "mail-search.h"
#include "mailbox-list-private.h"
#include "virtual-plugin.h"
#include "virtual-transaction.h"
#include "virtual-storage.h"

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

#define VIRTUAL_LIST_CONTEXT(obj) \
	MODULE_CONTEXT(obj, virtual_mailbox_list_module)

struct virtual_mailbox_list {
	union mailbox_list_module_context module_ctx;
};

extern struct mail_storage virtual_storage;
extern struct mailbox virtual_mailbox;
extern struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs;

struct virtual_storage_module virtual_storage_module =
	MODULE_CONTEXT_INIT(&mail_storage_module_register);
static MODULE_CONTEXT_DEFINE_INIT(virtual_mailbox_list_module,
				  &mailbox_list_module_register);

static bool ns_is_visible(struct mail_namespace *ns)
{
	return (ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 ||
		(ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 ||
		(ns->flags & NAMESPACE_FLAG_HIDDEN) == 0;
}

static const char *get_user_visible_mailbox_name(struct mailbox *box)
{
	if (ns_is_visible(box->list->ns))
		return box->vname;
	else {
		return t_strdup_printf("<hidden>%c%s",
				       box->list->hierarchy_sep, box->name);
	}
}

void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src)
{
	const char *name, *str;
	enum mail_error error;

	name = get_user_visible_mailbox_name(src);
	str = mail_storage_get_last_error(src->storage, &error);

	str = t_strdup_printf("%s (for backend mailbox %s)", str, name);
	mail_storage_set_error(dest->storage, error, str);
}

static struct mail_storage *virtual_storage_alloc(void)
{
	struct virtual_storage *storage;
	pool_t pool;

	pool = pool_alloconly_create("virtual storage", 1024);
	storage = p_new(pool, struct virtual_storage, 1);
	storage->storage = virtual_storage;
	storage->storage.pool = pool;
	p_array_init(&storage->open_stack, pool, 8);
	return &storage->storage;
}

static void
virtual_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
				  struct mailbox_list_settings *set)
{
	if (set->layout == NULL)
		set->layout = MAILBOX_LIST_NAME_FS;
	if (set->subscription_fname == NULL)
		set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME;
}

struct virtual_backend_box *
virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (strcmp(bboxes[i]->name, name) == 0)
			return bboxes[i];
	}
	return NULL;
}

struct virtual_backend_box *
virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	if (mailbox_id == 0)
		return NULL;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (bboxes[i]->mailbox_id == mailbox_id)
			return bboxes[i];
	}
	return NULL;
}

static bool virtual_mailbox_is_in_open_stack(struct virtual_storage *storage,
					     const char *name)
{
	const char *const *names;
	unsigned int i, count;

	names = array_get(&storage->open_stack, &count);
	for (i = 0; i < count; i++) {
		if (strcmp(names[i], name) == 0)
			return TRUE;
	}
	return FALSE;
}

static int virtual_backend_box_open_failed(struct virtual_mailbox *mbox,
					   struct virtual_backend_box *bbox)
{
	enum mail_error error;
	const char *str, *name;

	str = mail_storage_get_last_error(mailbox_get_storage(bbox->box),
					  &error);
	name = t_strdup(get_user_visible_mailbox_name(bbox->box));
	mailbox_free(&bbox->box);
	if (error == MAIL_ERROR_NOTFOUND) {
		/* ignore this. it could be intentional. */
		if (mbox->storage->storage.user->mail_debug) {
			i_debug("virtual mailbox %s: "
				"Skipping non-existing mailbox %s",
				mbox->box.vname, name);
		}
		return 0;
	}

	if (error == MAIL_ERROR_PERM && bbox->wildcard) {
		/* this mailbox wasn't explicitly specified. just skip it. */
		return 0;
	}
	str = t_strdup_printf(
		"Virtual mailbox open failed because of mailbox %s: %s",
		name, str);
	mail_storage_set_error(mbox->box.storage, error, str);
	return -1;
}

static int virtual_backend_box_open(struct virtual_mailbox *mbox,
				    struct virtual_backend_box *bbox,
				    enum mailbox_flags flags)
{
	struct mail_user *user = mbox->storage->storage.user;
	struct mail_namespace *ns;
	const char *mailbox;

	i_assert(bbox->box == NULL);

	flags |= MAILBOX_FLAG_KEEP_RECENT;

	mailbox = bbox->name;
	ns = mail_namespace_find(user->namespaces, &mailbox);
	bbox->box = mailbox_alloc(ns->list, mailbox, flags);

	if (mailbox_open(bbox->box) < 0)
		return virtual_backend_box_open_failed(mbox, bbox);
	i_array_init(&bbox->uids, 64);
	i_array_init(&bbox->sync_pending_removes, 64);
	mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL);
	return 1;
}

static int virtual_mailboxes_open(struct virtual_mailbox *mbox,
				  enum mailbox_flags flags)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;
	int ret;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; ) {
		ret = virtual_backend_box_open(mbox, bboxes[i], flags);
		if (ret <= 0) {
			if (ret < 0)
				break;
			mail_search_args_unref(&bboxes[i]->search_args);
			array_delete(&mbox->backend_boxes, i, 1);
			bboxes = array_get(&mbox->backend_boxes, &count);
		} else {
			i++;
		}
	}
	if (i == count)
		return 0;
	else {
		/* failed */
		for (; i > 0; i--) {
			mailbox_free(&bboxes[i-1]->box);
			array_free(&bboxes[i-1]->uids);
		}
		return -1;
	}
}

static struct mailbox *
virtual_mailbox_alloc(struct mail_storage *_storage, struct mailbox_list *list,
		      const char *name, enum mailbox_flags flags)
{
	struct virtual_storage *storage = (struct virtual_storage *)_storage;
	struct virtual_mailbox *mbox;
	pool_t pool;

	pool = pool_alloconly_create("virtual mailbox", 1024+512);
	mbox = p_new(pool, struct virtual_mailbox, 1);
	mbox->box = virtual_mailbox;
	mbox->box.pool = pool;
	mbox->box.storage = _storage;
	mbox->box.list = list;
	mbox->box.mail_vfuncs = &virtual_mail_vfuncs;
	mbox->vfuncs = virtual_mailbox_vfuncs;

	index_storage_mailbox_alloc(&mbox->box, name, flags,
				    VIRTUAL_INDEX_PREFIX);

	mbox->storage = storage;
	mbox->vseq_lookup_prev_mailbox = i_strdup("");

	mbox->virtual_ext_id =
		mail_index_ext_register(mbox->box.index, "virtual", 0,
			sizeof(struct virtual_mail_index_record),
			sizeof(uint32_t));
	return &mbox->box;
}

static void virtual_mailbox_close_internal(struct virtual_mailbox *mbox)
{
	struct virtual_backend_box **bboxes;
	unsigned int i, count;

	bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (bboxes[i]->search_result != NULL)
			mailbox_search_result_free(&bboxes[i]->search_result);

		if (bboxes[i]->box == NULL)
			continue;

		mail_search_args_deinit(bboxes[i]->search_args);
		mailbox_free(&bboxes[i]->box);
		if (array_is_created(&bboxes[i]->sync_outside_expunges))
			array_free(&bboxes[i]->sync_outside_expunges);
		array_free(&bboxes[i]->sync_pending_removes);
		array_free(&bboxes[i]->uids);
	}
	i_free_and_null(mbox->vseq_lookup_prev_mailbox);
}

static int virtual_mailbox_open(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	int ret = 0;

	if (virtual_mailbox_is_in_open_stack(mbox->storage, box->name)) {
		mail_storage_set_critical(box->storage,
			"Virtual mailbox loops: %s", box->name);
		return -1;
	}

	if (!array_is_created(&mbox->backend_boxes))
		ret = virtual_config_read(mbox);
	if (ret == 0) {
		array_append(&mbox->storage->open_stack, &box->name, 1);
		ret = virtual_mailboxes_open(mbox, box->flags);
		array_delete(&mbox->storage->open_stack,
			     array_count(&mbox->storage->open_stack)-1, 1);
	}
	if (ret < 0) {
		virtual_mailbox_close_internal(mbox);
		return -1;
	}
	return index_storage_mailbox_open(box, FALSE);
}

static void virtual_mailbox_close(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	virtual_mailbox_close_internal(mbox);
	index_storage_mailbox_close(box);
}

static void virtual_mailbox_free(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	virtual_config_free(mbox);
	index_storage_mailbox_free(box);
}

static int
virtual_mailbox_create(struct mailbox *box,
		       const struct mailbox_update *update ATTR_UNUSED,
		       bool directory ATTR_UNUSED)
{
	mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			       "Can't create virtual mailboxes");
	return -1;
}

static int
virtual_mailbox_update(struct mailbox *box,
		       const struct mailbox_update *update ATTR_UNUSED)
{
	mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			       "Can't update virtual mailboxes");
	return -1;
}

static int
virtual_mailbox_get_metadata(struct mailbox *box,
			     enum mailbox_metadata_items items,
			     struct mailbox_metadata *metadata_r)
{
	if ((items & MAILBOX_METADATA_GUID) != 0) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
				       "Virtual mailboxes have no GUIDs");
		return -1;
	}
	return index_mailbox_get_metadata(box, items, metadata_r);
}

static void
virtual_notify_callback(struct mailbox *bbox ATTR_UNUSED, struct mailbox *box)
{
	box->notify_callback(box, box->notify_context);
}

static void virtual_notify_changes(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		struct mailbox *bbox = bboxes[i]->box;

		if (box->notify_callback == NULL)
			mailbox_notify_changes_stop(bbox);
		else {
			mailbox_notify_changes(bbox, box->notify_min_interval,
					       virtual_notify_callback, box);
		}
	}
}

static int
virtual_list_get_mailbox_flags(struct mailbox_list *list,
			       const char *dir, const char *fname,
			       enum mailbox_list_file_type type,
			       struct stat *st_r,
			       enum mailbox_info_flags *flags)
{
	struct virtual_mailbox_list *mlist = VIRTUAL_LIST_CONTEXT(list);
	struct stat st2;
	const char *virtual_path;
	int ret;

	ret = mlist->module_ctx.super.
		get_mailbox_flags(list, dir, fname, type, st_r, flags);
	if (ret <= 0 || MAILBOX_INFO_FLAGS_FINISHED(*flags))
		return ret;

	/* see if it's a selectable mailbox */
	virtual_path = t_strconcat(dir, "/", fname, "/"VIRTUAL_CONFIG_FNAME,
				   NULL);
	if (stat(virtual_path, &st2) < 0)
		*flags |= MAILBOX_NOSELECT;
	return ret;
}

static void virtual_storage_add_list(struct mail_storage *storage ATTR_UNUSED,
				     struct mailbox_list *list)
{
	struct mailbox_list_vfuncs *v = list->vlast;
	struct virtual_mailbox_list *mlist;

	mlist = p_new(list->pool, struct virtual_mailbox_list, 1);
	mlist->module_ctx.super = *v;
	list->vlast = &mlist->module_ctx.super;

	v->get_mailbox_flags = virtual_list_get_mailbox_flags;

	MODULE_CONTEXT_SET(list, virtual_mailbox_list_module, mlist);
}

static int virtual_backend_uidmap_cmp(const uint32_t *uid,
				      const struct virtual_backend_uidmap *map)
{
	return *uid < map->real_uid ? -1 :
		*uid > map->real_uid ? 1 : 0;
}

static bool
virtual_get_virtual_uid(struct mailbox *box, const char *backend_mailbox,
			uint32_t backend_uidvalidity,
			uint32_t backend_uid, uint32_t *uid_r)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *bbox;
	struct mailbox_status status;
	const struct virtual_backend_uidmap *uids;

	if (strcmp(mbox->vseq_lookup_prev_mailbox, backend_mailbox) == 0)
		bbox = mbox->vseq_lookup_prev_bbox;
	else {
		i_free(mbox->vseq_lookup_prev_mailbox);
		mbox->vseq_lookup_prev_mailbox = i_strdup(backend_mailbox);

		bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox);
		mbox->vseq_lookup_prev_bbox = bbox;
	}
	if (bbox == NULL)
		return FALSE;

	mailbox_get_open_status(bbox->box, STATUS_UIDVALIDITY, &status);
	if (status.uidvalidity != backend_uidvalidity)
		return FALSE;

	uids = array_bsearch(&bbox->uids, &backend_uid,
			     virtual_backend_uidmap_cmp);
	if (uids == NULL)
		return FALSE;

	*uid_r = uids->virtual_uid;
	return TRUE;
}

static void
virtual_get_virtual_backend_boxes(struct mailbox *box,
				  ARRAY_TYPE(mailboxes) *mailboxes,
				  bool only_with_msgs)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (!only_with_msgs || array_count(&bboxes[i]->uids) > 0)
			array_append(mailboxes, &bboxes[i]->box, 1);
	}
}

static void
virtual_get_virtual_box_patterns(struct mailbox *box,
				 ARRAY_TYPE(mailbox_virtual_patterns) *includes,
				 ARRAY_TYPE(mailbox_virtual_patterns) *excludes)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	array_append_array(includes, &mbox->list_include_patterns);
	array_append_array(excludes, &mbox->list_exclude_patterns);
}

static bool virtual_is_inconsistent(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	if (mbox->inconsistent)
		return TRUE;

	return index_storage_is_inconsistent(box);
}

struct mail_storage virtual_storage = {
	.name = VIRTUAL_STORAGE_NAME,
	.class_flags = MAIL_STORAGE_CLASS_FLAG_NOQUOTA,

	.v = {
		NULL,
		virtual_storage_alloc,
		NULL,
		NULL,
		virtual_storage_add_list,
		virtual_storage_get_list_settings,
		NULL,
		virtual_mailbox_alloc,
		NULL
	}
};

struct mailbox virtual_mailbox = {
	.v = {
		index_storage_is_readonly,
		index_storage_allow_new_keywords,
		index_storage_mailbox_enable,
		index_storage_mailbox_exists,
		virtual_mailbox_open,
		virtual_mailbox_close,
		virtual_mailbox_free,
		virtual_mailbox_create,
		virtual_mailbox_update,
		index_storage_mailbox_delete,
		index_storage_mailbox_rename,
		index_storage_get_status,
		virtual_mailbox_get_metadata,
		NULL,
		NULL,
		virtual_storage_sync_init,
		index_mailbox_sync_next,
		index_mailbox_sync_deinit,
		NULL,
		virtual_notify_changes,
		virtual_transaction_begin,
		virtual_transaction_commit,
		virtual_transaction_rollback,
		NULL,
		virtual_mail_alloc,
		virtual_search_init,
		virtual_search_deinit,
		virtual_search_next_nonblock,
		virtual_search_next_update_seq,
		virtual_save_alloc,
		virtual_save_begin,
		virtual_save_continue,
		virtual_save_finish,
		virtual_save_cancel,
		mail_storage_copy,
		virtual_is_inconsistent
	}
};

struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs = {
	virtual_get_virtual_uid,
	virtual_get_virtual_backend_boxes,
	virtual_get_virtual_box_patterns
};