bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING redis */
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#include "lib.h"
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen#include "array.h"
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#include "str.h"
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#include "istream.h"
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#include "ostream.h"
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#include "connection.h"
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#include "dict-private.h"
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#define REDIS_DEFAULT_PORT 6379
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen#define REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen#define DICT_USERNAME_SEPARATOR '/'
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenenum redis_input_state {
c7555525421dfef0327974946bb22040e325db66Timo Sirainen /* expecting +OK reply for AUTH */
c7555525421dfef0327974946bb22040e325db66Timo Sirainen REDIS_INPUT_STATE_AUTH,
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen /* expecting +OK reply for SELECT */
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen REDIS_INPUT_STATE_SELECT,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* expecting $-1 / $<size> followed by GET reply */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen REDIS_INPUT_STATE_GET,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* expecting +QUEUED */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen REDIS_INPUT_STATE_MULTI,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* expecting +OK reply for DISCARD */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen REDIS_INPUT_STATE_DISCARD,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* expecting *<nreplies> */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen REDIS_INPUT_STATE_EXEC,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* expecting EXEC reply */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen REDIS_INPUT_STATE_EXEC_REPLY
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen};
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstruct redis_connection {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct connection conn;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct redis_dict *dict;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen string_t *last_reply;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen unsigned int bytes_left;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen bool value_not_found;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen bool value_received;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen};
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstruct redis_dict_reply {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen unsigned int reply_count;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict_transaction_commit_callback_t *callback;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen void *context;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen};
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstruct redis_dict {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct dict dict;
c7555525421dfef0327974946bb22040e325db66Timo Sirainen char *username, *password, *key_prefix, *expire_value;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen unsigned int timeout_msecs, db_id;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen struct ioloop *ioloop, *prev_ioloop;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct redis_connection conn;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen ARRAY(enum redis_input_state) input_states;
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen ARRAY(struct redis_dict_reply) replies;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen bool connected;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen bool transaction_open;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen bool db_id_set;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen};
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstruct redis_dict_transaction_context {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct dict_transaction_context ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen unsigned int cmd_count;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen char *error;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen};
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstatic struct connection_list *redis_connections;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenredis_input_state_add(struct redis_dict *dict, enum redis_input_state state)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_append(&dict->input_states, &state, 1);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_input_state_remove(struct redis_dict *dict)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_delete(&dict->input_states, 0, 1);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainenstatic void redis_callback(struct redis_dict *dict,
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const struct redis_dict_reply *reply,
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const struct dict_commit_result *result)
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen{
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen if (reply->callback != NULL) {
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen if (dict->prev_ioloop != NULL) {
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen /* Don't let callback see that we've created our
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen internal ioloop in case it wants to add some ios
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen or timeouts. */
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen current_ioloop = dict->prev_ioloop;
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen }
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen reply->callback(result, reply->context);
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen if (dict->prev_ioloop != NULL)
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen current_ioloop = dict->ioloop;
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen }
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen}
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic void
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenredis_disconnected(struct redis_connection *conn, const char *reason)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen{
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen const struct dict_commit_result result = {
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen DICT_COMMIT_RET_FAILED, reason
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen };
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const struct redis_dict_reply *reply;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen conn->dict->db_id_set = FALSE;
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen conn->dict->connected = FALSE;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen connection_disconnect(&conn->conn);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen array_foreach(&conn->dict->replies, reply)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_callback(conn->dict, reply, &result);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_clear(&conn->dict->replies);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_clear(&conn->dict->input_states);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (conn->dict->ioloop != NULL)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen io_loop_stop(conn->dict->ioloop);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen}
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic void redis_conn_destroy(struct connection *_conn)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen{
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen struct redis_connection *conn = (struct redis_connection *)_conn;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(conn, connection_disconnect_reason(_conn));
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen}
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainenstatic void redis_dict_wait_timeout(struct redis_dict *dict)
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen{
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const char *reason = t_strdup_printf(
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen "redis: Commit timed out in %u.%03u secs",
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen dict->timeout_msecs/1000, dict->timeout_msecs%1000);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(&dict->conn, reason);
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen}
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_wait(struct redis_dict *dict)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen struct timeout *to;
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(dict->ioloop == NULL);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen dict->prev_ioloop = current_ioloop;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict->ioloop = io_loop_create();
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen to = timeout_add(dict->timeout_msecs, redis_dict_wait_timeout, dict);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen connection_switch_ioloop(&dict->conn.conn);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen do {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen io_loop_run(dict->ioloop);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen } while (array_count(&dict->input_states) > 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
c935c4494b7fe73d84bd546c6d702f0e79b9fabaTimo Sirainen timeout_remove(&to);
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen io_loop_set_current(dict->prev_ioloop);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen connection_switch_ioloop(&dict->conn.conn);
35f3b7e05afecacd0332c210c6e253911c2813d8Timo Sirainen io_loop_set_current(dict->ioloop);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen io_loop_destroy(&dict->ioloop);
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen dict->prev_ioloop = NULL;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic int redis_input_get(struct redis_connection *conn, const char **error_r)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen{
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen const unsigned char *data;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen size_t size;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen const char *line;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (conn->bytes_left == 0) {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen /* read the size first */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen line = i_stream_next_line(conn->conn.input);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (line == NULL)
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen return 0;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (strcmp(line, "$-1") == 0) {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen conn->value_received = TRUE;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen conn->value_not_found = TRUE;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (conn->dict->ioloop != NULL)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen io_loop_stop(conn->dict->ioloop);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_remove(conn->dict);
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen return 1;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (line[0] != '$' || str_to_uint(line+1, &conn->bytes_left) < 0) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen *error_r = t_strdup_printf(
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen "redis: Unexpected input (wanted $size): %s", line);
c3f05842d124c798afdcf67ccf29d8fe29ef2316Timo Sirainen return -1;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen conn->bytes_left += 2; /* include trailing CRLF */
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen data = i_stream_get_data(conn->conn.input, &size);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (size > conn->bytes_left)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen size = conn->bytes_left;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen str_append_n(conn->last_reply, data, size);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen conn->bytes_left -= size;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_stream_skip(conn->conn.input, size);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen if (conn->bytes_left > 0)
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen return 0;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen /* reply fully read - drop trailing CRLF */
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen conn->value_received = TRUE;
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen str_truncate(conn->last_reply, str_len(conn->last_reply)-2);
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen if (conn->dict->ioloop != NULL)
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen io_loop_stop(conn->dict->ioloop);
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen redis_input_state_remove(conn->dict);
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen return 1;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen}
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic int
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenredis_conn_input_more(struct redis_connection *conn, const char **error_r)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = conn->dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_reply *reply;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const enum redis_input_state *states;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen enum redis_input_state state;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen unsigned int count, num_replies;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const char *line;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen states = array_get(&dict->input_states, &count);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (count == 0) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen line = i_stream_next_line(conn->conn.input);
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen if (line == NULL)
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen return 0;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen *error_r = t_strdup_printf(
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen "redis: Unexpected input (expected nothing): %s", line);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return -1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen state = states[0];
531ddc880d8c8d1d3b4b6c5cf8abb1abb4a2e0daTimo Sirainen if (state == REDIS_INPUT_STATE_GET)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen return redis_input_get(conn, error_r);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen line = i_stream_next_line(conn->conn.input);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (line == NULL)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return 0;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_remove(dict);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen switch (state) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case REDIS_INPUT_STATE_GET:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_unreached();
c7555525421dfef0327974946bb22040e325db66Timo Sirainen case REDIS_INPUT_STATE_AUTH:
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen case REDIS_INPUT_STATE_SELECT:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case REDIS_INPUT_STATE_MULTI:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case REDIS_INPUT_STATE_DISCARD:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (line[0] != '+')
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen break;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return 1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case REDIS_INPUT_STATE_EXEC:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (line[0] != '*' || str_to_uint(line+1, &num_replies) < 0)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen break;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply = array_idx_modifiable(&dict->replies, 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(reply->reply_count > 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (reply->reply_count != num_replies) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen *error_r = t_strdup_printf(
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen "redis: EXEC expected %u replies, not %u",
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply->reply_count, num_replies);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return -1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return 1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case REDIS_INPUT_STATE_EXEC_REPLY:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (*line != '+' && *line != ':')
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen break;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* success, just ignore the actual reply */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply = array_idx_modifiable(&dict->replies, 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(reply->reply_count > 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (--reply->reply_count == 0) {
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen const struct dict_commit_result result = {
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen DICT_COMMIT_RET_OK, NULL
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen };
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_callback(dict, reply, &result);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_delete(&dict->replies, 0, 1);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* if we're running in a dict-ioloop, we're handling a
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen synchronous commit and need to stop now */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (array_count(&dict->replies) == 0 &&
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen conn->dict->ioloop != NULL)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen io_loop_stop(conn->dict->ioloop);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return 1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
2644ab25367d56f775540e3539886d54a8453d61Aki Tuomi str_truncate(dict->conn.last_reply, 0);
2644ab25367d56f775540e3539886d54a8453d61Aki Tuomi str_append(dict->conn.last_reply, line);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen *error_r = t_strdup_printf("redis: Unexpected input (state=%d): %s", state, line);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return -1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_conn_input(struct connection *_conn)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_connection *conn = (struct redis_connection *)_conn;
3fdae9cc0f5d6ce05afdaf1e2ab35fb5dbdf0d17Timo Sirainen const char *error = NULL;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen int ret;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen switch (i_stream_read(_conn->input)) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case 0:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case -1:
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(conn, i_stream_get_error(_conn->input));
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen default:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen break;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen while ((ret = redis_conn_input_more(conn, &error)) > 0) ;
3fdae9cc0f5d6ce05afdaf1e2ab35fb5dbdf0d17Timo Sirainen if (ret < 0) {
3fdae9cc0f5d6ce05afdaf1e2ab35fb5dbdf0d17Timo Sirainen i_assert(error != NULL);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(conn, error);
3fdae9cc0f5d6ce05afdaf1e2ab35fb5dbdf0d17Timo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
7db7fbea5d8a07463b625f93d69166d56018dadfTimo Sirainenstatic void redis_conn_connected(struct connection *_conn, bool success)
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen{
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen struct redis_connection *conn = (struct redis_connection *)_conn;
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen
7db7fbea5d8a07463b625f93d69166d56018dadfTimo Sirainen if (!success) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen i_error("redis: connect(%s) failed: %m", _conn->name);
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen } else {
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen conn->dict->connected = TRUE;
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen }
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen if (conn->dict->ioloop != NULL)
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen io_loop_stop(conn->dict->ioloop);
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen}
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstatic const struct connection_settings redis_conn_set = {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen .input_max_size = (size_t)-1,
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen .output_max_size = (size_t)-1,
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen .client = TRUE
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen};
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstatic const struct connection_vfuncs redis_conn_vfuncs = {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen .destroy = redis_conn_destroy,
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen .input = redis_conn_input,
7db7fbea5d8a07463b625f93d69166d56018dadfTimo Sirainen .client_connected = redis_conn_connected
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen};
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic const char *redis_escape_username(const char *username)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const char *p;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen string_t *str = t_str_new(64);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen for (p = username; *p != '\0'; p++) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen switch (*p) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case DICT_USERNAME_SEPARATOR:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen str_append(str, "\\-");
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen break;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen case '\\':
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen str_append(str, "\\\\");
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen break;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen default:
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen str_append_c(str, *p);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return str_c(str);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
10399559650f552a23949772be79eb6a80198c5aTimo Sirainenstatic int
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenredis_dict_init(struct dict *driver, const char *uri,
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen const struct dict_settings *set,
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen struct dict **dict_r, const char **error_r)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen{
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct redis_dict *dict;
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen struct ip_addr ip;
009217abb57a24a4076092e8e4e165545747839eStephan Bosch unsigned int secs;
009217abb57a24a4076092e8e4e165545747839eStephan Bosch in_port_t port = REDIS_DEFAULT_PORT;
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen const char *const *args, *unix_path = NULL;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen int ret = 0;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (redis_connections == NULL) {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen redis_connections =
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection_list_init(&redis_conn_set,
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen &redis_conn_vfuncs);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict = i_new(struct redis_dict, 1);
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen if (net_addr2ip("127.0.0.1", &ip) < 0)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen i_unreached();
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->timeout_msecs = REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict->key_prefix = i_strdup("");
c7555525421dfef0327974946bb22040e325db66Timo Sirainen dict->password = i_strdup("");
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen args = t_strsplit(uri, ":");
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen for (; *args != NULL; args++) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen if (strncmp(*args, "path=", 5) == 0) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen unix_path = *args + 5;
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen } else if (strncmp(*args, "host=", 5) == 0) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen if (net_addr2ip(*args+5, &ip) < 0) {
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *error_r = t_strdup_printf("Invalid IP: %s",
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *args+5);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen ret = -1;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen } else if (strncmp(*args, "port=", 5) == 0) {
009217abb57a24a4076092e8e4e165545747839eStephan Bosch if (net_str2port(*args+5, &port) < 0) {
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *error_r = t_strdup_printf("Invalid port: %s",
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *args+5);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen ret = -1;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen } else if (strncmp(*args, "prefix=", 7) == 0) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_free(dict->key_prefix);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict->key_prefix = i_strdup(*args + 7);
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen } else if (strncmp(*args, "db=", 3) == 0) {
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen if (str_to_uint(*args+3, &dict->db_id) < 0) {
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen *error_r = t_strdup_printf(
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen "Invalid db number: %s", *args+3);
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen ret = -1;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen }
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen } else if (strncmp(*args, "expire_secs=", 12) == 0) {
2480bdc0a4fbb75aad5408fd9f344556fc605082Timo Sirainen const char *value = *args + 12;
2480bdc0a4fbb75aad5408fd9f344556fc605082Timo Sirainen
2480bdc0a4fbb75aad5408fd9f344556fc605082Timo Sirainen if (str_to_uint(value, &secs) < 0 || secs == 0) {
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen *error_r = t_strdup_printf(
2480bdc0a4fbb75aad5408fd9f344556fc605082Timo Sirainen "Invalid expire_secs: %s", value);
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen ret = -1;
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen }
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen i_free(dict->expire_value);
2480bdc0a4fbb75aad5408fd9f344556fc605082Timo Sirainen dict->expire_value = i_strdup(value);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen } else if (strncmp(*args, "timeout_msecs=", 14) == 0) {
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen if (str_to_uint(*args+14, &dict->timeout_msecs) < 0) {
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *error_r = t_strdup_printf(
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen "Invalid timeout_msecs: %s", *args+14);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen ret = -1;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen }
c7555525421dfef0327974946bb22040e325db66Timo Sirainen } else if (strncmp(*args, "password=", 9) == 0) {
c7555525421dfef0327974946bb22040e325db66Timo Sirainen i_free(dict->password);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen dict->password = i_strdup(*args + 9);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen } else {
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *error_r = t_strdup_printf("Unknown parameter: %s",
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *args);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen ret = -1;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen if (ret < 0) {
c7555525421dfef0327974946bb22040e325db66Timo Sirainen i_free(dict->password);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen i_free(dict->key_prefix);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen i_free(dict);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen return -1;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen }
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen if (unix_path != NULL) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen connection_init_client_unix(redis_connections, &dict->conn.conn,
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen unix_path);
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen } else {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen connection_init_client_ip(redis_connections, &dict->conn.conn,
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen &ip, port);
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->dict = *driver;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->conn.last_reply = str_new(default_pool, 256);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->conn.dict = dict;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen i_array_init(&dict->input_states, 4);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen i_array_init(&dict->replies, 4);
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen if (strchr(set->username, DICT_USERNAME_SEPARATOR) == NULL)
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen dict->username = i_strdup(set->username);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen else {
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen /* escape the username */
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen dict->username = i_strdup(redis_escape_username(set->username));
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen }
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen
10399559650f552a23949772be79eb6a80198c5aTimo Sirainen *dict_r = &dict->dict;
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen return 0;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen}
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstatic void redis_dict_deinit(struct dict *_dict)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen{
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct redis_dict *dict = (struct redis_dict *)_dict;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (array_count(&dict->input_states) > 0) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(dict->connected);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_wait(dict);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection_deinit(&dict->conn.conn);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen str_free(&dict->conn.last_reply);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_free(&dict->replies);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen array_free(&dict->input_states);
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen i_free(dict->expire_value);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_free(dict->key_prefix);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen i_free(dict->password);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_free(dict->username);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen i_free(dict);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (redis_connections->connections == NULL)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection_list_deinit(&redis_connections);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen}
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstatic void redis_dict_lookup_timeout(struct redis_dict *dict)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen{
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const char *reason = t_strdup_printf(
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen "redis: Lookup timed out in %u.%03u secs",
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen dict->timeout_msecs/1000, dict->timeout_msecs%1000);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(&dict->conn, reason);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen}
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic const char *
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenredis_dict_get_full_key(struct redis_dict *dict, const char *key)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key += strlen(DICT_PATH_SHARED);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen else if (strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE)) == 0) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key = t_strdup_printf("%s%c%s", dict->username,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen DICT_USERNAME_SEPARATOR,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key + strlen(DICT_PATH_PRIVATE));
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen } else {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_unreached();
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (*dict->key_prefix != '\0')
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key = t_strconcat(dict->key_prefix, key, NULL);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return key;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
c7555525421dfef0327974946bb22040e325db66Timo Sirainenstatic void redis_dict_auth(struct redis_dict *dict)
c7555525421dfef0327974946bb22040e325db66Timo Sirainen{
c7555525421dfef0327974946bb22040e325db66Timo Sirainen const char *cmd;
c7555525421dfef0327974946bb22040e325db66Timo Sirainen
c7555525421dfef0327974946bb22040e325db66Timo Sirainen if (*dict->password == '\0')
c7555525421dfef0327974946bb22040e325db66Timo Sirainen return;
c7555525421dfef0327974946bb22040e325db66Timo Sirainen
c7555525421dfef0327974946bb22040e325db66Timo Sirainen cmd = t_strdup_printf("*2\r\n$4\r\nAUTH\r\n$%d\r\n%s\r\n",
c7555525421dfef0327974946bb22040e325db66Timo Sirainen (int)strlen(dict->password), dict->password);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen o_stream_nsend_str(dict->conn.conn.output, cmd);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_AUTH);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen}
c7555525421dfef0327974946bb22040e325db66Timo Sirainen
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainenstatic void redis_dict_select_db(struct redis_dict *dict)
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen{
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen const char *cmd, *db_str;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen if (dict->db_id_set)
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen return;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen dict->db_id_set = TRUE;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen if (dict->db_id == 0) {
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen /* 0 is the default */
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen return;
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen }
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen db_str = dec2str(dict->db_id);
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen cmd = t_strdup_printf("*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen (int)strlen(db_str), db_str);
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen o_stream_nsend_str(dict->conn.conn.output, cmd);
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_SELECT);
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen}
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int redis_dict_lookup(struct dict *_dict, pool_t pool, const char *key,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **value_r, const char **error_r)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen{
8f9a18189f01448267100fa54c3b4bb8639a1a56Timo Sirainen struct redis_dict *dict = (struct redis_dict *)_dict;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen struct timeout *to;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen const char *cmd;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key = redis_dict_get_full_key(dict, key);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->conn.value_received = FALSE;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->conn.value_not_found = FALSE;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(dict->ioloop == NULL);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen dict->prev_ioloop = current_ioloop;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen dict->ioloop = io_loop_create();
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection_switch_ioloop(&dict->conn.conn);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (dict->conn.conn.fd_in == -1 &&
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection_client_connect(&dict->conn.conn) < 0) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen i_error("redis: Couldn't connect to %s", dict->conn.conn.name);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen } else {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen to = timeout_add(dict->timeout_msecs,
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen redis_dict_lookup_timeout, dict);
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen if (!dict->connected) {
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen /* wait for connection */
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen io_loop_run(dict->ioloop);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen if (dict->connected)
c7555525421dfef0327974946bb22040e325db66Timo Sirainen redis_dict_auth(dict);
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen }
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen if (dict->connected) {
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen redis_dict_select_db(dict);
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen cmd = t_strdup_printf("*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n",
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen (int)strlen(key), key);
1d2c463d23f09f15727edae9c78b07ec6a7a27daTimo Sirainen o_stream_nsend_str(dict->conn.conn.output, cmd);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen str_truncate(dict->conn.last_reply, 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_GET);
97d5d23d41aab07e0e7d98c1f9b834e325d7d1b3Timo Sirainen do {
97d5d23d41aab07e0e7d98c1f9b834e325d7d1b3Timo Sirainen io_loop_run(dict->ioloop);
97d5d23d41aab07e0e7d98c1f9b834e325d7d1b3Timo Sirainen } while (array_count(&dict->input_states) > 0);
aa8e63009c5aa3866bbf5a3e69a86b1ab480c4ddTimo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen timeout_remove(&to);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen io_loop_set_current(dict->prev_ioloop);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection_switch_ioloop(&dict->conn.conn);
35f3b7e05afecacd0332c210c6e253911c2813d8Timo Sirainen io_loop_set_current(dict->ioloop);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen io_loop_destroy(&dict->ioloop);
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainen dict->prev_ioloop = NULL;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (!dict->conn.value_received) {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen /* we failed in some way. make sure we disconnect since the
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen connection state isn't known anymore */
2644ab25367d56f775540e3539886d54a8453d61Aki Tuomi *error_r = t_strdup_printf("redis: Communication failure (last reply: %s)",
2644ab25367d56f775540e3539886d54a8453d61Aki Tuomi str_c(dict->conn.last_reply));
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(&dict->conn, *error_r);
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen return -1;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen if (dict->conn.value_not_found)
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen return 0;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen *value_r = p_strdup(pool, str_c(dict->conn.last_reply));
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen return 1;
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen}
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic struct dict_transaction_context *
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenredis_transaction_init(struct dict *_dict)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)_dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_transaction_context *ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(!dict->transaction_open);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict->transaction_open = TRUE;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen ctx = i_new(struct redis_dict_transaction_context, 1);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen ctx->ctx.dict = _dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (dict->conn.conn.fd_in == -1 &&
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen connection_client_connect(&dict->conn.conn) < 0) {
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen i_error("redis: Couldn't connect to %s",
ba9ec8849b975c50aa60b7f661797415fac70ce5Timo Sirainen dict->conn.conn.name);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen } else if (!dict->connected) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* wait for connection */
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_wait(dict);
c7555525421dfef0327974946bb22040e325db66Timo Sirainen if (dict->connected)
c7555525421dfef0327974946bb22040e325db66Timo Sirainen redis_dict_auth(dict);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen if (dict->connected)
640d320445eaad7f7ad834299a2726457f35f434Timo Sirainen redis_dict_select_db(dict);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return &ctx->ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
c2a66e7950cb4d3fc4d68e4480ea8f39bdd7c871Timo Sirainenstatic void
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenredis_transaction_commit(struct dict_transaction_context *_ctx, bool async,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict_transaction_commit_callback_t *callback,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen void *context)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_transaction_context *ctx =
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen (struct redis_dict_transaction_context *)_ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_reply *reply;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen unsigned int i;
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen struct dict_commit_result result = { .ret = DICT_COMMIT_RET_OK };
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(dict->transaction_open);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict->transaction_open = FALSE;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (ctx->error != NULL) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* make sure we're disconnected */
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(&dict->conn, ctx->error);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen result.ret = -1;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen result.error = ctx->error;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen } else if (_ctx->changed) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(ctx->cmd_count > 0);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1d2c463d23f09f15727edae9c78b07ec6a7a27daTimo Sirainen o_stream_nsend_str(dict->conn.conn.output,
1d2c463d23f09f15727edae9c78b07ec6a7a27daTimo Sirainen "*1\r\n$4\r\nEXEC\r\n");
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply = array_append_space(&dict->replies);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply->callback = callback;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply->context = context;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply->reply_count = ctx->cmd_count;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen for (i = 0; i < ctx->cmd_count; i++)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC_REPLY);
ff3c2a32a68136152f6771c5c4448cc0026476cdTimo Sirainen if (async) {
ff3c2a32a68136152f6771c5c4448cc0026476cdTimo Sirainen i_free(ctx);
c2a66e7950cb4d3fc4d68e4480ea8f39bdd7c871Timo Sirainen return;
ff3c2a32a68136152f6771c5c4448cc0026476cdTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_wait(dict);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen callback(&result, context);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen i_free(ctx->error);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_free(ctx);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_transaction_rollback(struct dict_transaction_context *_ctx)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_transaction_context *ctx =
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen (struct redis_dict_transaction_context *)_ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_reply *reply;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_assert(dict->transaction_open);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen dict->transaction_open = FALSE;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (ctx->error != NULL) {
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen /* make sure we're disconnected */
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen redis_disconnected(&dict->conn, ctx->error);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen } else if (_ctx->changed) {
1d2c463d23f09f15727edae9c78b07ec6a7a27daTimo Sirainen o_stream_nsend_str(dict->conn.conn.output,
1d2c463d23f09f15727edae9c78b07ec6a7a27daTimo Sirainen "*1\r\n$7\r\nDISCARD\r\n");
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply = array_append_space(&dict->replies);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen reply->reply_count = 1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_DISCARD);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen i_free(ctx->error);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen i_free(ctx);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic int redis_check_transaction(struct redis_dict_transaction_context *ctx)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (ctx->error != NULL)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return -1;
6ac32bfcdcd9e96e9b6614914fc4b0a926dcfa69Timo Sirainen if (!dict->connected) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen ctx->error = i_strdup("Disconnected during transaction");
6ac32bfcdcd9e96e9b6614914fc4b0a926dcfa69Timo Sirainen return -1;
6ac32bfcdcd9e96e9b6614914fc4b0a926dcfa69Timo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (ctx->ctx.changed)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return 0;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (o_stream_send_str(dict->conn.conn.output,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen "*1\r\n$5\r\nMULTI\r\n") < 0) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen ctx->error = i_strdup_printf("write() failed: %s",
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen o_stream_get_error(dict->conn.conn.output));
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return -1;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return 0;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainenstatic void
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainenredis_append_expire(struct redis_dict_transaction_context *ctx,
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen string_t *cmd, const char *key)
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen{
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen if (dict->expire_value == NULL)
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen return;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen str_printfa(cmd, "*3\r\n$6\r\nEXPIRE\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n",
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen (unsigned int)strlen(key), key,
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen (unsigned int)strlen(dict->expire_value),
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen dict->expire_value);
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen ctx->cmd_count++;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen}
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_set(struct dict_transaction_context *_ctx,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const char *key, const char *value)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_transaction_context *ctx =
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen (struct redis_dict_transaction_context *)_ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen string_t *cmd;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (redis_check_transaction(ctx) < 0)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key = redis_dict_get_full_key(dict, key);
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen cmd = t_str_new(128);
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen str_printfa(cmd, "*3\r\n$3\r\nSET\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n",
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen (unsigned int)strlen(key), key,
e85a0dac7542bbae17e81e735f88897fff86becbTimo Sirainen (unsigned int)strlen(value), value);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen ctx->cmd_count++;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen redis_append_expire(ctx, cmd, key);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (o_stream_send(dict->conn.conn.output, str_data(cmd), str_len(cmd)) < 0) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen ctx->error = i_strdup_printf("write() failed: %s",
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen o_stream_get_error(dict->conn.conn.output));
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_unset(struct dict_transaction_context *_ctx,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const char *key)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_transaction_context *ctx =
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen (struct redis_dict_transaction_context *)_ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const char *cmd;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (redis_check_transaction(ctx) < 0)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key = redis_dict_get_full_key(dict, key);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen cmd = t_strdup_printf("*2\r\n$3\r\nDEL\r\n$%u\r\n%s\r\n",
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen (unsigned int)strlen(key), key);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (o_stream_send_str(dict->conn.conn.output, cmd) < 0) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen ctx->error = i_strdup_printf("write() failed: %s",
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen o_stream_get_error(dict->conn.conn.output));
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen ctx->cmd_count++;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainenstatic void redis_atomic_inc(struct dict_transaction_context *_ctx,
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen const char *key, long long diff)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen{
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict_transaction_context *ctx =
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen (struct redis_dict_transaction_context *)_ctx;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen const char *diffstr;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen string_t *cmd;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen if (redis_check_transaction(ctx) < 0)
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen return;
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen key = redis_dict_get_full_key(dict, key);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen diffstr = t_strdup_printf("%lld", diff);
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen cmd = t_str_new(128);
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen str_printfa(cmd, "*3\r\n$6\r\nINCRBY\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n",
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen (unsigned int)strlen(key), key,
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen (unsigned int)strlen(diffstr), diffstr);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen ctx->cmd_count++;
5890b141d53b92449c61f257c613a4e13b48b61bTimo Sirainen redis_append_expire(ctx, cmd, key);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (o_stream_send(dict->conn.conn.output, str_data(cmd), str_len(cmd)) < 0) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen ctx->error = i_strdup_printf("write() failed: %s",
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen o_stream_get_error(dict->conn.conn.output));
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen }
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen}
1c7df2f6d12f5e7f0cf1e73452d4ae216a3d092cTimo Sirainen
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainenstruct dict dict_driver_redis = {
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen .name = "redis",
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen {
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .init = redis_dict_init,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .deinit = redis_dict_deinit,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .lookup = redis_dict_lookup,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .transaction_init = redis_transaction_init,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .transaction_commit = redis_transaction_commit,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .transaction_rollback = redis_transaction_rollback,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .set = redis_set,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .unset = redis_unset,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .atomic_inc = redis_atomic_inc,
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen }
31257b47d47510ceb093a6b218810a1a5b830c55Timo Sirainen};