[dovecot-cvs] dovecot/src/dict .cvsignore, NONE, 1.1 Makefile.am, NONE, 1.1 dict-cache.c, NONE, 1.1 dict-cache.h, NONE, 1.1 dict-server.c, NONE, 1.1 dict-server.h, NONE, 1.1 main.c, NONE, 1.1

cras at dovecot.org cras at dovecot.org
Sat Dec 31 00:09:05 EET 2005


Update of /var/lib/cvs/dovecot/src/dict
In directory talvi:/tmp/cvs-serv32553/src/dict

Added Files:
	.cvsignore Makefile.am dict-cache.c dict-cache.h dict-server.c 
	dict-server.h main.c 
Log Message:
Added dictinary lookup server and a "proxy" dict implementation to talk to
it.



--- NEW FILE: .cvsignore ---
*.la
*.lo
*.o
.deps
.libs
Makefile
Makefile.in
so_locations
dict

--- NEW FILE: Makefile.am ---
pkglibexecdir = $(libexecdir)/dovecot

pkglibexec_PROGRAMS = dict

AM_CPPFLAGS = \
	-I$(top_srcdir)/src/lib \
	-I$(top_srcdir)/src/lib-dict

libs = \
	../lib-dict/libdict.a \
	../lib-sql/libsql.a \
	../lib/liblib.a

dict_LDADD = \
	$(libs) \
	$(MODULE_LIBS) \
	$(SQL_LIBS)

dict_DEPENDENCIES = $(libs)

dict_SOURCES = \
	dict-cache.c \
	dict-server.c \
	main.c

noinst_HEADERS = \
	dict-cache.h \
	dict-server.h

--- NEW FILE: dict-cache.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "hash.h"
#include "dict.h"
#include "dict-cache.h"

struct dict_entry {
	int refcount;
	char *uri;
	struct dict *dict;
};

struct dict_cache {
	struct hash_table *dicts; /* uri -> struct dict_entry */
};

struct dict_cache *dict_cache_init(void)
{
	struct dict_cache *cache;

	cache = i_new(struct dict_cache, 1);
	cache->dicts = hash_create(default_pool, default_pool, 0, str_hash,
				   (hash_cmp_callback_t *)strcmp);
	return cache;
}

void dict_cache_deinit(struct dict_cache *cache)
{
	hash_destroy(cache->dicts);
	i_free(cache);
}

struct dict *dict_cache_get(struct dict_cache *cache, const char *uri)
{
	struct dict_entry *entry;

	entry = hash_lookup(cache->dicts, uri);
	if (entry == NULL) {
		entry = i_new(struct dict_entry, 1);
		entry->dict = dict_init(uri);
		entry->uri = i_strdup(uri);
	}
	entry->refcount++;
	return entry->dict;
}

void dict_cache_unref(struct dict_cache *cache, const char *uri)
{
	struct dict_entry *entry;

	entry = hash_lookup(cache->dicts, uri);
	i_assert(entry != NULL && entry->refcount > 0);

	if (--entry->refcount > 0)
		return;

	hash_remove(cache->dicts, uri);
	dict_deinit(entry->dict);
	i_free(entry->uri);
	i_free(entry);
}

--- NEW FILE: dict-cache.h ---
#ifndef __DICT_CACHE_H
#define __DICT_CACHE_H

struct dict_cache *dict_cache_init(void);
void dict_cache_deinit(struct dict_cache *cache);

struct dict *dict_cache_get(struct dict_cache *cache, const char *uri);
void dict_cache_unref(struct dict_cache *cache, const char *uri);

#endif

--- NEW FILE: dict-server.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "ioloop.h"
#include "network.h"
#include "istream.h"
#include "ostream.h"
#include "dict.h"
#include "dict-cache.h"
#include "dict-client.h"
#include "dict-server.h"

#include <stdlib.h>
#include <unistd.h>

struct dict_server_transaction {
	unsigned int id;
	struct dict_transaction_context *ctx;
};

struct dict_client_connection {
	struct dict_server *server;

	char *username;
	char *uri;
	struct dict *dict;

	int fd;
	struct io *io;
	struct istream *input;
	struct ostream *output;

	/* There are only a few transactions per client, so keeping them in
	   array is fast enough */
	array_t ARRAY_DEFINE(transactions, struct dict_server_transaction);
};

struct dict_server {
	char *path;
	int fd;
	struct io *io;

	struct dict_cache *cache;
};

struct dict_client_cmd {
	int cmd;
	int (*func)(struct dict_client_connection *conn, const char *line);
};

static void dict_client_connection_deinit(struct dict_client_connection *conn);

