/* Copyright (c) 2005-2018 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. */
/* When dict lookup timeout is reached, wait a bit longer if the last dict
ioloop wait was shorter than this. */
/* Log a warning if dict lookup takes longer than this many milliseconds. */
struct client_dict_cmd {
int refcount;
char *query;
unsigned int async_id;
bool reconnected;
bool retry_errors;
bool no_replies;
bool unfinished;
bool background;
const char *const *extra_args, const char *error,
bool disconnected);
struct {
void *context;
} api_callback;
};
struct dict_connection {
};
struct client_dict {
unsigned warn_slow_msecs;
char *last_connect_error;
unsigned int idle_msecs;
unsigned int transaction_id_counter;
};
struct client_dict_iter_result {
};
struct client_dict_iterate_context {
char *error;
const char **paths;
unsigned int result_idx;
bool cmd_sent;
bool seen_results;
bool finished;
bool deinit;
};
struct client_dict_transaction_context {
char *first_query;
char *error;
unsigned int id;
unsigned int query_count;
};
const char **error_r);
static struct client_dict_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
{
/* "" is a valid iteration reply */
} else {
value++;
args++;
}
return !cmd->unfinished;
}
static void
bool disconnected)
{
}
}
static struct client_dict_cmd *
{
unsigned int i, count;
for (i = 0; i < count; i++) {
if (!cmds[i]->background)
return cmds[i];
}
return NULL;
}
{
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;
}
/* If we've gotten here because all the time was spent in other ioloops
or locks, make sure there's a bit of time waiting for the dict
ioloop as well. There's a good chance that the reply can be read. */
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
{
/* transactions commands don't have replies. only COMMIT has. */
return FALSE;
}
return TRUE;
}
static void
const char *query)
{
const char *error;
return;
if (!ctx->sent_begin) {
return;
}
ctx->query_count++;
}
{
}
{
if (client_dict_is_finished(dict))
}
{
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
{
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,
{
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;
}
{
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
{
/* 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;
}
{
}
.unix_client_connect_msecs = 1000,
};
};
static int
const struct dict_settings *set,
{
/* 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;
}
{
}
{
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)
{
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)
{
} else switch (reply) {
case DICT_PROTOCOL_REPLY_OK:
break;
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
{
const char *query;
str_tabescape(key));
}
struct client_dict_sync_lookup {
char *error;
char *value;
int ret;
};
void *context)
{
}
{
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)
{
}
/* 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)
{
}
static void
{
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;
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;
return ret;
}
static struct dict_transaction_context *
{
}
static void
{
}
static void
enum dict_protocol_reply reply,
const char *value,
const char *const *extra_args,
const char *error, bool disconnected)
{
};
/* 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 */
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;
const char *query;
if (!async)
}
/* already failed */
};
} else {
/* nothing changed */
};
}
}
static void
{
(struct client_dict_transaction_context *)_ctx;
if (ctx->sent_begin) {
const char *query;
}
}
{
(struct client_dict_transaction_context *)_ctx;
const char *query;
}
const char *key)
{
(struct client_dict_transaction_context *)_ctx;
const char *query;
str_tabescape(key));
}
{
(struct client_dict_transaction_context *)_ctx;
const char *query;
}
{
(struct client_dict_transaction_context *)_ctx;
const char *query;
}
.name = "proxy",
{
.init = client_dict_init,
.wait = client_dict_wait,
.set = client_dict_set,
}
};