summaryrefslogtreecommitdiffstats
path: root/src/lib-master/ipc-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-master/ipc-client.c')
-rw-r--r--src/lib-master/ipc-client.c220
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);
+ }
+}