]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
dict server: Forgot to add new files in previous commit.
authorTimo Sirainen <tss@iki.fi>
Fri, 29 May 2009 21:13:13 +0000 (17:13 -0400)
committerTimo Sirainen <tss@iki.fi>
Fri, 29 May 2009 21:13:13 +0000 (17:13 -0400)
--HG--
branch : HEAD

src/dict/dict-commands.c [new file with mode: 0644]
src/dict/dict-commands.h [new file with mode: 0644]
src/dict/dict-connection.c [new file with mode: 0644]
src/dict/dict-connection.h [new file with mode: 0644]

diff --git a/src/dict/dict-commands.c b/src/dict/dict-commands.c
new file mode 100644 (file)
index 0000000..52a3161
--- /dev/null
@@ -0,0 +1,332 @@
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ostream.h"
+#include "str.h"
+#include "dict-client.h"
+#include "dict-settings.h"
+#include "dict-connection.h"
+#include "dict-commands.h"
+
+#include <stdlib.h>
+
+#define DICT_OUTPUT_OPTIMAL_SIZE 1024
+
+struct dict_client_cmd {
+       int cmd;
+       int (*func)(struct dict_connection *conn, const char *line);
+};
+
+static int cmd_lookup(struct dict_connection *conn, const char *line)
+{
+       const char *reply;
+       const char *value;
+       int ret;
+
+       if (conn->iter_ctx != NULL) {
+               i_error("dict client: LOOKUP: Can't lookup while iterating");
+               return -1;
+       }
+
+       /* <key> */
+       ret = dict_lookup(conn->dict, pool_datastack_create(), line, &value);
+       if (ret > 0) {
+               reply = t_strdup_printf("%c%s\n",
+                                       DICT_PROTOCOL_REPLY_OK, value);
+               o_stream_send_str(conn->output, reply);
+       } else {
+               reply = t_strdup_printf("%c\n", ret == 0 ?
+                                       DICT_PROTOCOL_REPLY_NOTFOUND :
+                                       DICT_PROTOCOL_REPLY_FAIL);
+               o_stream_send_str(conn->output, reply);
+       }
+       return 0;
+}
+
+static int cmd_iterate_flush(struct dict_connection *conn)
+{
+       string_t *str;
+       const char *key, *value;
+       int ret;
+
+       str = t_str_new(256);
+       o_stream_cork(conn->output);
+       while ((ret = dict_iterate(conn->iter_ctx, &key, &value)) > 0) {
+               str_truncate(str, 0);
+               str_printfa(str, "%s\t%s\n", key, value);
+               o_stream_send(conn->output, str_data(str), str_len(str));
+
+               if (o_stream_get_buffer_used_size(conn->output) >
+                   DICT_OUTPUT_OPTIMAL_SIZE) {
+                       if (o_stream_flush(conn->output) <= 0)
+                               break;
+                       /* flushed everything, continue */
+               }
+       }
+
+       if (ret <= 0) {
+               /* finished iterating */
+               o_stream_unset_flush_callback(conn->output);
+               dict_iterate_deinit(&conn->iter_ctx);
+               o_stream_send(conn->output, "\n", 1);
+       }
+       o_stream_uncork(conn->output);
+       return ret <= 0 ? 1 : 0;
+}
+
+static int cmd_iterate(struct dict_connection *conn, const char *line)
+{
+       const char *const *args;
+
+       if (conn->iter_ctx != NULL) {
+               i_error("dict client: ITERATE: Already iterating");
+               return -1;
+       }
+
+       args = t_strsplit(line, "\t");
+       if (str_array_length(args) != 2) {
+               i_error("dict client: ITERATE: broken input");
+               return -1;
+       }
+
+       /* <flags> <path> */
+       conn->iter_ctx = dict_iterate_init(conn->dict, args[1], atoi(args[0]));
+
+       o_stream_set_flush_callback(conn->output, cmd_iterate_flush, conn);
+       cmd_iterate_flush(conn);
+       return 0;
+}
+
+static struct dict_connection_transaction *
+dict_connection_transaction_lookup(struct dict_connection *conn,
+                                  unsigned int id)
+{
+       struct dict_connection_transaction *transactions;
+       unsigned int i, count;
+
+       if (!array_is_created(&conn->transactions))
+               return NULL;
+
+       transactions = array_get_modifiable(&conn->transactions, &count);
+       for (i = 0; i < count; i++) {
+               if (transactions[i].id == id)
+                       return &transactions[i];
+       }
+       return NULL;
+}
+
+static void
+dict_connection_transaction_array_remove(struct dict_connection *conn,
+                                        struct dict_connection_transaction *trans)
+{
+       const struct dict_connection_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_connection *conn, const char *line)
+{
+       struct dict_connection_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_connection_transaction_lookup(conn, id) != NULL) {
+               i_error("dict client: Transaction ID %u already exists", id);
+               return -1;
+       }
+
+       if (!array_is_created(&conn->transactions))
+               i_array_init(&conn->transactions, 4);
+
+       /* <id> */
+       trans = array_append_space(&conn->transactions);
+       trans->id = id;
+       trans->ctx = dict_transaction_begin(conn->dict);
+       return 0;
+}
+
+static int
+dict_connection_transaction_lookup_parse(struct dict_connection *conn,
+                                        const char *line,
+                                        struct dict_connection_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_connection_transaction_lookup(conn, id);
+       if (*trans_r == NULL) {
+               i_error("dict client: Transaction ID %u doesn't exist", id);
+               return -1;
+       }
+       return 0;
+}
+
+static int cmd_commit(struct dict_connection *conn, const char *line)
+{
+       struct dict_connection_transaction *trans;
+       const char *reply;
+       int ret;
+
+       if (conn->iter_ctx != NULL) {
+               i_error("dict client: COMMIT: Can't commit while iterating");
+               return -1;
+       }
+
+       if (dict_connection_transaction_lookup_parse(conn, line, &trans) < 0)
+               return -1;
+
+       ret = dict_transaction_commit(&trans->ctx);
+       reply = t_strdup_printf("%c\n", ret == 0 ? DICT_PROTOCOL_REPLY_OK :
+                               DICT_PROTOCOL_REPLY_FAIL);
+       o_stream_send_str(conn->output, reply);
+       dict_connection_transaction_array_remove(conn, trans);
+       return 0;
+}
+
+static int
+cmd_commit_async(struct dict_connection *conn, const char *line)
+{
+       struct dict_connection_transaction *trans;
+
+       if (conn->iter_ctx != NULL) {
+               i_error("dict client: COMMIT: Can't commit while iterating");
+               return -1;
+       }
+
+       if (dict_connection_transaction_lookup_parse(conn, line, &trans) < 0)
+               return -1;
+
+       dict_transaction_commit_async(&trans->ctx);
+       dict_connection_transaction_array_remove(conn, trans);
+       return 0;
+}
+
+static int cmd_rollback(struct dict_connection *conn, const char *line)
+{
+       struct dict_connection_transaction *trans;
+
+       if (dict_connection_transaction_lookup_parse(conn, line, &trans) < 0)
+               return -1;
+
+       dict_transaction_rollback(&trans->ctx);
+       dict_connection_transaction_array_remove(conn, trans);
+       return 0;
+}
+
+static int cmd_set(struct dict_connection *conn, const char *line)
+{
+       struct dict_connection_transaction *trans;
+       const char *const *args;
+
+       /* <id> <key> <value> */
+       args = t_strsplit(line, "\t");
+       if (str_array_length(args) != 3) {
+               i_error("dict client: SET: broken input");
+               return -1;
+       }
+
+       if (dict_connection_transaction_lookup_parse(conn, args[0], &trans) < 0)
+               return -1;
+
+        dict_set(trans->ctx, args[1], args[2]);
+       return 0;
+}
+
+static int cmd_unset(struct dict_connection *conn, const char *line)
+{
+       struct dict_connection_transaction *trans;
+       const char *const *args;
+
+       /* <id> <key> */
+       args = t_strsplit(line, "\t");
+       if (str_array_length(args) != 2) {
+               i_error("dict client: UNSET: broken input");
+               return -1;
+       }
+
+       if (dict_connection_transaction_lookup_parse(conn, args[0], &trans) < 0)
+               return -1;
+
+        dict_unset(trans->ctx, args[1]);
+       return 0;
+}
+
+static int cmd_atomic_inc(struct dict_connection *conn, const char *line)
+{
+       struct dict_connection_transaction *trans;
+       const char *const *args;
+       long long arg;
+
+       /* <id> <key> <diff> */
+       args = t_strsplit(line, "\t");
+       if (str_array_length(args) != 3) {
+               i_error("dict client: ATOMIC_INC: broken input");
+               return -1;
+       }
+
+       if (dict_connection_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_COMMIT_ASYNC, cmd_commit_async },
+       { DICT_PROTOCOL_CMD_ROLLBACK, cmd_rollback },
+       { DICT_PROTOCOL_CMD_SET, cmd_set },
+       { DICT_PROTOCOL_CMD_UNSET, cmd_unset },
+       { DICT_PROTOCOL_CMD_ATOMIC_INC, cmd_atomic_inc },
+
+       { 0, NULL }
+};
+
+static struct dict_client_cmd *dict_command_find(char cmd)
+{
+       unsigned int i;
+
+       for (i = 0; cmds[i].cmd != '\0'; i++) {
+               if (cmds[i].cmd == cmd)
+                       return &cmds[i];
+       }
+       return NULL;
+}
+
+int dict_command_input(struct dict_connection *conn, const char *line)
+{
+       struct dict_client_cmd *cmd;
+
+       cmd = dict_command_find(*line);
+       if (cmd == NULL) {
+               i_error("dict client: Unknown command %c", *line);
+               return -1;
+       }
+
+       return cmd->func(conn, line + 1);
+}
diff --git a/src/dict/dict-commands.h b/src/dict/dict-commands.h
new file mode 100644 (file)
index 0000000..9c3afa8
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DICT_COMMANDS_H
+#define DICT_COMMANDS_H
+
+struct dict_connection;
+
+int dict_command_input(struct dict_connection *conn, const char *line);
+
+#endif
diff --git a/src/dict/dict-connection.c b/src/dict/dict-connection.c
new file mode 100644 (file)
index 0000000..a687752
--- /dev/null
@@ -0,0 +1,189 @@
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "llist.h"
+#include "dict-client.h"
+#include "dict-settings.h"
+#include "dict-commands.h"
+#include "dict-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+static struct dict_connection *dict_connections;
+
+static int dict_connection_parse_handshake(struct dict_connection *conn,
+                                          const char *line)
+{
+       const char *username, *name, *value_type;
+
+       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 value type */
+       value_type = line;
+       while (*line != '\t' && *line != '\0') line++;
+
+       if (*line++ != '\t')
+               return -1;
+       conn->value_type = atoi(t_strdup_until(value_type, line - 1));
+
+       /* get username */
+       username = line;
+       while (*line != '\t' && *line != '\0') line++;
+
+       if (*line++ != '\t')
+               return -1;
+       conn->username = i_strdup_until(username, line - 1);
+
+       /* the rest is dict name. since we're looking it with getenv(),
+          disallow all funny characters that might confuse it, just in case. */
+       name = line;
+       while (*line > ' ' && *line != '=') line++;
+
+       if (*line != '\0')
+               return -1;
+
+       conn->name = i_strdup(name);
+       return 0;
+}
+
+static int dict_connection_dict_init(struct dict_connection *conn)
+{
+       const char *const *strlist;
+       unsigned int i, count;
+       const char *uri;
+
+       strlist = array_get(&dict_settings->dicts, &count);
+       for (i = 0; i < count; i += 2) {
+               if (strcmp(strlist[i], conn->name) == 0)
+                       break;
+       }
+
+       if (i == count) {
+               i_error("dict client: Unconfigured dictionary name '%s'",
+                       conn->name);
+               return -1;
+       }
+       uri = strlist[i+1];
+
+       conn->dict = dict_init(uri, conn->value_type, conn->username);
+       if (conn->dict == NULL) {
+               /* dictionary initialization failed */
+               i_error("Failed to initialize dictionary '%s'", conn->name);
+               return -1;
+       }
+       return 0;
+}
+
+static void dict_connection_input(struct dict_connection *conn)
+{
+       const char *line;
+       int ret;
+
+       switch (i_stream_read(conn->input)) {
+       case 0:
+               return;
+       case -1:
+               /* disconnected */
+               dict_connection_destroy(conn);
+               return;
+       case -2:
+               /* buffer full */
+               i_error("dict client: Sent us more than %d bytes",
+                       (int)DICT_CLIENT_MAX_LINE_LENGTH);
+               dict_connection_destroy(conn);
+               return;
+       }
+
+       if (conn->username == NULL) {
+               /* handshake not received yet */
+               if ((line = i_stream_next_line(conn->input)) == NULL)
+                       return;
+
+               if (dict_connection_parse_handshake(conn, line) < 0) {
+                       i_error("dict client: Broken handshake");
+                       dict_connection_destroy(conn);
+                       return;
+               }
+               if (dict_connection_dict_init(conn)) {
+                       dict_connection_destroy(conn);
+                       return;
+               }
+       }
+
+       while ((line = i_stream_next_line(conn->input)) != NULL) {
+               T_BEGIN {
+                       ret = dict_command_input(conn, line);
+               } T_END;
+               if (ret < 0) {
+                       dict_connection_destroy(conn);
+                       break;
+               }
+       }
+}
+
+struct dict_connection *dict_connection_create(int fd)
+{
+       struct dict_connection *conn;
+
+       conn = i_new(struct dict_connection, 1);
+       conn->fd = fd;
+       conn->input = i_stream_create_fd(fd, DICT_CLIENT_MAX_LINE_LENGTH,
+                                        FALSE);
+       conn->output = o_stream_create_fd(fd, 128*1024, FALSE);
+       conn->io = io_add(fd, IO_READ, dict_connection_input, conn);
+       DLLIST_PREPEND(&dict_connections, conn);
+       return conn;
+}
+
+void dict_connection_destroy(struct dict_connection *conn)
+{
+       struct dict_connection_transaction *transactions;
+       unsigned int i, count;
+
+       DLLIST_REMOVE(&dict_connections, conn);
+
+       if (array_is_created(&conn->transactions)) {
+               transactions = array_get_modifiable(&conn->transactions, &count);
+               for (i = 0; i < count; i++)
+                       dict_transaction_rollback(&transactions[i].ctx);
+               array_free(&conn->transactions);
+       }
+
+       if (conn->iter_ctx != NULL)
+               dict_iterate_deinit(&conn->iter_ctx);
+
+       io_remove(&conn->io);
+       i_stream_destroy(&conn->input);
+       o_stream_destroy(&conn->output);
+       if (close(conn->fd) < 0)
+               i_error("close(dict client) failed: %m");
+
+       if (conn->dict != NULL)
+               dict_deinit(&conn->dict);
+       i_free(conn->name);
+       i_free(conn->username);
+       i_free(conn);
+}
+
+void dict_connections_destroy_all(void)
+{
+       while (dict_connections != NULL)
+               dict_connection_destroy(dict_connections);
+}
diff --git a/src/dict/dict-connection.h b/src/dict/dict-connection.h
new file mode 100644 (file)
index 0000000..fcac0da
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef DICT_CONNECTION_H
+#define DICT_CONNECTION_H
+
+#include "dict.h"
+
+struct dict_connection_transaction {
+       unsigned int id;
+       struct dict_transaction_context *ctx;
+};
+
+struct dict_connection {
+       struct dict_connection *prev, *next;
+       struct dict_server *server;
+
+       char *username;
+       char *name;
+       struct dict *dict;
+       enum dict_data_type value_type;
+
+       int fd;
+       struct io *io;
+       struct istream *input;
+       struct ostream *output;
+
+       struct dict_iterate_context *iter_ctx;
+
+       /* There are only a few transactions per client, so keeping them in
+          array is fast enough */
+       ARRAY_DEFINE(transactions, struct dict_connection_transaction);
+};
+
+struct dict_connection *dict_connection_create(int fd);
+void dict_connection_destroy(struct dict_connection *conn);
+
+void dict_connections_destroy_all(void);
+
+#endif