view src/lib-dict/dict.c @ 22652:09523ad05bef

director: Log whenever HOST-RESET-USERS is used
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Sun, 05 Nov 2017 22:53:23 +0200
parents 16835a26b202
children cb108f786fb4
line wrap: on
line source

/* Copyright (c) 2005-2017 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "llist.h"
#include "str.h"
#include "dict-sql.h"
#include "dict-private.h"

static ARRAY(struct dict *) dict_drivers;

static struct dict *dict_driver_lookup(const char *name)
{
	struct dict *const *dicts;

	array_foreach(&dict_drivers, dicts) {
		struct dict *dict = *dicts;

		if (strcmp(dict->name, name) == 0)
			return dict;
	}
	return NULL;
}

void dict_driver_register(struct dict *driver)
{
	if (!array_is_created(&dict_drivers))
		i_array_init(&dict_drivers, 8);

	if (dict_driver_lookup(driver->name) != NULL) {
		i_fatal("dict_driver_register(%s): Already registered",
			driver->name);
	}
	array_append(&dict_drivers, &driver, 1);
}

void dict_driver_unregister(struct dict *driver)
{
	struct dict *const *dicts;
	unsigned int idx = UINT_MAX;

	array_foreach(&dict_drivers, dicts) {
		if (*dicts == driver) {
			idx = array_foreach_idx(&dict_drivers, dicts);
			break;
		}
	}
	i_assert(idx != UINT_MAX);
	array_delete(&dict_drivers, idx, 1);

	if (array_count(&dict_drivers) == 0)
		array_free(&dict_drivers);
}

int dict_init(const char *uri, enum dict_data_type value_type,
	      const char *username, const char *base_dir, struct dict **dict_r,
	      const char **error_r)
{
	struct dict_settings set;

	i_zero(&set);
	set.value_type = value_type;
	set.username = username;
	set.base_dir = base_dir;
	return dict_init_full(uri, &set, dict_r, error_r);
}

int dict_init_full(const char *uri, const struct dict_settings *set,
		   struct dict **dict_r, const char **error_r)
{
	struct dict *dict;
	const char *p, *name, *error;

	i_assert(set->username != NULL);

	p = strchr(uri, ':');
	if (p == NULL) {
		*error_r = t_strdup_printf("Dictionary URI is missing ':': %s",
					   uri);
		return -1;
	}

	name = t_strdup_until(uri, p);
	dict = dict_driver_lookup(name);
	if (dict == NULL) {
		*error_r = t_strdup_printf("Unknown dict module: %s", name);
		return -1;
	}
	if (dict->v.init(dict, p+1, set, dict_r, &error) < 0) {
		*error_r = t_strdup_printf("dict %s: %s", name, error);
		return -1;
	}
	i_assert(*dict_r != NULL);

	return 0;
}

void dict_deinit(struct dict **_dict)
{
	struct dict *dict = *_dict;

	*_dict = NULL;

	i_assert(dict->iter_count == 0);
	i_assert(dict->transaction_count == 0);
	i_assert(dict->transactions == NULL);

	dict->v.deinit(dict);
}

int dict_wait(struct dict *dict)
{
	return dict->v.wait == NULL ? 1 : dict->v.wait(dict);
}

bool dict_switch_ioloop(struct dict *dict)
{
	if (dict->v.switch_ioloop != NULL)
		return dict->v.switch_ioloop(dict);
	else
		return FALSE;
}

static bool dict_key_prefix_is_valid(const char *key)
{
	return strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0 ||
		strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE)) == 0;
}

int dict_lookup(struct dict *dict, pool_t pool, const char *key,
		const char **value_r)
{
	i_assert(dict_key_prefix_is_valid(key));
	return dict->v.lookup(dict, pool, key, value_r);
}

void dict_lookup_async(struct dict *dict, const char *key,
		       dict_lookup_callback_t *callback, void *context)
{
	if (dict->v.lookup_async == NULL) {
		struct dict_lookup_result result;

		i_zero(&result);
		result.ret = dict_lookup(dict, pool_datastack_create(),
					 key, &result.value);
		if (result.ret < 0)
			result.error = "Lookup failed";
		const char *const values[] = { result.value, NULL };
		result.values = values;
		callback(&result, context);
		return;
	}
	dict->v.lookup_async(dict, key, callback, context);
}

struct dict_iterate_context *
dict_iterate_init(struct dict *dict, const char *path, 
		  enum dict_iterate_flags flags)
{
	const char *paths[2];

	paths[0] = path;
	paths[1] = NULL;
	return dict_iterate_init_multiple(dict, paths, flags);
}

struct dict_iterate_context *
dict_iterate_init_multiple(struct dict *dict, const char *const *paths,
			   enum dict_iterate_flags flags)
{
	struct dict_iterate_context *ctx;
	unsigned int i;

	i_assert(paths[0] != NULL);
	for (i = 0; paths[i] != NULL; i++)
		i_assert(dict_key_prefix_is_valid(paths[i]));

	if (dict->v.iterate_init == NULL) {
		/* not supported by backend */
		i_error("%s: dict iteration not supported", dict->name);
		ctx = &dict_iter_unsupported;
	} else {
		ctx = dict->v.iterate_init(dict, paths, flags);
	}
	/* the dict in context can differ from the dict
	   passed as parameter, e.g. it can be dict-fail when
	   iteration is not supported. */
	ctx->dict->iter_count++;
	return ctx;
}

