dict-commands.c (dovecot-2.3.16) | : | dict-commands.c (dovecot-2.3.17) | ||
---|---|---|---|---|
/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ | /* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ | |||
#include "lib.h" | #include "lib.h" | |||
#include "array.h" | #include "array.h" | |||
#include "ostream.h" | #include "ostream.h" | |||
#include "str.h" | #include "str.h" | |||
#include "strescape.h" | #include "strescape.h" | |||
#include "stats-dist.h" | #include "stats-dist.h" | |||
#include "time-util.h" | #include "time-util.h" | |||
#include "dict-private.h" | ||||
#include "dict-client.h" | #include "dict-client.h" | |||
#include "dict-settings.h" | #include "dict-settings.h" | |||
#include "dict-connection.h" | #include "dict-connection.h" | |||
#include "dict-commands.h" | #include "dict-commands.h" | |||
#include "main.h" | #include "main.h" | |||
#define DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION 1 | #define DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION 1 | |||
#define DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION 1 | #define DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION 1 | |||
#define DICT_OUTPUT_OPTIMAL_SIZE 1024 | #define DICT_OUTPUT_OPTIMAL_SIZE 1024 | |||
struct dict_cmd_func { | struct dict_cmd_func { | |||
enum dict_protocol_cmd cmd; | enum dict_protocol_cmd cmd; | |||
int (*func)(struct dict_connection_cmd *cmd, const char *line); | int (*func)(struct dict_connection_cmd *cmd, const char *const *args); | |||
}; | }; | |||
struct dict_connection_cmd { | struct dict_connection_cmd { | |||
const struct dict_cmd_func *cmd; | const struct dict_cmd_func *cmd; | |||
struct dict_connection *conn; | struct dict_connection *conn; | |||
struct timeval start_timeval; | struct timeval start_timeval; | |||
struct event *event; | struct event *event; | |||
char *reply; | char *reply; | |||
struct dict_iterate_context *iter; | struct dict_iterate_context *iter; | |||
skipping to change at line 222 | skipping to change at line 223 | |||
str_append_c(str, DICT_PROTOCOL_REPLY_FAIL); | str_append_c(str, DICT_PROTOCOL_REPLY_FAIL); | |||
str_append_tabescaped(str, result->error); | str_append_tabescaped(str, result->error); | |||
} | } | |||
dict_cmd_reply_handle_stats(cmd, str, cmd_stats.lookups); | dict_cmd_reply_handle_stats(cmd, str, cmd_stats.lookups); | |||
str_append_c(str, '\n'); | str_append_c(str, '\n'); | |||
cmd->reply = i_strdup(str_c(str)); | cmd->reply = i_strdup(str_c(str)); | |||
dict_connection_cmd_try_flush(&cmd); | dict_connection_cmd_try_flush(&cmd); | |||
} | } | |||
static int cmd_lookup(struct dict_connection_cmd *cmd, const char *line) | static int cmd_lookup(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
/* <key> */ | const char *username; | |||
if (str_array_length(args) < 1) { | ||||
e_error(cmd->event, "LOOKUP: broken input"); | ||||
return -1; | ||||
} | ||||
username = args[1]; | ||||
/* <key> [<username>] */ | ||||
dict_connection_cmd_async(cmd); | dict_connection_cmd_async(cmd); | |||
event_add_str(cmd->event, "key", line); | event_add_str(cmd->event, "key", args[0]); | |||
dict_lookup_async(cmd->conn->dict, line, cmd_lookup_callback, cmd); | event_add_str(cmd->event, "user", username); | |||
const struct dict_op_settings set = { | ||||
.username = username, | ||||
}; | ||||
dict_lookup_async(cmd->conn->dict, &set, args[0], cmd_lookup_callback, cm | ||||
d); | ||||
return 1; | return 1; | |||
} | } | |||
static bool dict_connection_flush_if_full(struct dict_connection *conn) | static bool dict_connection_flush_if_full(struct dict_connection *conn) | |||
{ | { | |||
if (o_stream_get_buffer_used_size(conn->conn.output) > | if (o_stream_get_buffer_used_size(conn->conn.output) > | |||
DICT_OUTPUT_OPTIMAL_SIZE) { | DICT_OUTPUT_OPTIMAL_SIZE) { | |||
if (o_stream_flush(conn->conn.output) <= 0) { | if (o_stream_flush(conn->conn.output) <= 0) { | |||
/* continue later when there's more space | /* continue later when there's more space | |||
in output buffer */ | in output buffer */ | |||
skipping to change at line 343 | skipping to change at line 356 | |||
o_stream_uncork(conn->conn.output); | o_stream_uncork(conn->conn.output); | |||
} else { | } else { | |||
/* It's possible that the command gets finished via some other | /* It's possible that the command gets finished via some other | |||
code path. To make sure this doesn't cause hangs, uncork the | code path. To make sure this doesn't cause hangs, uncork the | |||
output when command gets freed. */ | output when command gets freed. */ | |||
cmd->uncork_pending = TRUE; | cmd->uncork_pending = TRUE; | |||
} | } | |||
dict_connection_unref_safe(conn); | dict_connection_unref_safe(conn); | |||
} | } | |||
static int cmd_iterate(struct dict_connection_cmd *cmd, const char *line) | static int cmd_iterate(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
const char *const *args; | const char *username; | |||
unsigned int flags; | unsigned int flags; | |||
uint64_t max_rows; | uint64_t max_rows; | |||
args = t_strsplit_tabescaped(line); | ||||
if (str_array_length(args) < 3 || | if (str_array_length(args) < 3 || | |||
str_to_uint(args[0], &flags) < 0 || | str_to_uint(args[0], &flags) < 0 || | |||
str_to_uint64(args[1], &max_rows) < 0) { | str_to_uint64(args[1], &max_rows) < 0) { | |||
e_error(cmd->event, "ITERATE: broken input"); | e_error(cmd->event, "ITERATE: broken input"); | |||
return -1; | return -1; | |||
} | } | |||
dict_connection_cmd_async(cmd); | dict_connection_cmd_async(cmd); | |||
username = args[3]; | ||||
/* <flags> <max_rows> <path> */ | const struct dict_op_settings set = { | |||
.username = username, | ||||
}; | ||||
/* <flags> <max_rows> <path> [<username>] */ | ||||
flags |= DICT_ITERATE_FLAG_ASYNC; | flags |= DICT_ITERATE_FLAG_ASYNC; | |||
event_add_str(cmd->event, "key", args[2]); | event_add_str(cmd->event, "key", args[2]); | |||
cmd->iter = dict_iterate_init_multiple(cmd->conn->dict, args+2, flags); | event_add_str(cmd->event, "user", username); | |||
cmd->iter = dict_iterate_init(cmd->conn->dict, &set, args[2], flags); | ||||
cmd->iter_flags = flags; | cmd->iter_flags = flags; | |||
if (max_rows > 0) | if (max_rows > 0) | |||
dict_iterate_set_limit(cmd->iter, max_rows); | dict_iterate_set_limit(cmd->iter, max_rows); | |||
dict_iterate_set_async_callback(cmd->iter, cmd_iterate_callback, cmd); | dict_iterate_set_async_callback(cmd->iter, cmd_iterate_callback, cmd); | |||
(void)dict_connection_cmd_output_more(cmd); | (void)dict_connection_cmd_output_more(cmd); | |||
return 1; | return 1; | |||
} | } | |||
static struct dict_connection_transaction * | static struct dict_connection_transaction * | |||
dict_connection_transaction_lookup(struct dict_connection *conn, | dict_connection_transaction_lookup(struct dict_connection *conn, | |||
skipping to change at line 404 | skipping to change at line 422 | |||
for (i = 0; i < count; i++) { | for (i = 0; i < count; i++) { | |||
if (transactions[i].id == id) { | if (transactions[i].id == id) { | |||
i_assert(transactions[i].ctx == NULL); | i_assert(transactions[i].ctx == NULL); | |||
array_delete(&conn->transactions, i, 1); | array_delete(&conn->transactions, i, 1); | |||
return; | return; | |||
} | } | |||
} | } | |||
i_unreached(); | i_unreached(); | |||
} | } | |||
static int cmd_begin(struct dict_connection_cmd *cmd, const char *line) | static int cmd_begin(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
unsigned int id; | unsigned int id; | |||
const char *username; | ||||
if (str_array_length(args) < 1) { | ||||
e_error(cmd->event, "BEGIN: broken input"); | ||||
return -1; | ||||
} | ||||
username = args[1]; | ||||
if (str_to_uint(line, &id) < 0) { | /* <id> [<username>] */ | |||
e_error(cmd->event, "Invalid transaction ID %s", line); | if (str_to_uint(args[0], &id) < 0) { | |||
e_error(cmd->event, "Invalid transaction ID %s", args[0]); | ||||
return -1; | return -1; | |||
} | } | |||
if (dict_connection_transaction_lookup(cmd->conn, id) != NULL) { | if (dict_connection_transaction_lookup(cmd->conn, id) != NULL) { | |||
e_error(cmd->event, "Transaction ID %u already exists", id); | e_error(cmd->event, "Transaction ID %u already exists", id); | |||
return -1; | return -1; | |||
} | } | |||
if (!array_is_created(&cmd->conn->transactions)) | if (!array_is_created(&cmd->conn->transactions)) | |||
i_array_init(&cmd->conn->transactions, 4); | i_array_init(&cmd->conn->transactions, 4); | |||
/* <id> */ | struct dict_op_settings set = { | |||
.username = username, | ||||
}; | ||||
trans = array_append_space(&cmd->conn->transactions); | trans = array_append_space(&cmd->conn->transactions); | |||
trans->id = id; | trans->id = id; | |||
trans->conn = cmd->conn; | trans->conn = cmd->conn; | |||
trans->ctx = dict_transaction_begin(cmd->conn->dict); | trans->ctx = dict_transaction_begin(cmd->conn->dict, &set); | |||
return 0; | return 0; | |||
} | } | |||
static int | static int | |||
dict_connection_transaction_lookup_parse(struct dict_connection *conn, | dict_connection_transaction_lookup_parse(struct dict_connection *conn, | |||
const char *line, | const char *id_str, | |||
struct dict_connection_transaction **tra ns_r) | struct dict_connection_transaction **tra ns_r) | |||
{ | { | |||
unsigned int id; | unsigned int id; | |||
if (str_to_uint(line, &id) < 0) { | if (str_to_uint(id_str, &id) < 0) { | |||
e_error(conn->conn.event, "Invalid transaction ID %s", line); | e_error(conn->conn.event, "Invalid transaction ID %s", id_str); | |||
return -1; | return -1; | |||
} | } | |||
*trans_r = dict_connection_transaction_lookup(conn, id); | *trans_r = dict_connection_transaction_lookup(conn, id); | |||
if (*trans_r == NULL) { | if (*trans_r == NULL) { | |||
e_error(conn->conn.event, "Transaction ID %u doesn't exist", id); | e_error(conn->conn.event, "Transaction ID %u doesn't exist", id); | |||
return -1; | return -1; | |||
} | } | |||
return 0; | return 0; | |||
} | } | |||
skipping to change at line 510 | skipping to change at line 538 | |||
cmd_commit_finish(cmd, result, FALSE); | cmd_commit_finish(cmd, result, FALSE); | |||
} | } | |||
static void cmd_commit_callback_async(const struct dict_commit_result *result, | static void cmd_commit_callback_async(const struct dict_commit_result *result, | |||
struct dict_connection_cmd *cmd) | struct dict_connection_cmd *cmd) | |||
{ | { | |||
cmd_commit_finish(cmd, result, TRUE); | cmd_commit_finish(cmd, result, TRUE); | |||
} | } | |||
static int | static int | |||
cmd_commit(struct dict_connection_cmd *cmd, const char *line) | cmd_commit(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0 ) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
cmd->trans_id = trans->id; | cmd->trans_id = trans->id; | |||
event_add_str(cmd->event, "user", trans->ctx->set.username); | ||||
dict_connection_cmd_async(cmd); | dict_connection_cmd_async(cmd); | |||
dict_transaction_commit_async(&trans->ctx, cmd_commit_callback, cmd); | dict_transaction_commit_async(&trans->ctx, cmd_commit_callback, cmd); | |||
return 1; | return 1; | |||
} | } | |||
static int | static int | |||
cmd_commit_async(struct dict_connection_cmd *cmd, const char *line) | cmd_commit_async(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0 ) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
cmd->trans_id = trans->id; | cmd->trans_id = trans->id; | |||
event_add_str(cmd->event, "user", trans->ctx->set.username); | ||||
dict_connection_cmd_async(cmd); | dict_connection_cmd_async(cmd); | |||
dict_transaction_commit_async(&trans->ctx, cmd_commit_callback_async, cmd ); | dict_transaction_commit_async(&trans->ctx, cmd_commit_callback_async, cmd ); | |||
return 1; | return 1; | |||
} | } | |||
static int cmd_rollback(struct dict_connection_cmd *cmd, const char *line) | static int | |||
cmd_rollback(struct dict_connection_cmd *cmd, const char *const *args) | ||||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0 ) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
event_add_str(cmd->event, "user", trans->ctx->set.username); | ||||
dict_transaction_rollback(&trans->ctx); | dict_transaction_rollback(&trans->ctx); | |||
dict_connection_transaction_array_remove(cmd->conn, trans->id); | dict_connection_transaction_array_remove(cmd->conn, trans->id); | |||
return 0; | return 0; | |||
} | } | |||
static int cmd_set(struct dict_connection_cmd *cmd, const char *line) | static int cmd_set(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
const char *const *args; | ||||
/* <id> <key> <value> */ | /* <id> <key> <value> */ | |||
args = t_strsplit_tabescaped(line); | ||||
if (str_array_length(args) != 3) { | if (str_array_length(args) != 3) { | |||
e_error(cmd->event, "SET: broken input"); | e_error(cmd->event, "SET: broken input"); | |||
return -1; | return -1; | |||
} | } | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
event_add_str(cmd->event, "user", trans->ctx->set.username); | ||||
dict_set(trans->ctx, args[1], args[2]); | dict_set(trans->ctx, args[1], args[2]); | |||
return 0; | return 0; | |||
} | } | |||
static int cmd_unset(struct dict_connection_cmd *cmd, const char *line) | static int cmd_unset(struct dict_connection_cmd *cmd, const char *const *args) | |||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
const char *const *args; | ||||
/* <id> <key> */ | /* <id> <key> */ | |||
args = t_strsplit_tabescaped(line); | ||||
if (str_array_length(args) != 2) { | if (str_array_length(args) != 2) { | |||
e_error(cmd->event, "UNSET: broken input"); | e_error(cmd->event, "UNSET: broken input"); | |||
return -1; | return -1; | |||
} | } | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
dict_unset(trans->ctx, args[1]); | dict_unset(trans->ctx, args[1]); | |||
return 0; | return 0; | |||
} | } | |||
static int cmd_atomic_inc(struct dict_connection_cmd *cmd, const char *line) | static int | |||
cmd_atomic_inc(struct dict_connection_cmd *cmd, const char *const *args) | ||||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
const char *const *args; | ||||
long long diff; | long long diff; | |||
/* <id> <key> <diff> */ | /* <id> <key> <diff> */ | |||
args = t_strsplit_tabescaped(line); | ||||
if (str_array_length(args) != 3 || | if (str_array_length(args) != 3 || | |||
str_to_llong(args[2], &diff) < 0) { | str_to_llong(args[2], &diff) < 0) { | |||
e_error(cmd->event, "ATOMIC_INC: broken input"); | e_error(cmd->event, "ATOMIC_INC: broken input"); | |||
return -1; | return -1; | |||
} | } | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
dict_atomic_inc(trans->ctx, args[1], diff); | dict_atomic_inc(trans->ctx, args[1], diff); | |||
return 0; | return 0; | |||
} | } | |||
static int cmd_timestamp(struct dict_connection_cmd *cmd, const char *line) | static int cmd_timestamp(struct dict_connection_cmd *cmd, const char *const *arg s) | |||
{ | { | |||
struct dict_connection_transaction *trans; | struct dict_connection_transaction *trans; | |||
const char *const *args; | ||||
long long tv_sec; | long long tv_sec; | |||
unsigned int tv_nsec; | unsigned int tv_nsec; | |||
/* <id> <secs> <nsecs> */ | /* <id> <secs> <nsecs> */ | |||
args = t_strsplit_tabescaped(line); | ||||
if (str_array_length(args) != 3 || | if (str_array_length(args) != 3 || | |||
str_to_llong(args[1], &tv_sec) < 0 || | str_to_llong(args[1], &tv_sec) < 0 || | |||
str_to_uint(args[2], &tv_nsec) < 0) { | str_to_uint(args[2], &tv_nsec) < 0) { | |||
e_error(cmd->event, "TIMESTAMP: broken input"); | e_error(cmd->event, "TIMESTAMP: broken input"); | |||
return -1; | return -1; | |||
} | } | |||
if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) | |||
return -1; | return -1; | |||
skipping to change at line 664 | skipping to change at line 690 | |||
return &cmds[i]; | return &cmds[i]; | |||
} | } | |||
return NULL; | return NULL; | |||
} | } | |||
int dict_command_input(struct dict_connection *conn, const char *line) | int dict_command_input(struct dict_connection *conn, const char *line) | |||
{ | { | |||
const struct dict_cmd_func *cmd_func; | const struct dict_cmd_func *cmd_func; | |||
struct dict_connection_cmd *cmd; | struct dict_connection_cmd *cmd; | |||
int ret; | int ret; | |||
const char *const *args; | ||||
cmd_func = dict_command_find((enum dict_protocol_cmd)*line); | cmd_func = dict_command_find((enum dict_protocol_cmd)*line); | |||
if (cmd_func == NULL) { | if (cmd_func == NULL) { | |||
e_error(conn->conn.event, "Unknown command %c", *line); | e_error(conn->conn.event, "Unknown command %c", *line); | |||
return -1; | return -1; | |||
} | } | |||
cmd = i_new(struct dict_connection_cmd, 1); | cmd = i_new(struct dict_connection_cmd, 1); | |||
cmd->conn = conn; | cmd->conn = conn; | |||
cmd->event = event_create(cmd->conn->conn.event); | cmd->event = event_create(cmd->conn->conn.event); | |||
cmd->cmd = cmd_func; | cmd->cmd = cmd_func; | |||
cmd->start_timeval = ioloop_timeval; | cmd->start_timeval = ioloop_timeval; | |||
array_push_back(&conn->cmds, &cmd); | array_push_back(&conn->cmds, &cmd); | |||
dict_connection_ref(conn); | dict_connection_ref(conn); | |||
if ((ret = cmd_func->func(cmd, line + 1)) <= 0) { | ||||
args = t_strsplit_tabescaped(line + 1); | ||||
if ((ret = cmd_func->func(cmd, args)) <= 0) { | ||||
dict_connection_cmd_remove(cmd); | dict_connection_cmd_remove(cmd); | |||
return ret; | return ret; | |||
} | } | |||
return 0; | return 0; | |||
} | } | |||
static bool dict_connection_cmds_try_output_more(struct dict_connection *conn) | static bool dict_connection_cmds_try_output_more(struct dict_connection *conn) | |||
{ | { | |||
struct dict_connection_cmd *cmd; | struct dict_connection_cmd *cmd; | |||
End of changes. 42 change blocks. | ||||
37 lines changed or deleted | 67 lines changed or added |