diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index e18ff338..2c3d3130 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -89,6 +89,7 @@ libguac_la_SOURCES = \ socket.c \ socket-fd.c \ socket-nest.c \ + socket-tee.c \ timestamp.c \ unicode.c \ user.c \ diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h index 27d0be5b..adc45ac8 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -191,6 +191,38 @@ guac_socket* guac_socket_open(int fd); */ guac_socket* guac_socket_nest(guac_socket* parent, int index); +/** + * Allocates and initializes a new guac_socket which delegates all socket + * operations to the given primary socket, while simultaneously duplicating all + * written data to the secondary socket. Freeing the returned guac_socket will + * free both primary and secondary sockets. + * + * Return values (error codes) will come only from the primary socket. Locks + * (like those used by guac_socket_instruction_begin() and + * guac_socket_instruction_end()) will affect only the primary socket. + * + * If an error occurs while allocating the guac_socket object, NULL is returned, + * and guac_error is set appropriately. + * + * @param primary + * The primary guac_socket to which all socket operations should be + * delegated. The error codes returned by socket operations, if any, will + * always come from this socket. This socket will also be the only socket + * locked when instructions begin (or unlocked when instructions end). + * + * @param secondary + * The secondary guac_socket to which all data written to the primary + * guac_socket should be copied. If an error prevents the write from + * succeeding, that error will be ignored. Only errors from the primary + * guac_socket will be acknowledged. + * + * @return + * A newly allocated guac_socket object associated with the given primary + * and secondary sockets, or NULL if an error occurs while allocating the + * guac_socket object. + */ +guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary); + /** * Writes the given unsigned int to the given guac_socket object. The data * written may be buffered until the buffer is flushed automatically or diff --git a/src/libguac/socket-tee.c b/src/libguac/socket-tee.c new file mode 100644 index 00000000..d6d6402b --- /dev/null +++ b/src/libguac/socket-tee.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "socket.h" + +#include + +/** + * Data specific to the tee implementation of guac_socket. + */ +typedef struct guac_socket_tee_data { + + /** + * The guac_socket to which all socket operations should be delegated. + */ + guac_socket* primary; + + /** + * The guac_socket to which all write and flush operations should be + * duplicated. + */ + guac_socket* secondary; + +} guac_socket_tee_data; + +/** + * Callback function which reads only from the primary socket. + * + * @param socket + * The tee socket to read from. + * + * @param buf + * The buffer to read data into. + * + * @param count + * The maximum number of bytes to read into the given buffer. + * + * @return + * The value returned by guac_socket_read() when invoked on the primary + * socket with the given parameters. + */ +static ssize_t __guac_socket_tee_read_handler(guac_socket* socket, + void* buf, size_t count) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate read to wrapped socket */ + return guac_socket_read(data->primary, buf, count); + +} + +/** + * Callback function which writes the given data to both underlying sockets, + * returning only the result from the primary socket. + * + * @param socket + * The tee socket to write through. + * + * @param buf + * The buffer of data to write. + * + * @param count + * The number of bytes in the buffer to be written. + * + * @return + * The number of bytes written if the write was successful, or -1 if an + * error occurs. + */ +static ssize_t __guac_socket_tee_write_handler(guac_socket* socket, + const void* buf, size_t count) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Write to secondary socket (ignoring result) */ + guac_socket_write(data->secondary, buf, count); + + /* Delegate write to wrapped socket */ + if (guac_socket_write(data->primary, buf, count)) + return -1; + + /* All data written successfully */ + return count; + +} + +/** + * Callback function which flushes both underlying sockets, returning only the + * result from the primary socket. + * + * @param socket + * The tee socket to flush. + * + * @return + * The value returned by guac_socket_flush() when invoked on the primary + * socket. + */ +static ssize_t __guac_socket_tee_flush_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Flush secondary socket (ignoring result) */ + guac_socket_flush(data->secondary); + + /* Delegate flush to wrapped socket */ + return guac_socket_flush(data->primary); + +} + +/** + * Callback function which delegates the lock operation to the primary + * socket alone. + * + * @param socket + * The tee socket on which guac_socket_instruction_begin() was invoked. + */ +static void __guac_socket_tee_lock_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate lock to wrapped socket */ + guac_socket_instruction_begin(data->primary); + +} + +/** + * Callback function which delegates the unlock operation to the primary + * socket alone. + * + * @param socket + * The tee socket on which guac_socket_instruction_end() was invoked. + */ +static void __guac_socket_tee_unlock_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate unlock to wrapped socket */ + guac_socket_instruction_end(data->primary); + +} + +/** + * Callback function which delegates the select operation to the primary + * socket alone. + * + * @param socket + * The tee socket on which guac_socket_select() was invoked. + * + * @param usec_timeout + * The timeout to specify when invoking guac_socket_select() on the + * primary socket. + * + * @return + * The value returned by guac_socket_select() when invoked with the + * given parameters on the primary socket. + */ +static int __guac_socket_tee_select_handler(guac_socket* socket, + int usec_timeout) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate select to wrapped socket */ + return guac_socket_select(data->primary, usec_timeout); + +} + +/** + * Callback function which frees all underlying data associated with the + * given tee socket, including both primary and secondary sockets. + * + * @param socket + * The tee socket being freed. + * + * @return + * Always zero. + */ +static int __guac_socket_tee_free_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Free underlying sockets */ + guac_socket_free(data->primary); + guac_socket_free(data->secondary); + + /* Freeing the tee socket always succeeds */ + free(data); + return 0; + +} + +guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary) { + + /* Set up socket to split outout into a file */ + guac_socket_tee_data* data = malloc(sizeof(guac_socket_tee_data)); + data->primary = primary; + data->secondary = secondary; + + /* Associate tee-specific data with new socket */ + guac_socket* socket = guac_socket_alloc(); + socket->data = data; + + /* Assign handlers */ + socket->read_handler = __guac_socket_tee_read_handler; + socket->write_handler = __guac_socket_tee_write_handler; + socket->select_handler = __guac_socket_tee_select_handler; + socket->flush_handler = __guac_socket_tee_flush_handler; + socket->lock_handler = __guac_socket_tee_lock_handler; + socket->unlock_handler = __guac_socket_tee_unlock_handler; + socket->free_handler = __guac_socket_tee_free_handler; + + return socket; + +} +