2016-09-18 13:35:43 -07:00
|
|
|
#include "io.h"
|
2016-09-15 16:28:52 -07:00
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
2016-09-18 13:35:43 -07:00
|
|
|
#include <inttypes.h>
|
2016-09-23 17:10:15 -07:00
|
|
|
#include <stdarg.h>
|
2016-10-18 12:38:30 -07:00
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <netinet/in.h>
|
2016-09-23 17:10:15 -07:00
|
|
|
#include <utstring.h>
|
2016-09-15 16:28:52 -07:00
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
|
2016-10-18 12:38:30 -07:00
|
|
|
/**
|
|
|
|
* Binds to an Internet socket at the given port. Removes any existing file at
|
|
|
|
* the pathname. Returns a non-blocking file descriptor for the socket, or -1
|
|
|
|
* if an error occurred.
|
|
|
|
*
|
|
|
|
* @note Since the returned file descriptor is non-blocking, it is not
|
|
|
|
* recommended to use the Linux read and write calls directly, since these
|
|
|
|
* might read or write a partial message. Instead, use the provided
|
|
|
|
* write_message and read_message methods.
|
|
|
|
*
|
|
|
|
* @param port The port to bind to.
|
2016-11-02 00:09:04 -07:00
|
|
|
* @param shall_listen Are we also starting to listen on the socket?
|
2016-10-18 12:38:30 -07:00
|
|
|
* @return A non-blocking file descriptor for the socket, or -1 if an error
|
|
|
|
* occurs.
|
|
|
|
*/
|
2016-11-02 00:09:04 -07:00
|
|
|
int bind_inet_sock(const int port, bool shall_listen) {
|
2016-10-18 12:38:30 -07:00
|
|
|
struct sockaddr_in name;
|
|
|
|
int socket_fd = socket(PF_INET, SOCK_STREAM, 0);
|
|
|
|
if (socket_fd < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("socket() failed for port %d.", port);
|
2016-10-18 12:38:30 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
name.sin_family = AF_INET;
|
|
|
|
name.sin_port = htons(port);
|
|
|
|
name.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
|
int on = 1;
|
|
|
|
/* TODO(pcm): http://stackoverflow.com/q/1150635 */
|
|
|
|
if (ioctl(socket_fd, FIONBIO, (char *) &on) < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("ioctl failed");
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("setsockopt failed for port %d", port);
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (bind(socket_fd, (struct sockaddr *) &name, sizeof(name)) < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Bind failed for port %d", port);
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-02 00:09:04 -07:00
|
|
|
if (shall_listen && listen(socket_fd, 5) == -1) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Could not listen to socket %d", port);
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return socket_fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds to a Unix domain streaming socket at the given
|
|
|
|
* pathname. Removes any existing file at the pathname.
|
|
|
|
*
|
|
|
|
* @param socket_pathname The pathname for the socket.
|
2016-11-02 00:09:04 -07:00
|
|
|
* @param shall_listen Are we also starting to listen on the socket?
|
2016-10-18 12:38:30 -07:00
|
|
|
* @return A blocking file descriptor for the socket, or -1 if an error
|
|
|
|
* occurs.
|
|
|
|
*/
|
2016-11-02 00:09:04 -07:00
|
|
|
int bind_ipc_sock(const char *socket_pathname, bool shall_listen) {
|
2016-09-15 16:28:52 -07:00
|
|
|
struct sockaddr_un socket_address;
|
2016-09-27 18:51:35 -07:00
|
|
|
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
2016-09-15 16:28:52 -07:00
|
|
|
if (socket_fd < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("socket() failed for pathname %s.", socket_pathname);
|
2016-09-15 16:28:52 -07:00
|
|
|
return -1;
|
|
|
|
}
|
2016-09-27 18:51:35 -07:00
|
|
|
/* Tell the system to allow the port to be reused. */
|
|
|
|
int on = 1;
|
|
|
|
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
|
|
|
|
sizeof(on)) < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("setsockopt failed for pathname %s", socket_pathname);
|
2016-09-27 18:51:35 -07:00
|
|
|
close(socket_fd);
|
2016-10-18 12:38:30 -07:00
|
|
|
return -1;
|
2016-09-27 18:51:35 -07:00
|
|
|
}
|
2016-09-15 16:28:52 -07:00
|
|
|
|
|
|
|
unlink(socket_pathname);
|
2016-11-19 12:19:49 -08:00
|
|
|
memset(&socket_address, 0, sizeof(socket_address));
|
2016-09-15 16:28:52 -07:00
|
|
|
socket_address.sun_family = AF_UNIX;
|
|
|
|
if (strlen(socket_pathname) + 1 > sizeof(socket_address.sun_path)) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Socket pathname is too long.");
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
2016-09-15 16:28:52 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
strncpy(socket_address.sun_path, socket_pathname,
|
|
|
|
strlen(socket_pathname) + 1);
|
|
|
|
|
|
|
|
if (bind(socket_fd, (struct sockaddr *) &socket_address,
|
2016-11-19 12:19:49 -08:00
|
|
|
sizeof(socket_address)) != 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Bind failed for pathname %s.", socket_pathname);
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-02 00:09:04 -07:00
|
|
|
if (shall_listen && listen(socket_fd, 5) == -1) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Could not listen to socket %s", socket_pathname);
|
2016-10-18 12:38:30 -07:00
|
|
|
close(socket_fd);
|
2016-09-15 16:28:52 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return socket_fd;
|
|
|
|
}
|
|
|
|
|
2016-10-18 12:38:30 -07:00
|
|
|
/**
|
|
|
|
* Connects to a Unix domain streaming socket at the given
|
2016-09-15 16:28:52 -07:00
|
|
|
* pathname. Returns a file descriptor for the socket, or -1 if
|
2016-10-18 12:38:30 -07:00
|
|
|
* an error occurred.
|
|
|
|
*/
|
2016-09-15 16:28:52 -07:00
|
|
|
int connect_ipc_sock(const char *socket_pathname) {
|
|
|
|
struct sockaddr_un socket_address;
|
|
|
|
int socket_fd;
|
|
|
|
|
2016-09-17 15:15:18 -07:00
|
|
|
socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
2016-09-15 16:28:52 -07:00
|
|
|
if (socket_fd < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("socket() failed for pathname %s.", socket_pathname);
|
2016-09-15 16:28:52 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-11-19 12:19:49 -08:00
|
|
|
memset(&socket_address, 0, sizeof(socket_address));
|
2016-09-15 16:28:52 -07:00
|
|
|
socket_address.sun_family = AF_UNIX;
|
|
|
|
if (strlen(socket_pathname) + 1 > sizeof(socket_address.sun_path)) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Socket pathname is too long.");
|
2016-09-15 16:28:52 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
strncpy(socket_address.sun_path, socket_pathname,
|
|
|
|
strlen(socket_pathname) + 1);
|
|
|
|
|
|
|
|
if (connect(socket_fd, (struct sockaddr *) &socket_address,
|
2016-11-19 12:19:49 -08:00
|
|
|
sizeof(socket_address)) != 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Connection to socket failed for pathname %s.", socket_pathname);
|
2016-09-15 16:28:52 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return socket_fd;
|
|
|
|
}
|
|
|
|
|
2016-10-18 12:38:30 -07:00
|
|
|
/**
|
|
|
|
* Accept a new client connection on the given socket
|
|
|
|
* descriptor. Returns a descriptor for the new socket.
|
|
|
|
*/
|
2016-09-17 15:15:18 -07:00
|
|
|
int accept_client(int socket_fd) {
|
2016-09-27 18:51:35 -07:00
|
|
|
int client_fd = accept(socket_fd, NULL, NULL);
|
2016-09-17 15:15:18 -07:00
|
|
|
if (client_fd < 0) {
|
2016-11-15 20:33:29 -08:00
|
|
|
LOG_ERROR("Error reading from socket.");
|
2016-09-17 15:15:18 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return client_fd;
|
|
|
|
}
|
|
|
|
|
2016-10-03 17:55:57 -07:00
|
|
|
/**
|
2016-10-18 12:38:30 -07:00
|
|
|
* Write a sequence of bytes into a file descriptor. This will block until one
|
|
|
|
* of the following happens: (1) there is an error (2) end of file, or (3) all
|
|
|
|
* length bytes have been written.
|
2016-10-03 17:55:57 -07:00
|
|
|
*
|
2016-10-18 12:38:30 -07:00
|
|
|
* @param fd The file descriptor to write to. It can be non-blocking.
|
2016-10-03 17:55:57 -07:00
|
|
|
* @param cursor The cursor pointing to the beginning of the bytes to send.
|
|
|
|
* @param length The size of the bytes sequence to write.
|
2016-10-18 12:38:30 -07:00
|
|
|
* @return int Whether there was an error while writing. 0 corresponds to
|
|
|
|
* success and -1 corresponds to an error (errno will be set).
|
2016-10-03 17:55:57 -07:00
|
|
|
*/
|
2016-10-18 12:38:30 -07:00
|
|
|
int write_bytes(int fd, uint8_t *cursor, size_t length) {
|
2016-10-03 17:55:57 -07:00
|
|
|
ssize_t nbytes = 0;
|
|
|
|
while (length > 0) {
|
|
|
|
/* While we haven't written the whole message, write to the file
|
|
|
|
* descriptor, advance the cursor, and decrease the amount left to write. */
|
|
|
|
nbytes = write(fd, cursor, length);
|
2016-10-18 12:38:30 -07:00
|
|
|
if (nbytes < 0) {
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* TODO(swang): Return the error instead of exiting. */
|
|
|
|
/* Force an exit if there was any other type of error. */
|
|
|
|
CHECK(nbytes < 0);
|
|
|
|
}
|
|
|
|
if (nbytes == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2016-10-03 17:55:57 -07:00
|
|
|
cursor += nbytes;
|
|
|
|
length -= nbytes;
|
|
|
|
}
|
2016-10-18 12:38:30 -07:00
|
|
|
return 0;
|
2016-10-03 17:55:57 -07:00
|
|
|
}
|
|
|
|
|
2016-09-27 18:51:35 -07:00
|
|
|
/**
|
|
|
|
* Write a sequence of bytes on a file descriptor. The bytes should then be read
|
|
|
|
* by read_message.
|
|
|
|
*
|
2016-10-18 12:38:30 -07:00
|
|
|
* @param fd The file descriptor to write to. It can be non-blocking.
|
2016-09-27 18:51:35 -07:00
|
|
|
* @param type The type of the message to send.
|
|
|
|
* @param length The size in bytes of the bytes parameter.
|
|
|
|
* @param bytes The address of the message to send.
|
2016-10-18 12:38:30 -07:00
|
|
|
* @return int Whether there was an error while writing. 0 corresponds to
|
|
|
|
* success and -1 corresponds to an error (errno will be set).
|
2016-09-27 18:51:35 -07:00
|
|
|
*/
|
2016-10-18 12:38:30 -07:00
|
|
|
int write_message(int fd, int64_t type, int64_t length, uint8_t *bytes) {
|
|
|
|
int closed;
|
|
|
|
closed = write_bytes(fd, (uint8_t *) &type, sizeof(type));
|
|
|
|
if (closed) {
|
|
|
|
return closed;
|
|
|
|
}
|
|
|
|
closed = write_bytes(fd, (uint8_t *) &length, sizeof(length));
|
|
|
|
if (closed) {
|
|
|
|
return closed;
|
|
|
|
}
|
|
|
|
closed = write_bytes(fd, bytes, length * sizeof(char));
|
|
|
|
if (closed) {
|
|
|
|
return closed;
|
|
|
|
}
|
|
|
|
return 0;
|
2016-10-03 17:55:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-10-18 12:38:30 -07:00
|
|
|
* Read a sequence of bytes from a file descriptor into a buffer. This will
|
|
|
|
* block until one of the following happens: (1) there is an error (2) end of
|
|
|
|
* file, or (3) all length bytes have been written.
|
2016-10-03 17:55:57 -07:00
|
|
|
*
|
|
|
|
* @note The buffer pointed to by cursor must already have length number of
|
|
|
|
* bytes allocated before calling this method.
|
|
|
|
*
|
2016-10-18 12:38:30 -07:00
|
|
|
* @param fd The file descriptor to read from. It can be non-blocking.
|
2016-10-03 17:55:57 -07:00
|
|
|
* @param cursor The cursor pointing to the beginning of the buffer.
|
|
|
|
* @param length The size of the byte sequence to read.
|
2016-10-18 12:38:30 -07:00
|
|
|
* @return int Whether there was an error while writing. 0 corresponds to
|
|
|
|
* success and -1 corresponds to an error (errno will be set).
|
2016-10-03 17:55:57 -07:00
|
|
|
*/
|
|
|
|
int read_bytes(int fd, uint8_t *cursor, size_t length) {
|
|
|
|
ssize_t nbytes = 0;
|
|
|
|
while (length > 0) {
|
|
|
|
/* While we haven't read the whole message, read from the file descriptor,
|
|
|
|
* advance the cursor, and decrease the amount left to read. */
|
|
|
|
nbytes = read(fd, cursor, length);
|
|
|
|
if (nbytes < 0) {
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Force an exit if there was any other type of error. */
|
|
|
|
CHECK(nbytes < 0);
|
|
|
|
}
|
|
|
|
if (nbytes == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
cursor += nbytes;
|
|
|
|
length -= nbytes;
|
|
|
|
}
|
|
|
|
return 0;
|
2016-09-18 13:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-09-27 18:51:35 -07:00
|
|
|
/**
|
2016-10-03 17:55:57 -07:00
|
|
|
* Read a sequence of bytes written by write_message from a file descriptor.
|
|
|
|
* This allocates space for the message.
|
2016-09-27 18:51:35 -07:00
|
|
|
*
|
|
|
|
* @note The caller must free the memory.
|
|
|
|
*
|
2016-10-18 12:38:30 -07:00
|
|
|
* @param fd The file descriptor to read from. It can be non-blocking.
|
2016-09-27 18:51:35 -07:00
|
|
|
* @param type The type of the message that is read will be written at this
|
2016-11-04 00:41:20 -07:00
|
|
|
* address. If there was an error while reading, this will be
|
|
|
|
* DISCONNECT_CLIENT.
|
2016-09-27 18:51:35 -07:00
|
|
|
* @param length The size in bytes of the message that is read will be written
|
2016-11-04 00:41:20 -07:00
|
|
|
* at this address. This size does not include the bytes used to encode
|
|
|
|
* the type and length. If there was an error while reading, this will
|
|
|
|
* be 0.
|
2016-09-27 18:51:35 -07:00
|
|
|
* @param bytes The address at which to write the pointer to the bytes that are
|
2016-11-04 00:41:20 -07:00
|
|
|
* read and allocated by this function. If there was an error while
|
|
|
|
* reading, this will be NULL.
|
2016-09-27 18:51:35 -07:00
|
|
|
* @return Void.
|
|
|
|
*/
|
|
|
|
void read_message(int fd, int64_t *type, int64_t *length, uint8_t **bytes) {
|
2016-11-19 12:19:49 -08:00
|
|
|
int closed = read_bytes(fd, (uint8_t *) type, sizeof(*type));
|
2016-10-03 17:55:57 -07:00
|
|
|
if (closed) {
|
|
|
|
goto disconnected;
|
|
|
|
}
|
2016-11-19 12:19:49 -08:00
|
|
|
closed = read_bytes(fd, (uint8_t *) length, sizeof(*length));
|
2016-10-03 17:55:57 -07:00
|
|
|
if (closed) {
|
|
|
|
goto disconnected;
|
2016-09-15 16:28:52 -07:00
|
|
|
}
|
2016-09-18 13:35:43 -07:00
|
|
|
*bytes = malloc(*length * sizeof(uint8_t));
|
2016-10-03 17:55:57 -07:00
|
|
|
closed = read_bytes(fd, *bytes, *length);
|
|
|
|
if (closed) {
|
|
|
|
free(*bytes);
|
|
|
|
goto disconnected;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
disconnected:
|
|
|
|
/* Handle the case in which the socket is closed. */
|
|
|
|
*type = DISCONNECT_CLIENT;
|
|
|
|
*length = 0;
|
|
|
|
*bytes = NULL;
|
|
|
|
return;
|
2016-09-18 13:35:43 -07:00
|
|
|
}
|
|
|
|
|
2016-11-04 00:41:20 -07:00
|
|
|
/**
|
|
|
|
* Read a sequence of bytes written by write_message from a file descriptor.
|
|
|
|
* This does not allocate space for the message if the provided buffer is
|
|
|
|
* large enough and can therefore often avoid allocations.
|
|
|
|
*
|
|
|
|
* @note The caller must create and free the buffer.
|
|
|
|
*
|
|
|
|
* @param fd The file descriptor to read from. It can be non-blocking.
|
|
|
|
* @param type The type of the message that is read will be written at this
|
|
|
|
* address. If there was an error while reading, this will be
|
|
|
|
* DISCONNECT_CLIENT.
|
|
|
|
* @param buffer The array the message will be written to. If it is not
|
|
|
|
* large enough to hold the message, it will be enlarged by read_buffer.
|
|
|
|
* @return Number of bytes of the message that were read. This size does not
|
|
|
|
* include the bytes used to encode the type and length. If there was
|
|
|
|
* an error while reading, this will be 0.
|
|
|
|
*/
|
|
|
|
int64_t read_buffer(int fd, int64_t *type, UT_array *buffer) {
|
|
|
|
int64_t length;
|
2016-11-19 12:19:49 -08:00
|
|
|
int closed = read_bytes(fd, (uint8_t *) type, sizeof(*type));
|
2016-11-04 00:41:20 -07:00
|
|
|
if (closed) {
|
|
|
|
goto disconnected;
|
|
|
|
}
|
2016-11-19 12:19:49 -08:00
|
|
|
closed = read_bytes(fd, (uint8_t *) &length, sizeof(length));
|
2016-11-04 00:41:20 -07:00
|
|
|
if (closed) {
|
|
|
|
goto disconnected;
|
|
|
|
}
|
|
|
|
if (length > utarray_len(buffer)) {
|
|
|
|
utarray_resize(buffer, length);
|
|
|
|
}
|
|
|
|
closed = read_bytes(fd, (uint8_t *) utarray_front(buffer), length);
|
|
|
|
if (closed) {
|
|
|
|
goto disconnected;
|
|
|
|
}
|
|
|
|
return length;
|
|
|
|
disconnected:
|
|
|
|
/* Handle the case in which the socket is closed. */
|
|
|
|
*type = DISCONNECT_CLIENT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-09-18 13:35:43 -07:00
|
|
|
/* Write a null-terminated string to a file descriptor. */
|
2016-09-27 18:51:35 -07:00
|
|
|
void write_log_message(int fd, char *message) {
|
2016-09-18 13:35:43 -07:00
|
|
|
/* Account for the \0 at the end of the string. */
|
2016-09-27 18:51:35 -07:00
|
|
|
write_message(fd, LOG_MESSAGE, strlen(message) + 1, (uint8_t *) message);
|
2016-09-18 13:35:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Reads a null-terminated string from the file descriptor that has been
|
2016-09-27 18:51:35 -07:00
|
|
|
* written by write_log_message. Allocates and returns a pointer to the string.
|
2016-09-18 13:35:43 -07:00
|
|
|
* NOTE: Caller must free the memory! */
|
2016-09-27 18:51:35 -07:00
|
|
|
char *read_log_message(int fd) {
|
2016-09-18 13:35:43 -07:00
|
|
|
uint8_t *bytes;
|
2016-09-27 18:51:35 -07:00
|
|
|
int64_t type;
|
2016-09-18 13:35:43 -07:00
|
|
|
int64_t length;
|
2016-09-27 18:51:35 -07:00
|
|
|
read_message(fd, &type, &length, &bytes);
|
|
|
|
CHECK(type == LOG_MESSAGE);
|
2016-09-18 13:35:43 -07:00
|
|
|
return (char *) bytes;
|
2016-09-15 16:28:52 -07:00
|
|
|
}
|
2016-09-23 17:10:15 -07:00
|
|
|
|
2016-09-27 18:51:35 -07:00
|
|
|
void write_formatted_log_message(int socket_fd, const char *format, ...) {
|
2016-09-23 17:10:15 -07:00
|
|
|
UT_string *cmd;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
utstring_new(cmd);
|
|
|
|
va_start(ap, format);
|
|
|
|
utstring_printf_va(cmd, format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
2016-09-27 18:51:35 -07:00
|
|
|
write_log_message(socket_fd, utstring_body(cmd));
|
2016-09-23 17:10:15 -07:00
|
|
|
utstring_free(cmd);
|
|
|
|
}
|