diff options
Diffstat (limited to 'src/lib-master/ipc-client.c')
-rw-r--r-- | src/lib-master/ipc-client.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/src/lib-master/ipc-client.c b/src/lib-master/ipc-client.c new file mode 100644 index 0000000..b734c1b --- /dev/null +++ b/src/lib-master/ipc-client.c @@ -0,0 +1,220 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "net.h" +#include "llist.h" +#include "istream.h" +#include "ostream.h" +#include "hostpid.h" +#include "master-service.h" +#include "ipc-client.h" + +#include <unistd.h> + +struct ipc_client_cmd { + struct ipc_client_cmd *prev, *next; + + ipc_client_callback_t *callback; + void *context; +}; + +struct ipc_client { + char *path; + ipc_client_callback_t *callback; + + int fd; + struct io *io; + struct timeout *to_failed; + struct istream *input; + struct ostream *output; + struct ipc_client_cmd *cmds_head, *cmds_tail; + unsigned int aborted_cmds_count; +}; + +static void ipc_client_disconnect(struct ipc_client *client); + +static void ipc_client_input_line(struct ipc_client *client, const char *line) +{ + struct ipc_client_cmd *cmd = client->cmds_head; + enum ipc_client_cmd_state state; + bool disconnect = FALSE; + + if (client->aborted_cmds_count > 0) { + /* the command was already aborted */ + cmd = NULL; + } else if (cmd == NULL) { + i_error("IPC proxy sent unexpected input: %s", line); + return; + } + + switch (*line++) { + case ':': + state = IPC_CLIENT_CMD_STATE_REPLY; + break; + case '+': + state = IPC_CLIENT_CMD_STATE_OK; + break; + case '-': + state = IPC_CLIENT_CMD_STATE_ERROR; + break; + default: + i_error("IPC proxy sent invalid input: %s", line); + line = "Invalid input"; + disconnect = TRUE; + state = IPC_CLIENT_CMD_STATE_ERROR; + break; + } + + if (state != IPC_CLIENT_CMD_STATE_REPLY) { + if (cmd != NULL) + DLLIST2_REMOVE(&client->cmds_head, + &client->cmds_tail, cmd); + else + client->aborted_cmds_count--; + } + if (cmd != NULL) + cmd->callback(state, line, cmd->context); + if (state != IPC_CLIENT_CMD_STATE_REPLY) + i_free(cmd); + if (disconnect) + ipc_client_disconnect(client); +} + +static void ipc_client_input(struct ipc_client *client) +{ + const char *line; + + if (i_stream_read(client->input) < 0) { + ipc_client_disconnect(client); + return; + } + while ((line = i_stream_next_line(client->input)) != NULL) T_BEGIN { + ipc_client_input_line(client, line); + } T_END; +} + +static int ipc_client_connect(struct ipc_client *client) +{ + if (client->fd != -1) + return 0; + + client->fd = net_connect_unix(client->path); + if (client->fd == -1) { + i_error("connect(%s) failed: %m", client->path); + return -1; + } + + client->io = io_add(client->fd, IO_READ, ipc_client_input, client); + client->input = i_stream_create_fd(client->fd, SIZE_MAX); + client->output = o_stream_create_fd(client->fd, SIZE_MAX); + o_stream_set_no_error_handling(client->output, TRUE); + return 0; +} + +static void ipc_client_abort_commands(struct ipc_client *client, + const char *reason) +{ + struct ipc_client_cmd *cmd, *next; + + cmd = client->cmds_head; + client->cmds_head = client->cmds_tail = NULL; + for (; cmd != NULL; cmd = next) { + cmd->callback(IPC_CLIENT_CMD_STATE_ERROR, reason, cmd->context); + next = cmd->next; + i_free(cmd); + } +} + +static void ipc_client_disconnect(struct ipc_client *client) +{ + timeout_remove(&client->to_failed); + ipc_client_abort_commands(client, "Disconnected"); + + if (client->fd == -1) + return; + + io_remove(&client->io); + i_stream_destroy(&client->input); + o_stream_destroy(&client->output); + if (close(client->fd) < 0) + i_error("close(%s) failed: %m", client->path); + client->fd = -1; +} + +struct ipc_client * +ipc_client_init(const char *ipc_socket_path) +{ + struct ipc_client *client; + + client = i_new(struct ipc_client, 1); + client->path = i_strdup(ipc_socket_path); + client->fd = -1; + return client; +} + +void ipc_client_deinit(struct ipc_client **_client) +{ + struct ipc_client *client = *_client; + + *_client = NULL; + + ipc_client_disconnect(client); + i_free(client->path); + i_free(client); +} + +static void ipc_client_cmd_connect_failed(struct ipc_client *client) +{ + ipc_client_abort_commands(client, "ipc connect failed"); + timeout_remove(&client->to_failed); +} + +struct ipc_client_cmd * +ipc_client_cmd(struct ipc_client *client, const char *cmd, + ipc_client_callback_t *callback, void *context) +{ + struct ipc_client_cmd *ipc_cmd; + struct const_iovec iov[2]; + + ipc_cmd = i_new(struct ipc_client_cmd, 1); + ipc_cmd->callback = callback; + ipc_cmd->context = context; + DLLIST2_APPEND(&client->cmds_head, &client->cmds_tail, ipc_cmd); + + if (client->to_failed != NULL || + ipc_client_connect(client) < 0) { + /* Delay calling the failure callback. Fail all commands until + the callback is called. */ + if (client->to_failed == NULL) { + client->to_failed = timeout_add_short(0, + ipc_client_cmd_connect_failed, client); + } + } else { + iov[0].iov_base = cmd; + iov[0].iov_len = strlen(cmd); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; + o_stream_nsendv(client->output, iov, N_ELEMENTS(iov)); + } + return ipc_cmd; +} + +void ipc_client_cmd_abort(struct ipc_client *client, + struct ipc_client_cmd **_cmd) +{ + struct ipc_client_cmd *cmd = *_cmd; + + *_cmd = NULL; + cmd->callback = NULL; + /* Free the command only if it's the oldest. Free also other such + commands in case they were aborted earlier. */ + while (client->cmds_head != NULL && + client->cmds_head->callback == NULL) { + struct ipc_client_cmd *head = client->cmds_head; + + client->aborted_cmds_count++; + DLLIST2_REMOVE(&client->cmds_head, &client->cmds_tail, head); + i_free(head); + } +} |