dict-client.c revision 567ace1f60eca983acbc9b42d7569a5c16e8c3ec
/* Copyright (c) 2005-2016 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "llist.h"
#include "str.h"
#include "strescape.h"
#include "net.h"
#include "istream.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_READ_TIMEOUT_SECS 30
/* Log a warning if dict lookup takes longer than this many seconds. */
#define DICT_CLIENT_READ_WARN_TIMEOUT_SECS 5
struct client_dict {
int fd;
const char *uri;
const char *username;
const char *path;
enum dict_data_type value_type;
unsigned int idle_msecs;
unsigned int connect_counter;
unsigned int transaction_id_counter;
unsigned int async_commits;
unsigned int iter_replies_skip;
unsigned int in_iteration:1;
unsigned int handshaked:1;
};
struct client_dict_iterate_context {
struct dict_iterate_context ctx;
char *error;
bool finished;
};
struct client_dict_transaction_context {
struct dict_transaction_context ctx;
/* for async commits */
void *context;
char *error;
unsigned int id;
unsigned int connect_counter;
unsigned int sent_begin:1;
unsigned int async:1;
unsigned int committed:1;
};
const char **error_r)
{
/* not connected currently */
return -1;
}
/* Send failed */
if (!dict->handshaked) {
/* we're trying to send hello, don't try to reconnect */
return -1;
}
/* Reconnect and try again. */
return -1;
return -1;
}
}
return 0;
}
static int
const char **error_r)
{
const char *query;
return -1;
return 0;
}
static int
{
if (!ctx->sent_begin) {
return -1;
}
*error_r = "Reconnected to dict-server - transaction lost";
return -1;
}
/* not connected, this'll fail */
*error_r = "Disconnected from dict-server";
return -1;
}
/* Send failed. Our transactions have died, so don't even try
to re-send the command */
return -1;
}
return 0;
}
static void
const char *query)
{
const char *error;
return;
}
static struct client_dict_transaction_context *
{
struct client_dict_transaction_context *ctx;
return ctx;
}
return NULL;
}
static void
const struct dict_commit_result *result)
{
struct client_dict_transaction_context *ctx;
return;
}
/* transaction isn't committed yet, but we disconnected from
dict. mark it as failed so commit will fail later. */
i_error("dict-client: Received transaction reply before it was committed");
return;
}
return;
}
/* the callback may call the dict code again, so remove this
transaction before calling it */
if (--dict->async_commits == 0) {
}
}
{
unsigned int diff;
do {
alarm(0);
if (ret != 0)
break;
/* interrupted most likely because of timeout,
but check anyway. */
if (ret > 0) {
if (diff >= DICT_CLIENT_READ_WARN_TIMEOUT_SECS) {
i_warning("read(%s): dict lookup took %u seconds",
}
}
return ret;
}
static int
const char **error_r)
{
unsigned int id;
char *line;
switch (ret) {
case -1:
return -1;
case -2:
return -1;
case 0:
"read(%s) failed: Timeout after %u seconds",
return -1;
default:
break;
}
}
if (*line == DICT_PROTOCOL_REPLY_ASYNC_COMMIT) {
struct dict_commit_result result;
switch (line[1]) {
case DICT_PROTOCOL_REPLY_OK:
break;
break;
case DICT_PROTOCOL_REPLY_FAIL: {
"dict-server returned failure: %s",
break;
}
default:
"dict-client: Invalid async commit line: %s", line);
return -1;
}
return -1;
}
return 0;
}
if (dict->iter_replies_skip > 0) {
/* called aborted the iteration before finishing it.
skip over the iteration reply */
if (*line == DICT_PROTOCOL_REPLY_OK)
return 0;
"dict-client: Invalid iteration reply line: %s", line);
return -1;
}
return 0;
}
return 1;
}
const char **error_r)
{
int ret;
return ret;
}
{
dict->async_commits == 0;
}
{
if (client_dict_is_finished(dict))
}
{
if (dict->idle_msecs > 0)
} else if (client_dict_is_finished(dict)) {
}
}
{
int ret;
;
return ret < 0 ? -1 : 0;
}
{
const char *query;
/* Try again later */
*error_r = "Waiting until the next connect attempt";
return -1;
}
} else {
}
return -1;
}
/* Dictionary lookups are blocking */
return -1;
}
return 0;
}
{
dict->connect_counter++;
dict->iter_replies_skip = 0;
/* abort all pending async commits */
}
}
}
static int
const struct dict_settings *set,
{
struct client_dict *dict;
const char *p, *dest_uri;
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 (uri[0] == ':') {
/* default path */
} else if (uri[0] == '/') {
/* absolute path */
} else {
/* relative path to base_dir */
}
return 0;
}
{
}
{
const char *error;
char *line;
int ret;
while (dict->async_commits > 0) {
break;
}
if (ret > 0) {
const char *reason = t_strdup_printf(
"dict-client: Unexpected reply waiting waiting for async commits: %s", line);
break;
}
}
/* we should have aborted all the async calls if we disconnected */
}
{
const char *query;
char *line;
str_tabescape(key));
return -1;
/* read reply */
return -1;
switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
return 1;
return 0;
case DICT_PROTOCOL_REPLY_FAIL:
t_strdup_printf("dict-server returned failure: %s",
return -1;
default:
return -1;
}
}
static struct dict_iterate_context *
enum dict_iterate_flags flags)
{
struct client_dict_iterate_context *ctx;
unsigned int i;
const char *error;
if (dict->in_iteration)
i_panic("dict-client: Only one iteration supported");
}
}
{
struct client_dict_iterate_context *ctx =
(struct client_dict_iterate_context *)_ctx;
const char *error;
return FALSE;
/* read next reply */
return FALSE;
}
if (*line == '\0') {
/* end of iteration */
return FALSE;
}
/* line contains key \t value */
switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
break;
case DICT_PROTOCOL_REPLY_FAIL:
return FALSE;
default:
break;
}
/* broken protocol */
return FALSE;
}
*value++ = '\0';
return TRUE;
}
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;
}
{
const char *error;
char *line;
int ret;
do {
if (ret < 0) {
} else if (ret > 0) {
const char *reason = t_strdup_printf(
"dict-client: Unexpected reply waiting waiting for async commits: %s", line);
}
}
static void
bool async,
void *context)
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
unsigned int id;
struct dict_commit_result result;
const char *query;
char *line;
else if (async) {
if (dict->async_commits++ == 0) {
}
return;
} else {
/* sync commit, read reply */
} else switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
break;
break;
case DICT_PROTOCOL_REPLY_FAIL: {
"dict-server returned failure: %s",
break;
}
default:
"dict-client: Invalid commit reply: %s", line);
break;
}
"dict-client: Invalid commit reply, "
}
}
}
}
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",
{
}
};