bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING memcached_ascii */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen#define MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* GET: expecting VALUE or END */
3fd02e831c32598deda589ae536fdaa4022d9750Aki Tuomi /* SET: expecting STORED / NOT_STORED */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* DELETE: expecting DELETED */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* (INCR+ADD)/DECR: expecting number / NOT_FOUND / STORED / NOT_STORED */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen struct dict_transaction_memory_context *memctx;
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen ARRAY(enum memcached_ascii_input_state) input_states;
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen ARRAY(struct memcached_ascii_dict_reply) replies;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic struct connection_list *memcached_ascii_connections;
5c5e738bcc6e50228bdadbf77a8ab16a2eb915bbTimo Sirainenmemcached_ascii_callback(struct memcached_ascii_dict *dict,
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const struct memcached_ascii_dict_reply *reply,
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. */
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenmemcached_ascii_disconnected(struct memcached_ascii_connection *conn,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen const struct memcached_ascii_dict_reply *reply;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen memcached_ascii_callback(conn->dict, reply, &result);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic void memcached_ascii_conn_destroy(struct connection *_conn)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen memcached_ascii_disconnected(conn, connection_disconnect_reason(_conn));
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic bool memcached_ascii_input_value(struct memcached_ascii_connection *conn)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen data = i_stream_get_data(conn->conn.input, &size);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* finished. drop the trailing CRLF */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen str_truncate(conn->reply_str, str_len(conn->reply_str)-2);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int memcached_ascii_input_reply_read(struct memcached_ascii_dict *dict,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen struct memcached_ascii_connection *conn = &dict->conn;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen const enum memcached_ascii_input_state *states;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* continue reading bulk reply */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen states = array_get(&dict->input_states, &count);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen "memcached_ascii: Unexpected input (expected nothing): %s", line);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen switch (states[0]) {
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* VALUE <key> <flags> <bytes>
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen if (str_to_uint(p+1, &conn->reply_bytes_left) < 0)
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen return memcached_ascii_input_reply_read(dict, error_r);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen "memcached_ascii: Unexpected input (state=%d): %s",
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int memcached_ascii_input_reply(struct memcached_ascii_dict *dict,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if ((ret = memcached_ascii_input_reply_read(dict, error_r)) <= 0)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* finished a reply */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen replies = array_get_modifiable(&dict->replies, &count);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen memcached_ascii_callback(dict, &replies[0], &result);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic void memcached_ascii_conn_input(struct connection *_conn)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen i_stream_get_disconnect_reason(_conn->input));
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen while ((ret = memcached_ascii_input_reply(conn->dict, &error)) > 0) ;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int memcached_ascii_input_wait(struct memcached_ascii_dict *dict,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen *error_r = "memcached_ascii: Communication failure";
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic void memcached_ascii_input_timeout(struct memcached_ascii_dict *dict)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen "memcached_ascii: Request timed out in %u.%03u secs",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen dict->timeout_msecs/1000, dict->timeout_msecs%1000);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen memcached_ascii_disconnected(&dict->conn, reason);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int memcached_ascii_wait_replies(struct memcached_ascii_dict *dict,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen while (array_count(&dict->input_states) > 0) {
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if ((ret = memcached_ascii_input_reply(dict, error_r)) != 0) {
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen memcached_ascii_disconnected(&dict->conn, *error_r);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen ret = memcached_ascii_input_wait(dict, error_r);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int memcached_ascii_wait(struct memcached_ascii_dict *dict,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* waiting for connection to finish */
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen ret = memcached_ascii_input_wait(dict, error_r);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if (memcached_ascii_wait_replies(dict, error_r) < 0)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen i_assert(array_count(&dict->input_states) == 0);
7db7fbea5d8a07463b625f93d69166d56018dadfTimo Sirainenmemcached_ascii_conn_connected(struct connection *_conn, bool success)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen struct memcached_ascii_connection *conn = (struct memcached_ascii_connection *)_conn;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen i_error("memcached_ascii: connect(%s, %u) failed: %m",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen net_ip2addr(&conn->dict->ip), conn->dict->port);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic const struct connection_settings memcached_ascii_conn_set = {
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic const struct connection_vfuncs memcached_ascii_conn_vfuncs = {
7db7fbea5d8a07463b625f93d69166d56018dadfTimo Sirainen .client_connected = memcached_ascii_conn_connected
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic const char *memcached_ascii_escape_username(const char *username)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen const char *p;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen switch (*p) {
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenmemcached_ascii_dict_init(struct dict *driver, const char *uri,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen connection_list_init(&memcached_ascii_conn_set,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen dict->timeout_msecs = MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS;
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen *error_r = t_strdup_printf("Invalid port: %s",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen } else if (strncmp(*args, "prefix=", 7) == 0) {
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo 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("Unknown parameter: %s",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen connection_init_client_ip(memcached_ascii_connections, &dict->conn.conn,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen dict->conn.reply_str = str_new(default_pool, 256);
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen if (strchr(set->username, DICT_USERNAME_SEPARATOR) == NULL)
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen /* escape the username */
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen dict->username = i_strdup(memcached_ascii_escape_username(set->username));
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic void memcached_ascii_dict_deinit(struct dict *_dict)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen if (memcached_ascii_connections->connections == NULL)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen connection_list_deinit(&memcached_ascii_connections);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic int memcached_ascii_connect(struct memcached_ascii_dict *dict,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen if (connection_client_connect(&dict->conn.conn) < 0) {
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen "memcached_ascii: Couldn't connect to %s:%u",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenstatic const char *
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenmemcached_ascii_dict_get_full_key(struct memcached_ascii_dict *dict,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen const char *key)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen if (strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen else if (strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE)) == 0) {
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen key = t_strdup_printf("%s%c%s", dict->username,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen key = t_strconcat(dict->key_prefix, key, NULL);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenmemcached_ascii_dict_lookup(struct dict *_dict, pool_t pool, const char *key,
8f9a18189f01448267100fa54c3b4bb8639a1a56Timo Sirainen struct memcached_ascii_dict *dict = (struct memcached_ascii_dict *)_dict;
8f9a18189f01448267100fa54c3b4bb8639a1a56Timo Sirainen enum memcached_ascii_input_state state = MEMCACHED_INPUT_STATE_GET;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if (memcached_ascii_connect(dict, error_r) < 0)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen key = memcached_ascii_dict_get_full_key(dict, key);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen *value_r = p_strdup(pool, str_c(dict->conn.reply_str));
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenmemcached_ascii_transaction_init(struct dict *_dict)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen pool = pool_alloconly_create("file dict transaction", 2048);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen ctx = p_new(pool, struct dict_transaction_memory_context, 1);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen dict_transaction_memory_init(ctx, _dict, pool);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenmemcached_send_change(struct dict_memcached_ascii_commit_ctx *ctx,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen const struct dict_transaction_memory_change *change)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen key = memcached_ascii_dict_get_full_key(ctx->dict, change->key);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen str_printfa(ctx->str, "set %s 0 0 %"PRIuSIZE_T"\r\n%s\r\n",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen key, strlen(change->value.str), change->value.str);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen array_append(&ctx->dict->input_states, &state, 1);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen /* same kludge as with append */
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen value = t_strdup_printf("%lld", change->value.diff);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen str_printfa(ctx->str, "add %s 0 0 %u\r\n%s\r\n",
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen array_append(&ctx->dict->input_states, &state, 1);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenmemcached_ascii_transaction_send(struct dict_memcached_ascii_commit_ctx *ctx,
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const char **error_r)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen struct memcached_ascii_dict *dict = ctx->dict;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen const struct dict_transaction_memory_change *changes;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (memcached_ascii_connect(dict, error_r) < 0)
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen old_state_count = array_count(&dict->input_states);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen changes = array_get(&ctx->memctx->changes, &count);
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen reply->reply_count = array_count(&dict->input_states) - old_state_count;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainenmemcached_ascii_transaction_commit(struct dict_transaction_context *_ctx,
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen (struct dict_transaction_memory_context *)_ctx;
75bb83681e30d6a86109bbafdfe6b513c11124bcTimo Sirainen struct dict_memcached_ascii_commit_ctx commit_ctx;
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen struct dict_commit_result result = { DICT_COMMIT_RET_OK, NULL };
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen result.ret = memcached_ascii_transaction_send(&commit_ctx, &result.error);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen if (memcached_ascii_wait(dict, &result.error) < 0)
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .transaction_init = memcached_ascii_transaction_init,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .transaction_commit = memcached_ascii_transaction_commit,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen .transaction_rollback = dict_transaction_memory_rollback,