static int cmd_lookup(struct dict_client_connection *conn, const char *line)
{
	const char *reply;
	char *value;
	int ret;

	/* <key> */
	ret = dict_lookup(conn->dict, pool_datastack_create(), line, &value);
	if (ret > 0) {
		reply = t_strdup_printf("%c%s", DICT_PROTOCOL_REPLY_OK, value);
		o_stream_send_str(conn->output, reply);
	} else {
		reply = t_strdup_printf("%c", ret == 0 ?
					DICT_PROTOCOL_REPLY_NOTFOUND :
					DICT_PROTOCOL_REPLY_FAIL);
		o_stream_send_str(conn->output, reply);
	}
	return 0;
}

static int cmd_iterate(struct dict_client_connection *conn, const char *line)
{
	struct dict_iterate_context *ctx;
	const char *const *args;
	const char *reply, *key, *value;
	int ret;

	args = t_strsplit(line, "\t");
	if (strarray_length(args) != 2) {
		i_error("dict client: ITERATE: broken input");
		return -1;
	}

	/* <recurse 1/0> <path> */
	o_stream_cork(conn->output);
	ctx = dict_iterate_init(conn->dict, args[1], atoi(args[0]));
	while ((ret = dict_iterate(ctx, &key, &value)) > 0) {
		/* FIXME: we don't want to keep blocking here. set a flush
		   function and send the replies there when buffer gets full */
		t_push();
		reply = t_strdup_printf("%s\t%s", key, value);
		o_stream_send_str(conn->output, reply);
		t_pop();
	}
	dict_iterate_deinit(ctx);
	o_stream_uncork(conn->output);
	return 0;
}

static struct dict_server_transaction *
dict_server_transaction_lookup(struct dict_client_connection *conn,
			       unsigned int id)
{
	struct dict_server_transaction *transactions;
	unsigned int i, count;

	if (!array_is_created(&conn->transactions))
		return NULL;

	transactions = array_get_modifyable(&conn->transactions, &count);
	for (i = 0; i < count; i++) {
		if (transactions[i].id == id)
			return &transactions[i];
	}
	return NULL;
}

static void
dict_server_transaction_array_remove(struct dict_client_connection *conn,
				     struct dict_server_transaction *trans)
{
	const struct dict_server_transaction *transactions;
	unsigned int i, count;

	transactions = array_get(&conn->transactions, &count);
	for (i = 0; i < count; i++) {
		if (&transactions[i] == trans) {
			array_delete(&conn->transactions, i, 1);
			break;
		}
	}
}

static int cmd_begin(struct dict_client_connection *conn, const char *line)
{
	struct dict_server_transaction *trans;
	unsigned int id;

	if (!is_numeric(line, '\0')) {
		i_error("dict client: Invalid transaction ID %s", line);
		return -1;
	}

	id = (unsigned int)strtoul(line, NULL, 10);
	if (dict_server_transaction_lookup(conn, id) != NULL) {
		i_error("dict client: Transaction ID %u already exists", id);
		return -1;
	}

	if (!array_is_created(&conn->transactions)) {
		ARRAY_CREATE(&conn->transactions, default_pool,
			     struct dict_server_transaction, 4);
	}

	/* <id> */
	trans = array_append_space(&conn->transactions);
	trans->id = id;
	trans->ctx = dict_transaction_begin(conn->dict);
	return 0;
}

static int
dict_server_transaction_lookup_parse(struct dict_client_connection *conn,
				     const char *line,
				     struct dict_server_transaction **trans_r)
{
	unsigned int id;

	if (!is_numeric(line, '\0')) {
		i_error("dict client: Invalid transaction ID %s", line);
		return -1;
	}

	id = (unsigned int)strtoul(line, NULL, 10);
	*trans_r = dict_server_transaction_lookup(conn, id);
	if (*trans_r == NULL) {
		i_error("dict client: Transaction ID %u doesn't exists", id);
		return -1;
	}
	return 0;
}

static int cmd_commit(struct dict_client_connection *conn, const char *line)
{
	struct dict_server_transaction *trans;
	const char *reply;
	int ret;

	if (dict_server_transaction_lookup_parse(conn, line, &trans) < 0)
		return -1;

	ret = dict_transaction_commit(trans->ctx);
	reply = t_strdup_printf("%c", ret == 0 ? DICT_PROTOCOL_REPLY_OK :
				DICT_PROTOCOL_REPLY_FAIL);
	o_stream_send_str(conn->output, reply);
	dict_server_transaction_array_remove(conn, trans);
	return 0;
}

