diff options
Diffstat (limited to 'src/ctl.c')
-rw-r--r-- | src/ctl.c | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/src/ctl.c b/src/ctl.c new file mode 100644 index 0000000..f9473a2 --- /dev/null +++ b/src/ctl.c @@ -0,0 +1,261 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> + +#include "ctl.h" +#include "marshal.h" +#include "log.h" +#include "compat/compat.h" + +/** + * Create a new listening Unix socket for control protocol. + * + * @param name The name of the Unix socket. + * @return The socket when successful, -1 otherwise. + */ +int +ctl_create(const char *name) +{ + int s; + struct sockaddr_un su; + int rc; + + log_debug("control", "create control socket %s", name); + + if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) return -1; + if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { + close(s); + return -1; + } + su.sun_family = AF_UNIX; + strlcpy(su.sun_path, name, sizeof(su.sun_path)); + if (bind(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) { + rc = errno; + close(s); + errno = rc; + return -1; + } + + log_debug("control", "listen to control socket %s", name); + if (listen(s, 5) == -1) { + rc = errno; + close(s); + errno = rc; + log_debug("control", "cannot listen to control socket %s", name); + return -1; + } + return s; +} + +/** + * Connect to the control Unix socket. + * + * @param name The name of the Unix socket. + * @return The socket when successful, -1 otherwise. + */ +int +ctl_connect(const char *name) +{ + int s; + struct sockaddr_un su; + int rc; + + log_debug("control", "connect to control socket %s", name); + + if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) return -1; + su.sun_family = AF_UNIX; + strlcpy(su.sun_path, name, sizeof(su.sun_path)); + if (connect(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) { + rc = errno; + log_warn("control", "unable to connect to socket %s", name); + close(s); + errno = rc; + return -1; + } + return s; +} + +/** + * Remove the control Unix socket. + * + * @param name The name of the Unix socket. + */ +void +ctl_cleanup(const char *name) +{ + log_debug("control", "cleanup control socket"); + if (unlink(name) == -1) log_warn("control", "unable to unlink %s", name); +} + +/** + * Serialize and "send" a structure through the control protocol. + * + * This function does not really send the message but outputs it to a buffer. + * + * @param output_buffer A pointer to a buffer to which the message will be + * appended. Can be @c NULL. In this case, the buffer will + * be allocated. + * @param[in,out] output_len The length of the provided buffer. Will be updated + * with the new length + * @param type The type of message we want to send. + * @param t The structure to be serialized and sent. + * @param mi The appropriate marshal structure for serialization. + * @return -1 in case of failure, 0 in case of success. + * + * Make sure this function logic matches the server-side one: @c levent_ctl_recv(). + */ +int +ctl_msg_send_unserialized(uint8_t **output_buffer, size_t *output_len, + enum hmsg_type type, void *t, struct marshal_info *mi) +{ + ssize_t len = 0, newlen; + void *buffer = NULL; + + log_debug("control", "send a message through control socket"); + if (t) { + len = marshal_serialize_(mi, t, &buffer, 0, NULL, 0); + if (len <= 0) { + log_warnx("control", "unable to serialize data"); + return -1; + } + } + + newlen = len + sizeof(struct hmsg_header); + + if (*output_buffer == NULL) { + *output_len = 0; + if ((*output_buffer = malloc(newlen)) == NULL) { + log_warn("control", "no memory available"); + free(buffer); + return -1; + } + } else { + void *new = realloc(*output_buffer, *output_len + newlen); + if (new == NULL) { + log_warn("control", "no memory available"); + free(buffer); + return -1; + } + *output_buffer = new; + } + + struct hmsg_header hdr; + memset(&hdr, 0, sizeof(struct hmsg_header)); + hdr.type = type; + hdr.len = len; + memcpy(*output_buffer + *output_len, &hdr, sizeof(struct hmsg_header)); + if (t) + memcpy(*output_buffer + *output_len + sizeof(struct hmsg_header), + buffer, len); + *output_len += newlen; + free(buffer); + return 0; +} + +/** + * "Receive" and unserialize a structure through the control protocol. + * + * Like @c ctl_msg_send_unserialized(), this function uses buffer to receive the + * incoming message. + * + * @param[in,out] input_buffer The buffer with the incoming message. Will be + * updated once the message has been unserialized to + * point to the remaining of the message or will be + * freed if all the buffer has been consumed. Can be + * @c NULL. + * @param[in,out] input_len The length of the provided buffer. Will be updated + * to the length of remaining data once the message + * has been unserialized. + * @param expected_type The expected message type. + * @param[out] t Will contain a pointer to the unserialized structure. + * Can be @c NULL if we don't want to store the + * answer. + * @param mi The appropriate marshal structure for unserialization. + * + * @return -1 in case of error, 0 in case of success and the number of bytes we + * request to complete unserialization. + * + * When requesting a notification, the input buffer is left untouched if we + * don't get one and we fail silently. + */ +size_t +ctl_msg_recv_unserialized(uint8_t **input_buffer, size_t *input_len, + enum hmsg_type expected_type, void **t, struct marshal_info *mi) +{ + struct hmsg_header hdr; + int rc = -1; + + if (*input_buffer == NULL || *input_len < sizeof(struct hmsg_header)) { + /* Not enough data. */ + return sizeof(struct hmsg_header) - *input_len; + } + + log_debug("control", "receive a message through control socket"); + memcpy(&hdr, *input_buffer, sizeof(struct hmsg_header)); + if (hdr.len > HMSG_MAX_SIZE) { + log_warnx("control", "message received is too large"); + /* We discard the whole buffer */ + free(*input_buffer); + *input_buffer = NULL; + *input_len = 0; + return -1; + } + if (*input_len < sizeof(struct hmsg_header) + hdr.len) { + /* Not enough data. */ + return sizeof(struct hmsg_header) + hdr.len - *input_len; + } + if (hdr.type != expected_type) { + if (expected_type == NOTIFICATION) return -1; + log_warnx("control", + "incorrect received message type (expected: %d, received: %d)", + expected_type, hdr.type); + goto end; + } + + if (t && !hdr.len) { + log_warnx("control", "no payload available in answer"); + goto end; + } + if (t) { + /* We have data to unserialize. */ + if (marshal_unserialize_(mi, *input_buffer + sizeof(struct hmsg_header), + hdr.len, t, NULL, 0, 0) <= 0) { + log_warnx("control", "unable to deserialize received data"); + goto end; + } + } + + rc = 0; +end: + /* Discard input buffer */ + *input_len -= sizeof(struct hmsg_header) + hdr.len; + if (*input_len == 0) { + free(*input_buffer); + *input_buffer = NULL; + } else + memmove(*input_buffer, + *input_buffer + sizeof(struct hmsg_header) + hdr.len, *input_len); + return rc; +} |