bool dict_iterate(struct dict_iterate_context *ctx,
		  const char **key_r, const char **value_r)
{
	if (ctx->max_rows > 0 && ctx->row_count >= ctx->max_rows) {
		/* row count was limited */
		ctx->has_more = FALSE;
		return FALSE;
	}
	if (!ctx->dict->v.iterate(ctx, key_r, value_r))
		return FALSE;
	ctx->row_count++;
	return TRUE;
}

void dict_iterate_set_async_callback(struct dict_iterate_context *ctx,
				     dict_iterate_callback_t *callback,
				     void *context)
{
	ctx->async_callback = callback;
	ctx->async_context = context;
}

void dict_iterate_set_limit(struct dict_iterate_context *ctx,
			    uint64_t max_rows)
{
	ctx->max_rows = max_rows;
}

bool dict_iterate_has_more(struct dict_iterate_context *ctx)
{
	return ctx->has_more;
}

int dict_iterate_deinit(struct dict_iterate_context **_ctx)
{
	struct dict_iterate_context *ctx = *_ctx;

	i_assert(ctx->dict->iter_count > 0);
	ctx->dict->iter_count--;

	*_ctx = NULL;
	return ctx->dict->v.iterate_deinit(ctx);
}

struct dict_transaction_context *dict_transaction_begin(struct dict *dict)
{
	struct dict_transaction_context *ctx;
	if (dict->v.transaction_init == NULL)
		ctx = &dict_transaction_unsupported;
	else
		ctx = dict->v.transaction_init(dict);
	/* the dict in context can differ from the dict
	   passed as parameter, e.g. it can be dict-fail when
	   transactions are not supported. */
	ctx->dict->transaction_count++;
	DLLIST_PREPEND(&ctx->dict->transactions, ctx);
	return ctx;
}

void dict_transaction_no_slowness_warning(struct dict_transaction_context *ctx)
{
	ctx->no_slowness_warning = TRUE;
}

void dict_transaction_set_timestamp(struct dict_transaction_context *ctx,
				    const struct timespec *ts)
{
	/* These asserts are mainly here to guarantee a possibility in future
	   to change the API to support multiple timestamps within the same
	   transaction, so this call would apply only to the following
	   changes. */
	i_assert(!ctx->changed);
	i_assert(ctx->timestamp.tv_sec == 0);
	i_assert(ts->tv_sec > 0);

	ctx->timestamp = *ts;
	if (ctx->dict->v.set_timestamp != NULL)
		ctx->dict->v.set_timestamp(ctx, ts);
}

