bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
8e1491e4924e3f9fc474a99438b6297b8d1ce1f3Timo Sirainen#define DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION 1
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen#define DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION 1
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen int (*func)(struct dict_connection_cmd *cmd, const char *line);
8bb454924c8b4612e4b657e60a860f8f0f3d6054Timo Sirainenstatic int cmd_iterate_flush(struct dict_connection_cmd *cmd);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic void dict_connection_cmd_output_more(struct dict_connection_cmd *cmd);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic void dict_connection_cmd_free(struct dict_connection_cmd *cmd)
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen if (dict_iterate_deinit(&cmd->iter, &error) < 0)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic void dict_connection_cmd_remove(struct dict_connection_cmd *cmd)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen for (i = 0; i < count; i++) {
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic void dict_connection_cmds_flush(struct dict_connection *conn)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen struct dict_connection_cmd *cmd, *const *first_cmdp;
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen i_assert(conn->minor_version < DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION);
8bb454924c8b4612e4b657e60a860f8f0f3d6054Timo Sirainen /* we may be able to start outputting iterations now. */
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen /* command not finished yet */
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainenstatic void dict_connection_cmd_try_flush(struct dict_connection_cmd **_cmd)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen if (cmd->conn->minor_version < DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION) {
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen o_stream_nsend_str(cmd->conn->output, t_strdup_printf("%c%u\t%s",
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainenstatic void dict_connection_cmd_async(struct dict_connection_cmd *cmd)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen if (cmd->conn->minor_version < DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen cmd->async_reply_id = ++cmd->conn->async_id_counter;
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen cmd->async_reply_id = ++cmd->conn->async_id_counter;
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen o_stream_nsend_str(cmd->conn->output, t_strdup_printf("%c%u\n",
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen DICT_PROTOCOL_REPLY_ASYNC_ID, cmd->async_reply_id));
5f08b0309190ec818d46bfe0e497468b30714a93Timo Sirainencmd_stats_update(struct dict_connection_cmd *cmd, struct stats_dist *stats)
cbe49ba128638e63395aedaa2144087c89835633Timo Sirainen diff = timeval_diff_usecs(&ioloop_timeval, &cmd->start_timeval);
5f08b0309190ec818d46bfe0e497468b30714a93Timo Sirainendict_cmd_reply_handle_stats(struct dict_connection_cmd *cmd,
8e1491e4924e3f9fc474a99438b6297b8d1ce1f3Timo Sirainen if (cmd->conn->minor_version < DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION)
217683437468663839ec9bb6c7d2892b98fae4f9Timo Sirainencmd_lookup_write_reply(struct dict_connection_cmd *cmd,
217683437468663839ec9bb6c7d2892b98fae4f9Timo Sirainen if (cmd->conn->minor_version < DICT_CLIENT_PROTOCOL_VERSION_MIN_MULTI_OK ||
217683437468663839ec9bb6c7d2892b98fae4f9Timo Sirainen /* the results get double-tabescaped so they end up becoming a single
217683437468663839ec9bb6c7d2892b98fae4f9Timo Sirainen for (unsigned int i = 0; values[i] != NULL; i++) {
217683437468663839ec9bb6c7d2892b98fae4f9Timo Sirainen str_append_c(str, DICT_PROTOCOL_REPLY_MULTI_OK);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainencmd_lookup_callback(const struct dict_lookup_result *result, void *context)
217683437468663839ec9bb6c7d2892b98fae4f9Timo Sirainen cmd_lookup_write_reply(cmd, result->values, str);
8e1491e4924e3f9fc474a99438b6297b8d1ce1f3Timo Sirainen str_append_c(str, DICT_PROTOCOL_REPLY_NOTFOUND);
5f08b0309190ec818d46bfe0e497468b30714a93Timo Sirainen dict_cmd_reply_handle_stats(cmd, str, cmd_stats.lookups);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_lookup(struct dict_connection_cmd *cmd, const char *line)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen dict_lookup_async(cmd->conn->dict, line, cmd_lookup_callback, cmd);
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainenstatic bool dict_connection_flush_if_full(struct dict_connection *conn)
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen if (o_stream_get_buffer_used_size(conn->output) >
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen /* continue later when there's more space
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen in output buffer */
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen o_stream_set_flush_pending(conn->output, TRUE);
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen /* flushed everything, continue */
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_iterate_flush(struct dict_connection_cmd *cmd)
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen if (!dict_connection_flush_if_full(cmd->conn))
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen while (dict_iterate(cmd->iter, &key, &value)) {
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen str_append_c(str, DICT_PROTOCOL_REPLY_ASYNC_REPLY);
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen str_printfa(str, "%u\t", cmd->async_reply_id);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if ((cmd->iter_flags & DICT_ITERATE_FLAG_NO_VALUE) == 0)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen o_stream_nsend(cmd->conn->output, str_data(str), str_len(str));
1fb0136a1eef1990e24268e0916a998b36149e14Timo Sirainen if (!dict_connection_flush_if_full(cmd->conn))
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen /* wait for the next iteration callback */
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen if (dict_iterate_deinit(&cmd->iter, &error) < 0) {
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen str_printfa(str, "%c%s", DICT_PROTOCOL_REPLY_FAIL, error);
5f08b0309190ec818d46bfe0e497468b30714a93Timo Sirainen dict_cmd_reply_handle_stats(cmd, str, cmd_stats.iterations);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic void cmd_iterate_callback(void *context)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_iterate(struct dict_connection_cmd *cmd, const char *line)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: ITERATE: broken input");
8c0e4118f21c30455d4911fabc4bf50bfaeca712Timo Sirainen /* <flags> <max_rows> <path> */
8c0e4118f21c30455d4911fabc4bf50bfaeca712Timo Sirainen cmd->iter = dict_iterate_init_multiple(cmd->conn->dict, args+2, flags);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen dict_iterate_set_async_callback(cmd->iter, cmd_iterate_callback, cmd);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainendict_connection_transaction_lookup(struct dict_connection *conn,
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen unsigned int id)
e20e638805c4bd54e039891a3e92760b1dfa189aTimo Sirainen struct dict_connection_transaction *transaction;
e20e638805c4bd54e039891a3e92760b1dfa189aTimo Sirainen array_foreach_modifiable(&conn->transactions, transaction) {
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainendict_connection_transaction_array_remove(struct dict_connection *conn,
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen unsigned int id)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen const struct dict_connection_transaction *transactions;
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen transactions = array_get(&conn->transactions, &count);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen for (i = 0; i < count; i++) {
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_begin(struct dict_connection_cmd *cmd, const char *line)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: Invalid transaction ID %s", line);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if (dict_connection_transaction_lookup(cmd->conn, id) != NULL) {
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: Transaction ID %u already exists", id);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if (!array_is_created(&cmd->conn->transactions))
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen trans = array_append_space(&cmd->conn->transactions);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen trans->ctx = dict_transaction_begin(cmd->conn->dict);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainendict_connection_transaction_lookup_parse(struct dict_connection *conn,
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: Invalid transaction ID %s", line);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen *trans_r = dict_connection_transaction_lookup(conn, id);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: Transaction ID %u doesn't exist", id);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainencmd_commit_finish(struct dict_connection_cmd *cmd,
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen const struct dict_commit_result *result, bool async)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen str_append_c(str, DICT_PROTOCOL_REPLY_ASYNC_COMMIT);
5f08b0309190ec818d46bfe0e497468b30714a93Timo Sirainen dict_cmd_reply_handle_stats(cmd, str, cmd_stats.commits);
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen dict_connection_transaction_array_remove(cmd->conn, cmd->trans_id);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic void cmd_commit_callback(const struct dict_commit_result *result,
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainenstatic void cmd_commit_callback_async(const struct dict_commit_result *result,
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainencmd_commit(struct dict_connection_cmd *cmd, const char *line)
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0)
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen dict_transaction_commit_async(&trans->ctx, cmd_commit_callback, cmd);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainencmd_commit_async(struct dict_connection_cmd *cmd, const char *line)
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0)
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen dict_transaction_commit_async(&trans->ctx, cmd_commit_callback_async, cmd);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_rollback(struct dict_connection_cmd *cmd, const char *line)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0)
ac00e30516d93d289c71bd59f338c26693329e67Timo Sirainen dict_connection_transaction_array_remove(cmd->conn, trans->id);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_set(struct dict_connection_cmd *cmd, const char *line)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen /* <id> <key> <value> */
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_unset(struct dict_connection_cmd *cmd, const char *line)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen /* <id> <key> */
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic int cmd_atomic_inc(struct dict_connection_cmd *cmd, const char *line)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen /* <id> <key> <diff> */
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: ATOMIC_INC: broken input");
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
345fceae2f430dcad449f2a09598ba1a225116ddTimo Sirainenstatic int cmd_timestamp(struct dict_connection_cmd *cmd, const char *line)
345fceae2f430dcad449f2a09598ba1a225116ddTimo Sirainen /* <id> <secs> <nsecs> */
18c01c2573243be3eaaf3c584236d865ae3390fdTimo Sirainen i_error("dict client: TIMESTAMP: broken input");
345fceae2f430dcad449f2a09598ba1a225116ddTimo Sirainen if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
345fceae2f430dcad449f2a09598ba1a225116ddTimo Sirainen dict_transaction_set_timestamp(trans->ctx, &ts);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen { DICT_PROTOCOL_CMD_COMMIT_ASYNC, cmd_commit_async },
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen { DICT_PROTOCOL_CMD_ATOMIC_INC, cmd_atomic_inc },
345fceae2f430dcad449f2a09598ba1a225116ddTimo Sirainen { DICT_PROTOCOL_CMD_TIMESTAMP, cmd_timestamp },
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenstatic const struct dict_cmd_func *dict_command_find(enum dict_protocol_cmd cmd)
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen unsigned int i;
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainenint dict_command_input(struct dict_connection *conn, const char *line)
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen cmd_func = dict_command_find((enum dict_protocol_cmd)*line);
1ea214b620715aa8b213c5686383b821e720fe42Timo Sirainen i_error("dict client: Unknown command %c", *line);
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainen if ((ret = cmd_func->func(cmd, line + 1)) <= 0) {
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainenstatic bool dict_connection_cmds_try_output_more(struct dict_connection *conn)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen struct dict_connection_cmd *const *cmdp, *cmd;
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen /* only iterators may be returning a lot of data */
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen /* not an iterator */
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen /* unfinished */
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen /* cmd should be freed now, restart output */
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen if (conn->minor_version < DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen /* try to flush the rest */
1a883718249b8db128a105ee7f03293873f1f9c5Timo Sirainenvoid dict_connection_cmds_output_more(struct dict_connection *conn)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen if (!dict_connection_cmds_try_output_more(conn))
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainenstatic void dict_connection_cmd_output_more(struct dict_connection_cmd *cmd)
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen struct dict_connection_cmd *const *first_cmdp;
44c54229a117b8dab05eaff9c54ba61fbae9a39fTimo Sirainen if (cmd->conn->minor_version < DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION) {