/* 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 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); } }