view src/plugins/quota-clone/quota-clone-plugin.c @ 19592:cf3fdf335484

quota-clone: Fixed update-recursion crash when closing mailbox. We need to avoid recursing for all mailboxes that quota recalculation might open and close, not just the one that triggered the recalculation.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 19 Jan 2016 15:11:44 +0200
parents 0f22db71df7a
children 51a4c305a087
line wrap: on
line source

/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "module-context.h"
#include "dict.h"
#include "mail-storage-private.h"
#include "quota.h"
#include "quota-clone-plugin.h"

#define DICT_QUOTA_CLONE_PATH DICT_PATH_PRIVATE"quota/"
#define DICT_QUOTA_CLONE_BYTES_PATH DICT_QUOTA_CLONE_PATH"storage"
#define DICT_QUOTA_CLONE_COUNT_PATH DICT_QUOTA_CLONE_PATH"messages"

#define QUOTA_CLONE_USER_CONTEXT(obj) \
	MODULE_CONTEXT(obj, quota_clone_user_module)
#define QUOTA_CLONE_CONTEXT(obj) \
	MODULE_CONTEXT(obj, quota_clone_storage_module)

static MODULE_CONTEXT_DEFINE_INIT(quota_clone_user_module,
				  &mail_user_module_register);
static MODULE_CONTEXT_DEFINE_INIT(quota_clone_storage_module,
				  &mail_storage_module_register);

struct quota_clone_user {
	union mail_user_module_context module_ctx;
	struct dict *dict;
	bool quota_flushing;
};

struct quota_clone_mailbox {
	union mailbox_module_context module_ctx;
	bool quota_changed;
};

static void quota_clone_flush(struct mailbox *box)
{
	struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
	struct quota_clone_user *quser =
		QUOTA_CLONE_USER_CONTEXT(box->storage->user);
	struct dict_transaction_context *trans;
	struct quota_root_iter *iter;
	struct quota_root *root;
	uint64_t value, limit;
	int ret;

	/* we'll clone the first quota root */
	iter = quota_root_iter_init(box);
	root = quota_root_iter_next(iter);
	quota_root_iter_deinit(&iter);
	if (root == NULL) {
		/* no quota roots defined for this mailbox - ignore */
		qbox->quota_changed = FALSE;
		return;
	}

	trans = dict_transaction_begin(quser->dict);
	/* update bytes */
	ret = quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES,
				 &value, &limit);
	if (ret < 0)
		i_error("quota_clone_plugin: Failed to lookup current quota bytes");
	else {
		dict_set(trans, DICT_QUOTA_CLONE_BYTES_PATH,
			 t_strdup_printf("%llu", (unsigned long long)value));
	}
	/* update messages */
	ret = quota_get_resource(root, "", QUOTA_NAME_MESSAGES,
				 &value, &limit);
	if (ret < 0)
		i_error("quota_clone_plugin: Failed to lookup current quota count");
	else {
		dict_set(trans, DICT_QUOTA_CLONE_COUNT_PATH,
			 t_strdup_printf("%llu", (unsigned long long)value));
	}
	if (dict_transaction_commit(&trans) < 0)
		i_error("quota_clone_plugin: Failed to commit dict update");
	else
		qbox->quota_changed = FALSE;
}

static int quota_clone_save_finish(struct mail_save_context *ctx)
{
	struct quota_clone_mailbox *qbox =
		QUOTA_CLONE_CONTEXT(ctx->transaction->box);

	qbox->quota_changed = TRUE;
	return qbox->module_ctx.super.save_finish(ctx);
}

static int
quota_clone_copy(struct mail_save_context *ctx, struct mail *mail)
{
	struct quota_clone_mailbox *qbox =
		QUOTA_CLONE_CONTEXT(ctx->transaction->box);

	qbox->quota_changed = TRUE;
	return qbox->module_ctx.super.copy(ctx, mail);
}

static void
quota_clone_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
				enum mailbox_sync_type sync_type)
{
	struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);

	if (qbox->module_ctx.super.sync_notify != NULL)
		qbox->module_ctx.super.sync_notify(box, uid, sync_type);

	if (sync_type == MAILBOX_SYNC_TYPE_EXPUNGE)
		qbox->quota_changed = TRUE;
}

static void quota_clone_mailbox_close(struct mailbox *box)
{
	struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
	struct quota_clone_user *quser =
		QUOTA_CLONE_USER_CONTEXT(box->storage->user);

	qbox->module_ctx.super.close(box);

	if (quser->quota_flushing) {
		/* recursing back from quota recalculation */
	} else if (qbox->quota_changed) {
		quser->quota_flushing = TRUE;
		quota_clone_flush(box);
		quser->quota_flushing = FALSE;
	}
}

static void quota_clone_mailbox_allocated(struct mailbox *box)
{
	struct quota_clone_user *quser =
		QUOTA_CLONE_USER_CONTEXT(box->storage->user);
	struct mailbox_vfuncs *v = box->vlast;
	struct quota_clone_mailbox *qbox;

	if (quser == NULL)
		return;

	qbox = p_new(box->pool, struct quota_clone_mailbox, 1);
	qbox->module_ctx.super = *v;
	box->vlast = &qbox->module_ctx.super;

	v->save_finish = quota_clone_save_finish;
	v->copy = quota_clone_copy;
	v->sync_notify = quota_clone_mailbox_sync_notify;
	v->close = quota_clone_mailbox_close;
	MODULE_CONTEXT_SET(box, quota_clone_storage_module, qbox);
}

static void quota_clone_mail_user_deinit(struct mail_user *user)
{
	struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT(user);

	dict_deinit(&quser->dict);
	quser->module_ctx.super.deinit(user);
}

static void quota_clone_mail_user_created(struct mail_user *user)
{
	struct quota_clone_user *quser;
	struct mail_user_vfuncs *v = user->vlast;
	struct dict_settings dict_set;
	struct dict *dict;
	const char *uri, *error;

	uri = mail_user_plugin_getenv(user, "quota_clone_dict");
	if (uri == NULL || uri[0] == '\0') {
		i_error("The quota_clone_dict setting is missing from configuration");
		return;
	}

	memset(&dict_set, 0, sizeof(dict_set));
	dict_set.username = user->username;
	dict_set.base_dir = user->set->base_dir;
	(void)mail_user_get_home(user, &dict_set.home_dir);
	if (dict_init_full(uri, &dict_set, &dict, &error) < 0) {
		i_error("quota_clone_dict: Failed to initialize '%s': %s",
			uri, error);
		return;
	}

	quser = p_new(user->pool, struct quota_clone_user, 1);
	quser->module_ctx.super = *v;
	user->vlast = &quser->module_ctx.super;
	v->deinit = quota_clone_mail_user_deinit;
	quser->dict = dict;
	MODULE_CONTEXT_SET(user, quota_clone_user_module, quser);
}

static struct mail_storage_hooks quota_clone_mail_storage_hooks = {
	.mailbox_allocated = quota_clone_mailbox_allocated,
	.mail_user_created = quota_clone_mail_user_created
};

void quota_clone_plugin_init(struct module *module ATTR_UNUSED)
{
	mail_storage_hooks_add(module, &quota_clone_mail_storage_hooks);
}

void quota_clone_plugin_deinit(void)
{
	mail_storage_hooks_remove(&quota_clone_mail_storage_hooks);
}

const char *quota_clone_plugin_dependencies[] = { "quota", NULL };