int dict_transaction_commit(struct dict_transaction_context **_ctx)
{
	struct dict_transaction_context *ctx = *_ctx;

	*_ctx = NULL;
	i_assert(ctx->dict->transaction_count > 0);
	ctx->dict->transaction_count--;
	DLLIST_REMOVE(&ctx->dict->transactions, ctx);
	return ctx->dict->v.transaction_commit(ctx, FALSE, NULL, NULL);
}

void dict_transaction_commit_async(struct dict_transaction_context **_ctx,
				   dict_transaction_commit_callback_t *callback,
				   void *context)
{
	struct dict_transaction_context *ctx = *_ctx;

	*_ctx = NULL;
	i_assert(ctx->dict->transaction_count > 0);
	ctx->dict->transaction_count--;
	DLLIST_REMOVE(&ctx->dict->transactions, ctx);
	ctx->dict->v.transaction_commit(ctx, TRUE, callback, context);
}

void dict_transaction_rollback(struct dict_transaction_context **_ctx)
{
	struct dict_transaction_context *ctx = *_ctx;

	*_ctx = NULL;
	i_assert(ctx->dict->transaction_count > 0);
	ctx->dict->transaction_count--;
	DLLIST_REMOVE(&ctx->dict->transactions, ctx);
	ctx->dict->v.transaction_rollback(ctx);
}

void dict_set(struct dict_transaction_context *ctx,
	      const char *key, const char *value)
{
	i_assert(dict_key_prefix_is_valid(key));

	T_BEGIN {
		ctx->dict->v.set(ctx, key, value);
	} T_END;
	ctx->changed = TRUE;
}

void dict_unset(struct dict_transaction_context *ctx,
		const char *key)
{
	i_assert(dict_key_prefix_is_valid(key));

	T_BEGIN {
		ctx->dict->v.unset(ctx, key);
	} T_END;
	ctx->changed = TRUE;
}

void dict_append(struct dict_transaction_context *ctx,
		 const char *key, const char *value)
{
	i_assert(dict_key_prefix_is_valid(key));

	ctx->dict->v.append(ctx, key, value);
	ctx->changed = TRUE;
}

void dict_atomic_inc(struct dict_transaction_context *ctx,
		     const char *key, long long diff)
{
	i_assert(dict_key_prefix_is_valid(key));

	if (diff != 0) T_BEGIN {
		ctx->dict->v.atomic_inc(ctx, key, diff);
		ctx->changed = TRUE;
	} T_END;
}

const char *dict_escape_string(const char *str)
{
	const char *p;
	string_t *ret;

	/* see if we need to escape it */
	for (p = str; *p != '\0'; p++) {
		if (*p == '/' || *p == '\\')
			break;
	}

	if (*p == '\0')
		return str;

	/* escape */
	ret = t_str_new((size_t) (p - str) + 128);
	str_append_n(ret, str, (size_t) (p - str));

	for (; *p != '\0'; p++) {
		switch (*p) {
		case '/':
			str_append_c(ret, '\\');
			str_append_c(ret, '|');
			break;
		case '\\':
			str_append_c(ret, '\\');
			str_append_c(ret, '\\');
			break;
		default:
			str_append_c(ret, *p);
			break;
		}
	}
	return str_c(ret);
}

const char *dict_unescape_string(const char *str)
{
	const char *p;
	string_t *ret;

	/* see if we need to unescape it */
	for (p = str; *p != '\0'; p++) {
		if (*p == '\\')
			break;
	}

	if (*p == '\0')
		return str;

	/* unescape */
	ret = t_str_new((size_t) (p - str) + strlen(p) + 1);
	str_append_n(ret, str, (size_t) (p - str));

	for (; *p != '\0'; p++) {
		if (*p != '\\')
			str_append_c(ret, *p);
		else {
			if (*++p == '|')
				str_append_c(ret, '/');
			else if (*p == '\0')
				break;
			else
				str_append_c(ret, *p);
		}
	}
	return str_c(ret);
}