view src/lib-dict/dict.c @ 22325:e01bc3015b2f

lib-index: Check .log.2 rotation only when syncing Instead of also whenever appending transactions to .log file. This shouldn't change the behavior much, and it's needed for the following change to work correctly.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 11 Jul 2017 15:33:56 +0300
parents b74ab3872a80
children 16835a26b202
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));

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

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

	ctx->dict->v.unset(ctx, key);
	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) {
		ctx->dict->v.atomic_inc(ctx, key, diff);
		ctx->changed = TRUE;
	}
}

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);
}