static int cmd_rollback(struct dict_client_connection *conn, const char *line)
{
	struct dict_server_transaction *trans;

	if (dict_server_transaction_lookup_parse(conn, line, &trans) < 0)
		return -1;

	dict_transaction_rollback(trans->ctx);
	dict_server_transaction_array_remove(conn, trans);
	return 0;
}

static int cmd_set(struct dict_client_connection *conn, const char *line)
{
	struct dict_server_transaction *trans;
	const char *const *args;

	/* <id> <key> <value> */
	args = t_strsplit(line, "\t");
	if (strarray_length(args) != 3) {
		i_error("dict client: SET: broken input");
		return -1;
	}

	if (dict_server_transaction_lookup_parse(conn, args[0], &trans) < 0)
		return -1;

        dict_set(trans->ctx, args[1], args[2]);
	return 0;
}

static int cmd_atomic_inc(struct dict_client_connection *conn, const char *line)
{
	struct dict_server_transaction *trans;
	const char *const *args;
	long long arg;

	/* <id> <key> <diff> */
	args = t_strsplit(line, "\t");
	if (strarray_length(args) != 3) {
		i_error("dict client: ATOMIC_INC: broken input");
		return -1;
	}

	if (dict_server_transaction_lookup_parse(conn, args[0], &trans) < 0)
		return -1;

	if (*args[2] != '-')
		arg = (long long)strtoull(args[2], NULL, 10);
	else
		arg = -(long long)strtoull(args[2]+1, NULL, 10);
        dict_atomic_inc(trans->ctx, args[1], arg);
	return 0;
}

static struct dict_client_cmd cmds[] = {
	{ DICT_PROTOCOL_CMD_LOOKUP, cmd_lookup },
	{ DICT_PROTOCOL_CMD_ITERATE, cmd_iterate },
	{ DICT_PROTOCOL_CMD_BEGIN, cmd_begin },
	{ DICT_PROTOCOL_CMD_COMMIT, cmd_commit },
	{ DICT_PROTOCOL_CMD_ROLLBACK, cmd_rollback },
	{ DICT_PROTOCOL_CMD_SET, cmd_set },
	{ DICT_PROTOCOL_CMD_ATOMIC_INC, cmd_atomic_inc },

	{ 0, NULL }
};

static int dict_client_parse_handshake(struct dict_client_connection *conn,
				       const char *line)
{
	const char *username;

	if (*line++ != DICT_PROTOCOL_CMD_HELLO)
		return -1;

	/* check major version */
	if (*line++ - '0' != DICT_CLIENT_PROTOCOL_MAJOR_VERSION ||
	    *line++ != '\t')
		return -1;

	/* skip minor version */
	while (*line != '\t' && *line != '\0') line++;

	if (*line++ != '\t')
		return -1;

	/* get username */
	username = line;
	while (*line != '\t' && *line != '\0') line++;

	if (*line++ != '\t')
		return -1;

	conn->username = i_strdup(username);

	/* the rest is dict URI */
	conn->uri = i_strdup(line);
	conn->dict = dict_cache_get(conn->server->cache, conn->uri);
	return 0;
}

static void dict_client_connection_input(void *context)
{
	struct dict_client_connection *conn = context;
	const char *line;
	unsigned int i;
	int ret;

	switch (i_stream_read(conn->input)) {
	case 0:
		return;
	case -1:
		/* disconnected */
		dict_client_connection_deinit(conn);
		return;
	case -2:
		/* buffer full */
		i_error("BUG: Dict client sent us more than %d bytes",
			(int)DICT_CLIENT_MAX_LINE_LENGTH);
		dict_client_connection_deinit(conn);
		return;
	}

	if (conn->username == NULL) {
		/* handshake not received yet */
		if ((line = i_stream_next_line(conn->input)) == NULL)
			return;

		if (dict_client_parse_handshake(conn, line) < 0) {
			dict_client_connection_deinit(conn);
			return;
		}
	}

	while ((line = i_stream_next_line(conn->input)) != NULL) {
		ret = 0;
		for (i = 0; cmds[i].cmd != '\0'; i++) {
			if (cmds[i].cmd == *line) {
				t_push();
				ret = cmds[i].func(conn, line + 1);
				t_pop();
				break;
			}
		}
		if (ret < 0) {
			dict_client_connection_deinit(conn);
			break;
		}
	}
}

static void dict_client_connection_deinit(struct dict_client_connection *conn)
{
	const struct dict_server_transaction *transactions;
	unsigned int i, count;

	transactions = array_get(&conn->transactions, &count);
	for (i = 0; i < count; i++)
                dict_transaction_rollback(transactions[i].ctx);
	array_free(&conn->transactions);

	io_remove(conn->io);
	i_stream_unref(conn->input);
	o_stream_unref(conn->output);
	if (close(conn->fd) < 0)
		i_error("close(dict client) failed: %m");

	if (conn->dict != NULL)
		dict_cache_unref(conn->server->cache, conn->uri);
	i_free(conn->uri);
	i_free(conn->username);
	i_free(conn);
}

