dict-client.c revision 155e321dd4de5796e2f81a7d336b2df09b5603a0
/* 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_REQUEST_WARN_TIMEOUT_MSECS 5000
struct client_dict_cmd {
int refcount;
struct client_dict *dict;
struct timeval start_time;
char *query;
bool retry_errors;
bool no_replies;
bool unfinished;
bool background;
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;
char *last_connect_error;
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
{
return !cmd->unfinished;
}
static void
bool disconnected)
{
}
{
struct client_dict_cmd *const *cmds;
unsigned int i, count;
const char *error;
int cmd_diff;
/* find the first expired non-background command */
for (i = 0; i < count; i++) {
if (cmds[i]->background)
continue;
/* 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;
}
break;
}
"Dict server timeout: %s "
"(%u commands pending, oldest sent %u.%03u secs ago: %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. */
}
}
{
struct client_dict_cmd *const *cmds;
unsigned int count;
bool finished;
if (count == 0) {
i_error("%s: Received reply without pending commands: %s",
return -1;
}
client_dict_cmd_ref(cmds[0]);
if (!client_dict_cmd_unref(cmds[0])) {
/* 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;
/* uri = [idle_msecs=<n>:] [<path>] ":" <uri> */
if (p == NULL) {
return -1;
}
*error_r = "Invalid idle_msecs";
return -1;
}
uri = p+1;
}
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 secs (%d.%03d in dict wait, %d.%03d in other ioloops, "
}
static void
{
struct dict_lookup_result result;
} else switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
break;
break;
case DICT_PROTOCOL_REPLY_FAIL:
t_strdup_printf("dict-server returned failure: %s",
break;
default:
"dict-client: Invalid lookup '%s' reply: %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)
{
/* 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
{
struct client_dict_iter_result *result;
}
/* failed */
} else switch (*line) {
case '\0':
/* end of iteration */
return;
case DICT_PROTOCOL_REPLY_OK:
/* key \t value */
break;
case DICT_PROTOCOL_REPLY_FAIL:
break;
default:
break;
}
/* broken protocol */
}
return;
}
/* iterator was already deinitialized */
return;
}
else
value = "";
}
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
bool disconnected)
{
struct dict_commit_result result = {
};
/* failed */
if (disconnected)
} else switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
break;
break;
/* fallthrough */
case DICT_PROTOCOL_REPLY_FAIL: {
break;
}
default:
"dict-client: Invalid commit reply: %s", line);
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,
}
};