]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
dict-redis: Implement new init() API
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Wed, 19 Apr 2023 13:38:52 +0000 (16:38 +0300)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 20 Nov 2023 12:21:56 +0000 (14:21 +0200)
src/lib-dict/dict-redis.c

index 98f3da748a335dfb62567ec16317d9bb7d1cbc8b..9a3ca36ae576071b3449e05bb8604e6799b4da84 100644 (file)
@@ -6,10 +6,10 @@
 #include "istream.h"
 #include "ostream.h"
 #include "connection.h"
+#include "settings.h"
+#include "settings-parser.h"
 #include "dict-private.h"
 
-#define REDIS_DEFAULT_PORT 6379
-#define REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30)
 #define DICT_USERNAME_SEPARATOR '/'
 
 enum redis_input_state {
@@ -47,8 +47,7 @@ struct redis_dict_reply {
 
 struct redis_dict {
        struct dict dict;
-       char *password, *key_prefix, *expire_value;
-       unsigned int timeout_msecs, db_id;
+       struct dict_redis_settings *set;
 
        struct redis_connection conn;
        struct timeout *to;
@@ -68,6 +67,59 @@ struct redis_dict_transaction_context {
        char *error;
 };
 
+struct dict_redis_settings {
+       pool_t pool;
+
+       const char *dict_redis_socket_path;
+       const char *dict_redis_host;
+       in_port_t dict_redis_port;
+       const char *dict_redis_key_prefix;
+       const char *dict_redis_password;
+       unsigned int dict_redis_db_id;
+       unsigned int dict_redis_expire;
+       unsigned int dict_redis_request_timeout;
+
+       struct ip_addr dict_redis_ip;
+};
+
+static bool dict_redis_settings_check(void *_set, pool_t pool, const char **error_r);
+
+#undef DEF
+#define DEF(type, name) \
+       SETTING_DEFINE_STRUCT_##type(#name, name, struct dict_redis_settings)
+static const struct setting_define dict_redis_setting_defines[] = {
+       DEF(STR, dict_redis_socket_path),
+       DEF(STR, dict_redis_host),
+       DEF(IN_PORT, dict_redis_port),
+       DEF(STR, dict_redis_key_prefix),
+       DEF(STR, dict_redis_password),
+       DEF(UINT, dict_redis_db_id),
+       DEF(TIME, dict_redis_expire),
+       DEF(TIME_MSECS, dict_redis_request_timeout),
+
+       SETTING_DEFINE_LIST_END
+};
+static const struct dict_redis_settings dict_redis_default_settings = {
+       .dict_redis_socket_path = "",
+       .dict_redis_host = "127.0.0.1",
+       .dict_redis_port = 6379,
+       .dict_redis_key_prefix = "",
+       .dict_redis_password = "",
+       .dict_redis_db_id = 0,
+       .dict_redis_expire = 0,
+       .dict_redis_request_timeout = 30 * 1000,
+};
+const struct setting_parser_info dict_redis_setting_parser_info = {
+       .name = "dict_redis",
+
+       .defines = dict_redis_setting_defines,
+       .defaults = &dict_redis_default_settings,
+
+       .struct_size = sizeof(struct dict_redis_settings),
+       .pool_offset1 = 1 + offsetof(struct dict_redis_settings, pool),
+       .check_func = dict_redis_settings_check,
+};
+
 static struct connection_list *redis_connections;
 
 static void redis_dict_wait(struct dict *_dict);
@@ -134,7 +186,8 @@ static void redis_dict_wait_timeout(struct redis_dict *dict)
 {
        const char *reason = t_strdup_printf(
                "redis: Commit timed out in %u.%03u secs",
-               dict->timeout_msecs/1000, dict->timeout_msecs%1000);
+               dict->set->dict_redis_request_timeout / 1000,
+               dict->set->dict_redis_request_timeout % 1000);
        redis_disconnected(&dict->conn, reason);
 }
 
@@ -148,7 +201,7 @@ static const char *redis_wait(struct redis_dict *dict)
        if (dict->to != NULL)
                dict->to = io_loop_move_timeout(&dict->to);
        else {
-               dict->to = timeout_add(dict->timeout_msecs,
+               dict->to = timeout_add(dict->set->dict_redis_request_timeout,
                                       redis_dict_wait_timeout, dict);
        }
        connection_switch_ioloop(&dict->conn.conn);
@@ -366,73 +419,120 @@ static const char *redis_escape_username(const char *username)
        return str_c(str);
 }
 
-static int
-redis_dict_init_legacy(struct dict *driver, const char *uri,
-                      const struct dict_legacy_settings *set,
-                      struct dict **dict_r, const char **error_r)
+static bool dict_redis_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+                                     const char **error_r)
 {
-       struct redis_dict *dict;
-       struct ip_addr ip;
-       unsigned int secs;
-       in_port_t port = REDIS_DEFAULT_PORT;
-       const char *const *args, *value, *unix_path = NULL;
-       int ret = 0;
+       struct dict_redis_settings *set = _set;
 
+       if (net_addr2ip(set->dict_redis_host, &set->dict_redis_ip) < 0) {
+               *error_r = "dict_redis_host is not a valid IP";
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static struct dict *
+redis_dict_init_common(const struct dict *dict_driver, struct event *event,
+                      struct dict_redis_settings *set)
+{
        if (redis_connections == NULL) {
                redis_connections =
                        connection_list_init(&redis_conn_set,
                                             &redis_conn_vfuncs);
        }
 
-       dict = i_new(struct redis_dict, 1);
-       if (net_addr2ip("127.0.0.1", &ip) < 0)
+       struct redis_dict *dict = i_new(struct redis_dict, 1);
+       dict->set = set;
+       dict->conn.conn.event_parent = event;
+
+       if (set->dict_redis_socket_path[0] != '\0') {
+               connection_init_client_unix(redis_connections, &dict->conn.conn,
+                                           set->dict_redis_socket_path);
+       } else {
+               connection_init_client_ip(redis_connections, &dict->conn.conn,
+                                         NULL, &set->dict_redis_ip,
+                                         set->dict_redis_port);
+       }
+       event_set_append_log_prefix(dict->conn.conn.event, "redis: ");
+       dict->dict = *dict_driver;
+       dict->conn.last_reply = str_new(default_pool, 256);
+       dict->conn.dict = dict;
+
+       i_array_init(&dict->input_states, 4);
+       i_array_init(&dict->replies, 4);
+
+       return &dict->dict;
+}
+
+static int
+redis_dict_init(const struct dict *dict_driver, struct event *event,
+               struct dict **dict_r, const char **error_r)
+{
+       struct dict_redis_settings *set;
+
+       if (settings_get(event, &dict_redis_setting_parser_info, 0,
+                        &set, error_r) < 0)
+               return -1;
+       *dict_r = redis_dict_init_common(dict_driver, event, set);
+       return 0;
+}
+
+static int
+redis_dict_init_legacy(struct dict *dict_driver, const char *uri,
+                      const struct dict_legacy_settings *legacy_set,
+                      struct dict **dict_r, const char **error_r)
+{
+       pool_t pool = pool_alloconly_create("dict_redis_settings", 128);
+       struct dict_redis_settings *set =
+               p_new(pool, struct dict_redis_settings, 1);
+       *set = dict_redis_default_settings;
+       set->pool = pool;
+       if (net_addr2ip(set->dict_redis_host, &set->dict_redis_ip) < 0)
                i_unreached();
-       dict->timeout_msecs = REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS;
-       dict->key_prefix = i_strdup("");
-       dict->password   = i_strdup("");
 
-       args = t_strsplit(uri, ":");
+       const char *const *args = t_strsplit(uri, ":");
+       const char *value;
+       int ret = 0;
        for (; *args != NULL; args++) {
                if (str_begins(*args, "path=", &value)) {
-                       unix_path = value;
+                       set->dict_redis_socket_path = p_strdup(pool, value);
                } else if (str_begins(*args, "host=", &value)) {
-                       if (net_addr2ip(value, &ip) < 0) {
+                       if (net_addr2ip(value, &set->dict_redis_ip) < 0) {
                                *error_r = t_strdup_printf("Invalid IP: %s",
                                                           value);
                                ret = -1;
+                       } else {
+                               set->dict_redis_host = p_strdup(pool, value);
                        }
                } else if (str_begins(*args, "port=", &value)) {
-                       if (net_str2port(value, &port) < 0) {
+                       if (net_str2port(value, &set->dict_redis_port) < 0) {
                                *error_r = t_strdup_printf("Invalid port: %s",
                                                           value);
                                ret = -1;
                        }
                } else if (str_begins(*args, "prefix=", &value)) {
-                       i_free(dict->key_prefix);
-                       dict->key_prefix = i_strdup(value);
+                       set->dict_redis_key_prefix = p_strdup(pool, value);
                } else if (str_begins(*args, "db=", &value)) {
-                       if (str_to_uint(value, &dict->db_id) < 0) {
+                       if (str_to_uint(value, &set->dict_redis_db_id) < 0) {
                                *error_r = t_strdup_printf(
                                        "Invalid db number: %s", value);
                                ret = -1;
                        }
                } else if (str_begins(*args, "expire_secs=", &value)) {
-                       if (str_to_uint(value, &secs) < 0 || secs == 0) {
+                       if (str_to_uint(value, &set->dict_redis_expire) < 0 ||
+                           set->dict_redis_expire == 0) {
                                *error_r = t_strdup_printf(
                                        "Invalid expire_secs: %s", value);
                                ret = -1;
                        }
-                       i_free(dict->expire_value);
-                       dict->expire_value = i_strdup(value);
                } else if (str_begins(*args, "timeout_msecs=", &value)) {
-                       if (str_to_uint(value, &dict->timeout_msecs) < 0) {
+                       if (str_to_uint(value, &set->dict_redis_request_timeout) < 0) {
                                *error_r = t_strdup_printf(
                                        "Invalid timeout_msecs: %s", value);
                                ret = -1;
                        }
                } else if (str_begins(*args, "password=", &value)) {
-                       i_free(dict->password);
-                       dict->password = i_strdup(value);
+                       set->dict_redis_password = p_strdup(pool, value);
                } else {
                        *error_r = t_strdup_printf("Unknown parameter: %s",
                                                   *args);
@@ -440,30 +540,12 @@ redis_dict_init_legacy(struct dict *driver, const char *uri,
                }
        }
        if (ret < 0) {
-               i_free(dict->password);
-               i_free(dict->key_prefix);
-               i_free(dict);
+               pool_unref(&pool);
                return -1;
        }
 
-       dict->conn.conn.event_parent = set->event_parent;
-
-       if (unix_path != NULL) {
-               connection_init_client_unix(redis_connections, &dict->conn.conn,
-                                           unix_path);
-       } else {
-               connection_init_client_ip(redis_connections, &dict->conn.conn,
-                                         NULL, &ip, port);
-       }
-       event_set_append_log_prefix(dict->conn.conn.event, "redis: ");
-       dict->dict = *driver;
-       dict->conn.last_reply = str_new(default_pool, 256);
-       dict->conn.dict = dict;
-
-       i_array_init(&dict->input_states, 4);
-       i_array_init(&dict->replies, 4);
-
-       *dict_r = &dict->dict;
+       *dict_r = redis_dict_init_common(dict_driver, legacy_set->event_parent,
+                                        set);
        return 0;
 }
 
@@ -481,10 +563,8 @@ static void redis_dict_deinit(struct dict *_dict)
        str_free(&dict->conn.last_reply);
        array_free(&dict->replies);
        array_free(&dict->input_states);
+       settings_free(dict->set);
        i_free(dict->last_error);
-       i_free(dict->expire_value);
-       i_free(dict->key_prefix);
-       i_free(dict->password);
        i_free(dict);
 
        if (redis_connections->connections == NULL)
@@ -505,7 +585,8 @@ static void redis_dict_lookup_timeout(struct redis_dict *dict)
 {
        const char *reason = t_strdup_printf(
                "redis: Lookup timed out in %u.%03u secs",
-               dict->timeout_msecs/1000, dict->timeout_msecs%1000);
+               dict->set->dict_redis_request_timeout / 1000,
+               dict->set->dict_redis_request_timeout % 1000);
        redis_disconnected(&dict->conn, reason);
 }
 
@@ -525,8 +606,8 @@ redis_dict_get_full_key(struct redis_dict *dict, const char *username,
        } else {
                i_unreached();
        }
-       if (*dict->key_prefix != '\0')
-               key = t_strconcat(dict->key_prefix, key, NULL);
+       if (*dict->set->dict_redis_key_prefix != '\0')
+               key = t_strconcat(dict->set->dict_redis_key_prefix, key, NULL);
        return key;
 }
 
@@ -534,11 +615,12 @@ static void redis_dict_auth(struct redis_dict *dict)
 {
        const char *cmd;
 
-       if (*dict->password == '\0')
+       if (*dict->set->dict_redis_password == '\0')
                return;
 
        cmd = t_strdup_printf("*2\r\n$4\r\nAUTH\r\n$%zu\r\n%s\r\n",
-                             strlen(dict->password), dict->password);
+                             strlen(dict->set->dict_redis_password),
+                             dict->set->dict_redis_password);
        o_stream_nsend_str(dict->conn.conn.output, cmd);
        redis_input_state_add(dict, REDIS_INPUT_STATE_AUTH);
 }
@@ -550,11 +632,11 @@ static void redis_dict_select_db(struct redis_dict *dict)
        if (dict->db_id_set)
                return;
        dict->db_id_set = TRUE;
-       if (dict->db_id == 0) {
+       if (dict->set->dict_redis_db_id == 0) {
                /* 0 is the default */
                return;
        }
-       db_str = dec2str(dict->db_id);
+       db_str = dec2str(dict->set->dict_redis_db_id);
        cmd = t_strdup_printf("*2\r\n$6\r\nSELECT\r\n$%zu\r\n%s\r\n",
                              strlen(db_str), db_str);
        o_stream_nsend_str(dict->conn.conn.output, cmd);
@@ -585,7 +667,7 @@ static int redis_dict_lookup(struct dict *_dict,
            connection_client_connect(&dict->conn.conn) < 0) {
                e_error(dict->conn.conn.event, "Couldn't connect");
        } else {
-               to = timeout_add(dict->timeout_msecs,
+               to = timeout_add(dict->set->dict_redis_request_timeout,
                                 redis_dict_lookup_timeout, dict);
                if (!dict->connected) {
                        /* wait for connection */
@@ -694,7 +776,8 @@ redis_transaction_commit(struct dict_transaction_context *_ctx, bool async,
                        redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC_REPLY);
                if (async) {
                        if (dict->to == NULL) {
-                               dict->to = timeout_add(dict->timeout_msecs,
+                               dict->to = timeout_add(
+                                       dict->set->dict_redis_request_timeout,
                                        redis_dict_wait_timeout, dict);
                        }
                        i_free(ctx);
@@ -759,12 +842,13 @@ redis_append_expire(struct redis_dict_transaction_context *ctx,
                    string_t *cmd, const char *key)
 {
        struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict;
-       const char *expire_value = dict->expire_value;
+       unsigned int expire_secs = dict->set->dict_redis_expire;
 
        if (ctx->ctx.set.expire_secs > 0)
-               expire_value = dec2str(ctx->ctx.set.expire_secs);
-       if (expire_value == NULL)
+               expire_secs = ctx->ctx.set.expire_secs;
+       if (expire_secs == 0)
                return;
+       const char *expire_value = dec2str(expire_secs);
 
        str_printfa(cmd, "*3\r\n$6\r\nEXPIRE\r\n$%zu\r\n%s\r\n$%zu\r\n%s\r\n",
                    strlen(key), key, strlen(expire_value), expire_value);
@@ -851,6 +935,7 @@ struct dict dict_driver_redis = {
        .name = "redis",
        .flags = DICT_DRIVER_FLAG_SUPPORT_EXPIRE_SECS,
        .v = {
+               .init = redis_dict_init,
                .init_legacy = redis_dict_init_legacy,
                .deinit = redis_dict_deinit,
                .wait = redis_dict_wait,