static struct dict_client_connection *
dict_client_connection_init(struct dict_server *server, int fd)
{
	struct dict_client_connection *conn;

	conn = i_new(struct dict_client_connection, 1);
	conn->server = server;
	conn->fd = fd;
	conn->input = i_stream_create_file(fd, default_pool,
					   DICT_CLIENT_MAX_LINE_LENGTH, FALSE);
	conn->output = o_stream_create_file(fd, default_pool, 128*1024, FALSE);
	conn->io = io_add(fd, IO_READ, dict_client_connection_input, conn);
	return conn;
}

static void dict_server_listener_accept(void *context)
{
	struct dict_server *server = context;
	int fd;

	fd = net_accept(server->fd, NULL, NULL);
	if (fd < 0) {
		if (fd < -1)
			i_fatal("accept(%s) failed: %m", server->path);
	} else {
		net_set_nonblock(fd, TRUE);
		dict_client_connection_init(server, fd);
	}
}

struct dict_server *dict_server_init(const char *path)
{
	struct dict_server *server;

	server = i_new(struct dict_server, 1);
	server->path = i_strdup(path);
	server->fd = net_listen_unix(path, 64);
	if (server->fd == -1)
		i_fatal("net_listen_unix(%s) failed: %m", path);
	server->io = io_add(server->fd, IO_READ,
			    dict_server_listener_accept, server);
	server->cache = dict_cache_init();
	return server;
}

void dict_server_deinit(struct dict_server *server)
{
	dict_cache_deinit(server->cache);
	if (close(server->fd) < 0)
		i_error("close(%s) failed: %m", server->path);
	i_free(server->path);
	i_free(server);
}

--- NEW FILE: dict-server.h ---
#ifndef __DICT_SERVER_H
#define __DICT_SERVER_H

struct dict;

struct dict_server *dict_server_init(const char *path);
void dict_server_deinit(struct dict_server *server);

#endif

--- NEW FILE: main.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "lib-signals.h"
#include "ioloop.h"
#include "fd-close-on-exec.h"
#include "restrict-access.h"
#include "randgen.h"
#include "dict-sql.h"
#include "dict-client.h"
#include "dict-server.h"
#include "module-dir.h"

#include <stdlib.h>
#include <syslog.h>

#define DICT_SERVER_PATH "/var/run/dovecot/dict-server"

struct ioloop *ioloop;

static struct module *modules;
static struct dict_server *dict_server;

static void sig_die(int signo, void *context __attr_unused__)
{
	/* warn about being killed because of some signal, except SIGINT (^C)
	   which is too common at least while testing :) */
	if (signo != SIGINT)
		i_warning("Killed with signal %d", signo);
	io_loop_stop(ioloop);
}

static void drop_privileges(void)
{
	/* Log file or syslog opening probably requires roots */
	i_set_failure_internal();

	/* Maybe needed. Have to open /dev/urandom before possible
	   chrooting. */
	random_init();

	restrict_access_by_env(FALSE);
}

static void main_init(void)
{
	lib_signals_init();
        lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL);
        lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL);
        lib_signals_set_handler(SIGPIPE, FALSE, NULL, NULL);
        lib_signals_set_handler(SIGALRM, FALSE, NULL, NULL);

	dict_client_register();
	dict_sql_register();

	modules = getenv("MODULE_DIR") == NULL ? NULL :
		module_dir_load(getenv("MODULE_DIR"), TRUE);

	dict_server = dict_server_init(DICT_SERVER_PATH);
}

static void main_deinit(void)
{
	dict_server_deinit(dict_server);

	module_dir_unload(modules);

	dict_sql_unregister();
	dict_client_unregister();

	random_deinit();
	lib_signals_deinit();
	closelog();
}

int main(void)
{
#ifdef DEBUG
	if (getenv("LOGGED_IN") != NULL && getenv("GDB") == NULL)
		fd_debug_verify_leaks(3, 1024);
#endif

	/* NOTE: we start rooted, so keep the code minimal until
	   restrict_access_by_env() is called */
	lib_init();
	drop_privileges();

	ioloop = io_loop_create(system_pool);

	main_init();
        io_loop_run(ioloop);
	main_deinit();

	io_loop_destroy(ioloop);
	lib_deinit();

	return 0;
}



More information about the dovecot-cvs mailing list