dict-client.c revision 542d7a4afa3d7d8686ebdba691ad02d1c2e99452
/* Copyright (c) 2005-2016 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "llist.h"
#include "str.h"
#include "strescape.h"
#include "file-lock.h"
#include "time-util.h"
#include "connection.h"
#include "ostream.h"
#include "eacces-error.h"
#include "dict-private.h"
#include "dict-client.h"
#include <unistd.h>
#include <fcntl.h>
/* Disconnect from dict server after this many milliseconds of idling after
sending a command. Because dict server does blocking dict accesses, it can
handle only one client at a time. This is why the default timeout is zero,
so that there won't be many dict processes just doing nothing. Zero means
that the socket is disconnected immediately after returning to ioloop. */
#define DICT_CLIENT_DEFAULT_TIMEOUT_MSECS 0
/* Abort dict lookup after this many seconds. */
#define DICT_CLIENT_REQUEST_TIMEOUT_MSECS 30000
/* Log a warning if dict lookup takes longer than this many milliseconds. */
#define DICT_CLIENT_DEFAULT_WARN_SLOW_MSECS 5000
struct client_dict_cmd {
int refcount;
struct client_dict *dict;
struct timeval start_time;
char *query;
unsigned int async_id;
struct timeval async_id_received_time;
bool reconnected;
bool retry_errors;
bool no_replies;
bool unfinished;
bool background;
const char *const *extra_args, const char *error,
bool disconnected);
struct client_dict_iterate_context *iter;
struct client_dict_transaction_context *trans;
struct {
void *context;
} api_callback;
};
struct dict_connection {
struct connection conn;
struct client_dict *dict;
};
struct client_dict {
struct dict_connection conn;
enum dict_data_type value_type;
unsigned warn_slow_msecs;
char *last_connect_error;
struct io_wait_timer *wait_timer;
struct timeout *to_requests;
unsigned int idle_msecs;
unsigned int transaction_id_counter;
};
struct client_dict_iter_result {
};
struct client_dict_iterate_context {
struct dict_iterate_context ctx;
char *error;
const char **paths;
enum dict_iterate_flags flags;
unsigned int result_idx;
bool cmd_sent;
bool seen_results;
bool finished;
bool deinit;
};
struct client_dict_transaction_context {
struct dict_transaction_context ctx;
char *first_query;
char *error;
unsigned int id;
unsigned int query_count;
bool sent_begin:1;
};
static struct connection_list *dict_connections;
const char **error_r);
static struct client_dict_cmd *
{
struct client_dict_cmd *cmd;
return cmd;
}
{
}
{
return TRUE;
return FALSE;
}
{
/* Don't let callback see that we've created our
internal ioloop in case it wants to add some ios
or timeouts. */
}
}
{
/* stop client_dict_wait() */
}
}
static bool
{
enum dict_protocol_reply reply;
/* "" is a valid iteration reply */
} else {
value++;
args++;
}
return !cmd->unfinished;
}
static void
bool disconnected)
{
}
}
static struct client_dict_cmd *
{
struct client_dict_cmd *const *cmds;
unsigned int i, count;
for (i = 0; i < count; i++) {
if (!cmds[i]->background)
return cmds[i];
}
return NULL;
}
{
struct client_dict_cmd *cmd;
const char *error;
int cmd_diff;
/* find the first non-background command. there must be at least one. */
/* need to re-create this timeout. the currently-oldest
command was added when another command was still
running with an older timeout. */
dict->to_requests =
return;
}
"Dict server timeout: %s "
"(%u commands pending, oldest sent %u.%03u secs ago: %s, %s)",
}
static int
{
if (ret < 0)
return -1;
return 0;
}
static bool
const char **error_r)
{
int ret;
/* we're no longer idling. even with no_replies=TRUE we're going to
ret = -1;
} else {
if (ret < 0) {
}
}
/* Reconnect and try again. */
;
} else {
ret = 0;
}
}
if (cmd->no_replies) {
/* just send and forget */
return TRUE;
} else if (ret < 0) {
/* we didn't successfully send this command to dict */
return FALSE;
} else {
dict->to_requests =
}
return TRUE;
}
}
static bool
{
struct client_dict_cmd *cmd;
/* transactions commands don't have replies. only COMMIT has. */
return FALSE;
}
return TRUE;
}
static void
const char *query)
{
struct client_dict_cmd *cmd;
const char *error;
return;
if (!ctx->sent_begin) {
return;
}
ctx->query_count++;
}
{
}
{
if (client_dict_is_finished(dict))
}
{
struct client_dict_cmd *const *cmdp;
if (!(*cmdp)->background)
return TRUE;
}
return FALSE;
}
{
if (dict->idle_msecs > 0)
} else if (client_dict_is_finished(dict)) {
/* we had non-background commands, but now we're back to
having only background commands. remove timeouts. */
}
}
{
return;
if (!client_dict_have_nonbackground_cmds(dict)) {
/* we only have background-commands.
remove the request timeout. */
}
}
static int
{
struct client_dict_cmd *const *cmds;
i_error("%s: Received invalid async-id line: %s",
return -1;
}
for (i = 0; i < count; i++) {
return 0;
}
}
i_error("%s: Received async-id line, but all %u commands already have it: %s",
return -1;
}
const char *async_arg,
{
struct client_dict_cmd *const *cmds;
i_error("%s: Received invalid async-reply line: %s",
return -1;
}
for (i = 0; i < count; i++) {
*idx_r = i;
return 0;
}
}
i_error("%s: Received reply for nonexistent async-id %u: %s",
return -1;
}
{
struct client_dict_cmd *const *cmds;
const char *const *args;
unsigned int i, count;
bool finished;
if (line[0] == DICT_PROTOCOL_REPLY_ASYNC_ID)
if (count == 0) {
i_error("%s: Received reply without pending commands: %s",
return -1;
}
return -1;
args++;
} else {
i = 0;
}
client_dict_cmd_ref(cmds[i]);
if (!client_dict_cmd_unref(cmds[i])) {
/* disconnected during command handling */
return -1;
}
if (!finished) {
/* more lines needed for this command */
return 1;
}
return 1;
}
{
return 0;
/* Try again later */
return -1;
}
} else {
}
return -1;
}
return 0;
}
static void
{
struct client_dict_cmd *const *cmdp;
/* abort all commands */
}
}
{
/* all transactions that have sent BEGIN are no longer valid */
}
}
const char **error_r)
{
const char *error;
int ret;
if (!(*cmdp)->retry_errors) {
i++;
/* don't retry iteration that already returned
something to the caller. otherwise we'd return
duplicates. */
i++;
} else {
}
}
}
return -1;
}
if (array_count(&retry_cmds) == 0)
return 0;
/* if it fails again, don't retry anymore */
if (ret < 0) {
ret = -1;
}
return ret;
}
{
}
static const struct connection_settings dict_conn_set = {
.unix_client_connect_msecs = 1000,
};
static const struct connection_vfuncs dict_conn_vfuncs = {
};
static int
const struct dict_settings *set,
{
struct client_dict *dict;
unsigned int idle_msecs = DICT_CLIENT_DEFAULT_TIMEOUT_MSECS;
unsigned int warn_slow_msecs = DICT_CLIENT_DEFAULT_WARN_SLOW_MSECS;
/* uri = [idle_msecs=<n>:] [warn_slow_msecs=<n>:] [<path>] ":" <uri> */
for (;;) {
if (p == NULL) {
return -1;
}
*error_r = "Invalid idle_msecs";
return -1;
}
uri = p+1;
if (p == NULL) {
return -1;
}
*error_r = "Invalid warn_slow_msecs";
return -1;
}
uri = p+1;
} else {
break;
}
}
return -1;
}
if (dict_connections == NULL) {
}
if (uri[0] == ':') {
/* default path */
} else if (uri[0] == '/') {
/* absolute path */
} else {
/* relative path to base_dir */
}
return 0;
}
{
}
{
return;
}
{
}
{
int lock_msecs = (file_lock_wait_get_total_usecs() -
return t_strdup_printf(
"%d.%03d in dict wait, %d.%03d in other ioloops, %d.%03d in locks",
}
static const char *
const char *const *extra_args)
{
unsigned int tv_start_usec, tv_end_usec;
if (cmd->reconnected) {
int reconnected_msecs =
}
int async_reply_msecs =
}
if (extra_args != NULL &&
int server_msecs_since_start =
"took %u.%03d secs",
server_msecs_since_start/1000,
server_msecs_since_start%1000,
}
}
static void
enum dict_protocol_reply reply,
const char *value,
const char *const *extra_args,
const char *error,
bool disconnected ATTR_UNUSED)
{
struct dict_lookup_result result;
} else switch (reply) {
case DICT_PROTOCOL_REPLY_OK:
break;
break;
case DICT_PROTOCOL_REPLY_FAIL:
t_strdup_printf("dict-server returned failure: %s",
value);
break;
default:
"dict-client: Invalid lookup '%s' reply: %c%s",
break;
}
/* include timing info always in error messages */
} else if (!cmd->background &&
i_warning("read(%s): dict lookup took %s: %s",
}
}
static void
{
struct client_dict_cmd *cmd;
const char *query;
str_tabescape(key));
}
struct client_dict_sync_lookup {
char *error;
char *value;
int ret;
};
void *context)
{
}
{
struct client_dict_sync_lookup lookup;
case -1:
return -1;
case 0:
return 0;
case 1:
return 1;
}
i_unreached();
}
{
return;
}
static void
struct client_dict_cmd *cmd,
const char *const *extra_args)
{
/* iterator was already deinitialized */
return;
}
/* include timing info always in error messages */
} else if (!cmd->background &&
i_warning("read(%s): dict iteration took %s: %s",
}
}
} else {
/* synchronous lookup */
}
}
static void
enum dict_protocol_reply reply,
const char *value,
const char *const *extra_args,
const char *error,
bool disconnected ATTR_UNUSED)
{
struct client_dict_iter_result *result;
}
/* failed */
} else switch (reply) {
/* end of iteration */
return;
case DICT_PROTOCOL_REPLY_OK:
/* key \t value */
iter_value = extra_args[0];
extra_args++;
break;
case DICT_PROTOCOL_REPLY_FAIL:
break;
default:
break;
}
/* broken protocol */
}
return;
}
/* iterator was already deinitialized */
return;
}
}
static struct dict_iterate_context *
enum dict_iterate_flags flags)
{
struct client_dict_iterate_context *ctx;
}
static void
{
struct client_dict_cmd *cmd;
unsigned int i;
/* we can't do this query in _iterate_init(), because
_set_limit() hasn't been called yet at that point. */
}
}
{
struct client_dict_iterate_context *ctx =
(struct client_dict_iterate_context *)_ctx;
const struct client_dict_iter_result *results;
unsigned int count;
return FALSE;
}
ctx->result_idx++;
return TRUE;
}
}
ctx->result_idx = 0;
}
return FALSE;
}
const char **error_r)
{
struct client_dict_iterate_context *ctx =
(struct client_dict_iterate_context *)_ctx;
return ret;
}
static struct dict_transaction_context *
{
struct client_dict_transaction_context *ctx;
}
static void
{
}
static void
enum dict_protocol_reply reply,
const char *value,
const char *const *extra_args,
const char *error, bool disconnected)
{
struct dict_commit_result result = {
};
/* failed */
if (disconnected)
} else switch (reply) {
case DICT_PROTOCOL_REPLY_OK:
break;
break;
/* fallthrough */
case DICT_PROTOCOL_REPLY_FAIL: {
/* value contains the obsolete trans_id */
const char *error = extra_args[0];
extra_args++;
break;
}
default:
"dict-client: Invalid commit reply: %c%s",
break;
}
/* include timing info always in error messages */
i_warning("read(%s): dict commit took %s: "
"%s (%u commands, first: %s)",
}
}
static void
bool async,
void *context)
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
struct client_dict_cmd *cmd;
const char *query;
if (!async)
}
/* already failed */
struct dict_commit_result result = {
};
} else {
/* nothing changed */
struct dict_commit_result result = {
};
}
}
static void
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
if (ctx->sent_begin) {
const char *query;
}
}
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
const char *query;
}
const char *key)
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
const char *query;
str_tabescape(key));
}
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
const char *query;
}
struct dict dict_driver_client = {
.name = "proxy",
{
.init = client_dict_init,
.wait = client_dict_wait,
.set = client_dict_set,
}
};