Commit 7b41a8ee for guacamole.apache.org
commit 7b41a8ee3b6b9cea8cff27d777f67803e6246296
Author: Michael Jumper <mjumper@apache.org>
Date: Fri Sep 25 18:08:50 2026 -0700
GUACAMOLE-2118: Ensure socket I/O cannot block indefinitely.
diff --git a/src/libguac/guacamole/socket-fntypes.h b/src/libguac/guacamole/socket-fntypes.h
index daf96d0a..de5d95b4 100644
--- a/src/libguac/guacamole/socket-fntypes.h
+++ b/src/libguac/guacamole/socket-fntypes.h
@@ -85,7 +85,9 @@ typedef ssize_t guac_socket_flush_handler(guac_socket* socket);
/**
* When set within a guac_socket, a handler of this type will be called
* whenever exclusive access to the guac_socket is required, such as when
- * guac_socket_instruction_begin() is called.
+ * guac_socket_instruction_begin() is called. Locking the socket with a handler
+ * of this type does not prevent the socket's shutdown handler from being
+ * called.
*
* @param socket
* The guac_socket to which exclusive access is required.
@@ -112,5 +114,16 @@ typedef void guac_socket_unlock_handler(guac_socket* socket);
*/
typedef int guac_socket_free_handler(guac_socket* socket);
+/**
+ * Generic handler for the cancellation of all pending and future operations
+ * on a socket, modeled after the standard POSIX shutdown() function. When set
+ * within a guac_socket, a handler of this type will be called when the socket
+ * is shut down using guac_socket_shutdown(), and may be called more than once.
+ *
+ * @param socket
+ * The guac_socket whose operations should be canceled.
+ */
+typedef void guac_socket_shutdown_handler(guac_socket* socket);
+
#endif
diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h
index 6075f39b..774a8a98 100644
--- a/src/libguac/guacamole/socket.h
+++ b/src/libguac/guacamole/socket.h
@@ -118,6 +118,12 @@ struct guac_socket {
*/
pthread_t __keep_alive_thread;
+ /**
+ * Handler which will be called whenever guac_socket_shutdown() is invoked
+ * on this socket.
+ */
+ guac_socket_shutdown_handler* shutdown_handler;
+
};
/**
@@ -129,6 +135,19 @@ struct guac_socket {
*/
guac_socket* guac_socket_alloc();
+/**
+ * Cancels all pending, active, and future operations on the given guac_socket.
+ * All such operations will cease blocking and fail immediately. The socket
+ * itself remains allocated and must still be freed with guac_socket_free().
+ *
+ * This function may be safely called more than once.
+ *
+ * @param socket
+ * The guac_socket whose pending, active, and future operations should be
+ * canceled.
+ */
+void guac_socket_shutdown(guac_socket* socket);
+
/**
* Frees the given guac_socket and all associated resources.
*
diff --git a/src/libguac/recording.c b/src/libguac/recording.c
index 2cfcdf39..69294b2a 100644
--- a/src/libguac/recording.c
+++ b/src/libguac/recording.c
@@ -69,9 +69,26 @@ guac_recording* guac_recording_create(guac_client* client,
return NULL;
}
+ /* Wrap recording file in socket */
+ guac_socket* socket = guac_socket_open(fd);
+ if (socket == NULL) {
+ guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording "
+ "failed: %s: %s", guac_error_message,
+ guac_status_string(guac_error));
+ close(fd);
+ return NULL;
+ }
+
/* Create recording structure with reference to underlying socket */
guac_recording* recording = guac_mem_alloc(sizeof(guac_recording));
- recording->socket = guac_socket_open(fd);
+ if (recording == NULL) {
+ guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording "
+ "failed: %s", guac_status_string(guac_error));
+ guac_socket_free(socket);
+ return NULL;
+ }
+
+ recording->socket = socket;
recording->include_output = include_output;
recording->include_mouse = include_mouse;
recording->include_touch = include_touch;
@@ -79,8 +96,20 @@ guac_recording* guac_recording_create(guac_client* client,
/* Replace client socket with wrapped recording socket only if including
* output within the recording */
- if (include_output)
- client->socket = guac_socket_tee(client->socket, recording->socket);
+ if (include_output) {
+
+ guac_socket* client_socket = guac_socket_tee(client->socket, recording->socket);
+ if (client_socket == NULL) {
+ guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording "
+ "failed: %s", guac_status_string(guac_error));
+ guac_socket_free(recording->socket);
+ guac_mem_free(recording);
+ return NULL;
+ }
+
+ client->socket = client_socket;
+
+ }
/* Recording creation succeeded */
guac_client_log(client, GUAC_LOG_INFO, "Recording of session will be "
diff --git a/src/libguac/socket-broadcast.c b/src/libguac/socket-broadcast.c
index b87a6717..fe9d98e5 100644
--- a/src/libguac/socket-broadcast.c
+++ b/src/libguac/socket-broadcast.c
@@ -313,6 +313,37 @@ static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
}
+/**
+ * Callback which is invoked by the broadcast handler to cancel all pending
+ * and future operations on the given user's socket.
+ *
+ * @param user
+ * The user whose socket should be shut down.
+ *
+ * @param data
+ * Arbitrary data passed to the broadcast handler. This is not needed
+ * by this callback, and should be left as NULL.
+ *
+ * @return
+ * Always NULL.
+ */
+static void* __shutdown_callback(guac_user* user, void* data) {
+ guac_socket_shutdown(user->socket);
+ return NULL;
+}
+
+/**
+ * Shutdown handler which cancels all pending and future operations on the
+ * sockets of all broadcasted users.
+ *
+ * @param socket
+ * The broadcast socket to shut down.
+ */
+static void __guac_socket_broadcast_shutdown_handler(guac_socket* socket) {
+ guac_socket_broadcast_data* data = (guac_socket_broadcast_data*) socket->data;
+ data->broadcast_handler(data->client, __shutdown_callback, NULL);
+}
+
/**
* Callback which handles select operations on the broadcast socket, waiting
* for data to become available such that the next read operation will not
@@ -402,13 +433,14 @@ static guac_socket* __guac_socket_init(
pthread_mutex_init(&(data->socket_lock), &lock_attributes);
/* Set read/write handlers */
- socket->read_handler = __guac_socket_broadcast_read_handler;
- socket->write_handler = __guac_socket_broadcast_write_handler;
- socket->select_handler = __guac_socket_broadcast_select_handler;
- socket->flush_handler = __guac_socket_broadcast_flush_handler;
- socket->lock_handler = __guac_socket_broadcast_lock_handler;
- socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
- socket->free_handler = __guac_socket_broadcast_free_handler;
+ socket->read_handler = __guac_socket_broadcast_read_handler;
+ socket->write_handler = __guac_socket_broadcast_write_handler;
+ socket->select_handler = __guac_socket_broadcast_select_handler;
+ socket->flush_handler = __guac_socket_broadcast_flush_handler;
+ socket->lock_handler = __guac_socket_broadcast_lock_handler;
+ socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
+ socket->free_handler = __guac_socket_broadcast_free_handler;
+ socket->shutdown_handler = __guac_socket_broadcast_shutdown_handler;
return socket;
diff --git a/src/libguac/socket-fd.c b/src/libguac/socket-fd.c
index e8d8707c..c2517ee1 100644
--- a/src/libguac/socket-fd.c
+++ b/src/libguac/socket-fd.c
@@ -34,6 +34,8 @@
#ifdef ENABLE_WINSOCK
#include <winsock2.h>
+#else
+#include <sys/socket.h>
#endif
/**
@@ -51,6 +53,13 @@ typedef struct guac_socket_fd_data {
*/
int written;
+ /**
+ * Whether a write has failed, thus invalidating further use of the socket.
+ * If a failed write has occurred, this value is set to the value of errno
+ * associated with that failure.
+ */
+ int failed_errno;
+
/**
* The main write buffer. Bytes written go here before being flushed
* to the open file descriptor.
@@ -74,7 +83,8 @@ typedef struct guac_socket_fd_data {
/**
* Writes the entire contents of the given buffer to the file descriptor
* associated with the given socket, retrying as necessary until the whole
- * buffer is written, and aborting if an error occurs.
+ * buffer is written, and aborting if an error occurs. If an error occurs, errno
+ * and the failed_errno member of guac_socket_fd_data are set appropriately.
*
* @param socket
* The guac_socket associated with the file descriptor to which the given
@@ -111,6 +121,11 @@ ssize_t guac_socket_fd_write(guac_socket* socket,
/* Record errors in guac_error */
if (retval < 0) {
+#ifdef ENABLE_WINSOCK
+ data->failed_errno = errno = WSAGetLastError();
+#else
+ data->failed_errno = errno;
+#endif
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error writing data to socket";
return retval;
@@ -182,14 +197,28 @@ static ssize_t guac_socket_fd_flush(guac_socket* socket) {
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
+ /* Drop any further writes to failed socket (instruction stream is no longer
+ * valid) */
+ if (data->failed_errno) {
+ errno = data->failed_errno;
+ guac_error = GUAC_STATUS_SEE_ERRNO;
+ guac_error_message = "Error writing data to socket";
+ return 1;
+ }
+
/* Flush remaining bytes in buffer */
if (data->written > 0) {
/* Write ALL bytes in buffer immediately */
- if (guac_socket_fd_write(socket, data->out_buf, data->written))
- return 1;
+ int flush_failed = guac_socket_fd_write(socket, data->out_buf, data->written);
+ /* All outstanding content in the buffer is either fully written or no
+ * longer valid (failed/partial write) */
data->written = 0;
+
+ if (flush_failed)
+ return 1;
+
}
return 0;
@@ -250,6 +279,14 @@ static ssize_t guac_socket_fd_write_buffered(guac_socket* socket,
const char* current = buf;
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
+ /* Refuse further writes/buffering if the stream is already broken */
+ if (data->failed_errno) {
+ errno = data->failed_errno;
+ guac_error = GUAC_STATUS_SEE_ERRNO;
+ guac_error_message = "Error writing data to socket";
+ return -1;
+ }
+
/* Append to buffer, flush if necessary */
while (count > 0) {
@@ -361,6 +398,25 @@ static int guac_socket_fd_select_handler(guac_socket* socket,
}
+/**
+ * Cancels all pending and future operations on the given socket by shutting
+ * down the underlying file descriptor.
+ *
+ * @param socket
+ * The guac_socket whose operations should be canceled.
+ */
+static void guac_socket_fd_shutdown_handler(guac_socket* socket) {
+
+ guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
+
+#ifdef ENABLE_WINSOCK
+ shutdown(data->fd, SD_BOTH);
+#else
+ shutdown(data->fd, SHUT_RDWR);
+#endif
+
+}
+
/**
* Frees all implementation-specific data associated with the given socket, but
* not the socket object itself.
@@ -424,11 +480,21 @@ guac_socket* guac_socket_open(int fd) {
/* Allocate socket and associated data */
guac_socket* socket = guac_socket_alloc();
+ if (socket == NULL)
+ return NULL;
+
guac_socket_fd_data* data = guac_mem_alloc(sizeof(guac_socket_fd_data));
+ if (data == NULL) {
+ guac_mem_free(socket);
+ guac_error = GUAC_STATUS_NO_MEMORY;
+ guac_error_message = "Could not allocate memory for socket data";
+ return NULL;
+ }
/* Store file descriptor as socket data */
data->fd = fd;
data->written = 0;
+ data->failed_errno = 0;
socket->data = data;
pthread_mutexattr_init(&lock_attributes);
@@ -439,13 +505,14 @@ guac_socket* guac_socket_open(int fd) {
pthread_mutex_init(&(data->buffer_lock), &lock_attributes);
/* Set read/write handlers */
- socket->read_handler = guac_socket_fd_read_handler;
- socket->write_handler = guac_socket_fd_write_handler;
- socket->select_handler = guac_socket_fd_select_handler;
- socket->lock_handler = guac_socket_fd_lock_handler;
- socket->unlock_handler = guac_socket_fd_unlock_handler;
- socket->flush_handler = guac_socket_fd_flush_handler;
- socket->free_handler = guac_socket_fd_free_handler;
+ socket->read_handler = guac_socket_fd_read_handler;
+ socket->write_handler = guac_socket_fd_write_handler;
+ socket->select_handler = guac_socket_fd_select_handler;
+ socket->lock_handler = guac_socket_fd_lock_handler;
+ socket->unlock_handler = guac_socket_fd_unlock_handler;
+ socket->flush_handler = guac_socket_fd_flush_handler;
+ socket->free_handler = guac_socket_fd_free_handler;
+ socket->shutdown_handler = guac_socket_fd_shutdown_handler;
return socket;
diff --git a/src/libguac/socket-ssl.c b/src/libguac/socket-ssl.c
index 1e295f1e..c0ece9e5 100644
--- a/src/libguac/socket-ssl.c
+++ b/src/libguac/socket-ssl.c
@@ -25,6 +25,11 @@
#include <pthread.h>
#include <stdlib.h>
+#ifdef ENABLE_WINSOCK
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
#include <openssl/ssl.h>
@@ -56,16 +61,59 @@ typedef struct guac_socket_ssl_data {
*/
pthread_mutex_t socket_lock;
+ /**
+ * Lock which serializes all use of the SSL connection. OpenSSL does not
+ * allow a connection to be read and written at the same time.
+ */
+ pthread_mutex_t ssl_lock;
+
} guac_socket_ssl_data;
+/**
+ * Returns whether data already decrypted by OpenSSL awaits a read from the
+ * given SSL socket.
+ *
+ * @param socket
+ * The guac_socket to test.
+ *
+ * @return
+ * Non-zero if decrypted data awaits a read, zero otherwise.
+ */
+static int __guac_socket_ssl_pending(guac_socket* socket) {
+
+ guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
+
+ pthread_mutex_lock(&(data->ssl_lock));
+ int pending = SSL_pending(data->ssl);
+ pthread_mutex_unlock(&(data->ssl_lock));
+
+ return pending > 0;
+
+}
+
static ssize_t __guac_socket_ssl_read_handler(guac_socket* socket,
void* buf, size_t count) {
- /* Read from socket */
guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
int retval;
+ /* Wait for input before locking the SSL connection (SSL_read() must not
+ * block while we hold ssl_lock or writes will be blocked, as well) */
+ if (!__guac_socket_ssl_pending(socket)) {
+ char peeked;
+ int received;
+ GUAC_RETRY_EINTR(received, recv(data->fd, &peeked, 1, MSG_PEEK));
+ if (received < 0) {
+ guac_error = GUAC_STATUS_SEE_ERRNO;
+ guac_error_message = "Error reading data from secure socket";
+ return -1;
+ }
+ }
+
+ /* Read from socket */
+ pthread_mutex_lock(&(data->ssl_lock));
retval = SSL_read(data->ssl, buf, count);
+ pthread_mutex_unlock(&(data->ssl_lock));
/* Record errors in guac_error */
if (retval <= 0) {
@@ -84,7 +132,9 @@ static ssize_t __guac_socket_ssl_write_handler(guac_socket* socket,
guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
int retval;
+ pthread_mutex_lock(&(data->ssl_lock));
retval = SSL_write(data->ssl, buf, count);
+ pthread_mutex_unlock(&(data->ssl_lock));
/* Record errors in guac_error */
if (retval <= 0) {
@@ -99,6 +149,12 @@ static ssize_t __guac_socket_ssl_write_handler(guac_socket* socket,
static int __guac_socket_ssl_select_handler(guac_socket* socket, int usec_timeout) {
guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
+
+ /* Data already decrypted by OpenSSL is not visible to a wait on the
+ * descriptor */
+ if (__guac_socket_ssl_pending(socket))
+ return 1;
+
int retval = guac_wait_for_fd(data->fd, usec_timeout);
/* Properly set guac_error */
@@ -116,6 +172,18 @@ static int __guac_socket_ssl_select_handler(guac_socket* socket, int usec_timeou
}
+static void __guac_socket_ssl_shutdown_handler(guac_socket* socket) {
+
+ guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
+
+#ifdef ENABLE_WINSOCK
+ shutdown(data->fd, SD_BOTH);
+#else
+ shutdown(data->fd, SHUT_RDWR);
+#endif
+
+}
+
static int __guac_socket_ssl_free_handler(guac_socket* socket) {
/* Shutdown SSL */
@@ -127,6 +195,7 @@ static int __guac_socket_ssl_free_handler(guac_socket* socket) {
close(data->fd);
pthread_mutex_destroy(&(data->socket_lock));
+ pthread_mutex_destroy(&(data->ssl_lock));
guac_mem_free(data);
return 0;
@@ -194,18 +263,20 @@ guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd) {
pthread_mutexattr_init(&lock_attributes);
pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&(data->socket_lock), &lock_attributes);
+ pthread_mutex_init(&(data->ssl_lock), &lock_attributes);
/* Store file descriptor as socket data */
data->fd = fd;
socket->data = data;
/* Set read/write handlers */
- socket->read_handler = __guac_socket_ssl_read_handler;
- socket->write_handler = __guac_socket_ssl_write_handler;
- socket->select_handler = __guac_socket_ssl_select_handler;
- socket->free_handler = __guac_socket_ssl_free_handler;
- socket->lock_handler = __guac_socket_ssl_lock_handler;
- socket->unlock_handler = __guac_socket_ssl_unlock_handler;
+ socket->read_handler = __guac_socket_ssl_read_handler;
+ socket->write_handler = __guac_socket_ssl_write_handler;
+ socket->select_handler = __guac_socket_ssl_select_handler;
+ socket->free_handler = __guac_socket_ssl_free_handler;
+ socket->shutdown_handler = __guac_socket_ssl_shutdown_handler;
+ socket->lock_handler = __guac_socket_ssl_lock_handler;
+ socket->unlock_handler = __guac_socket_ssl_unlock_handler;
return socket;
diff --git a/src/libguac/socket-tee.c b/src/libguac/socket-tee.c
index c6450b70..473b79ed 100644
--- a/src/libguac/socket-tee.c
+++ b/src/libguac/socket-tee.c
@@ -124,8 +124,8 @@ static ssize_t __guac_socket_tee_flush_handler(guac_socket* socket) {
}
/**
- * Callback function which delegates the lock operation to the primary
- * socket alone.
+ * Callback function which delegates the lock operation to both underlying
+ * sockets.
*
* @param socket
* The tee socket on which guac_socket_instruction_begin() was invoked.
@@ -141,8 +141,8 @@ static void __guac_socket_tee_lock_handler(guac_socket* socket) {
}
/**
- * Callback function which delegates the unlock operation to the primary
- * socket alone.
+ * Callback function which delegates the unlock operation to both underlying
+ * sockets.
*
* @param socket
* The tee socket on which guac_socket_instruction_end() was invoked.
@@ -182,6 +182,22 @@ static int __guac_socket_tee_select_handler(guac_socket* socket,
}
+/**
+ * Callback function which delegates the shutdown operation to both underlying
+ * sockets.
+ *
+ * @param socket
+ * The tee socket whose underlying sockets should be shut down.
+ */
+static void __guac_socket_tee_shutdown_handler(guac_socket* socket) {
+
+ guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;
+
+ guac_socket_shutdown(data->primary);
+ guac_socket_shutdown(data->secondary);
+
+}
+
/**
* Callback function which frees all underlying data associated with the
* given tee socket, including both primary and secondary sockets.
@@ -218,13 +234,14 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary) {
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;
+ 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;
+ socket->shutdown_handler = __guac_socket_tee_shutdown_handler;
return socket;
diff --git a/src/libguac/socket-wsa.c b/src/libguac/socket-wsa.c
index ec17d827..3bccda31 100644
--- a/src/libguac/socket-wsa.c
+++ b/src/libguac/socket-wsa.c
@@ -21,6 +21,7 @@
#include "guacamole/error.h"
#include "guacamole/socket.h"
+#include <errno.h>
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
@@ -45,6 +46,13 @@ typedef struct guac_socket_wsa_data {
*/
int written;
+ /**
+ * Whether a write has failed, thus invalidating further use of the
+ * socket. If a failed write has occurred, this value is set to the error
+ * code reported by WSAGetLastError() for that failure.
+ */
+ int failed_wsa_error;
+
/**
* The main write buffer. Bytes written go here before being flushed
* to the open socket.
@@ -68,7 +76,9 @@ typedef struct guac_socket_wsa_data {
/**
* Writes the entire contents of the given buffer to the SOCKET handle
* associated with the given socket, retrying as necessary until the whole
- * buffer is written, and aborting if an error occurs.
+ * buffer is written, and aborting if an error occurs. If an error occurs, errno
+ * and the failed_wsa_error member of guac_socket_wsa_data are set to the error
+ * code reported by WSAGetLastError().
*
* @param socket
* The guac_socket associated with the SOCKET handle to which the given
@@ -99,6 +109,7 @@ ssize_t guac_socket_wsa_write(guac_socket* socket,
/* Record errors in guac_error */
if (retval < 0) {
+ data->failed_wsa_error = errno = WSAGetLastError();
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error writing data to socket";
return retval;
@@ -165,14 +176,29 @@ static ssize_t guac_socket_wsa_flush(guac_socket* socket) {
guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data;
+ /* Drop any further writes to failed socket (instruction stream is no longer
+ * valid) */
+ if (data->failed_wsa_error) {
+ WSASetLastError(data->failed_wsa_error);
+ errno = data->failed_wsa_error;
+ guac_error = GUAC_STATUS_SEE_ERRNO;
+ guac_error_message = "Error writing data to socket";
+ return 1;
+ }
+
/* Flush remaining bytes in buffer */
if (data->written > 0) {
/* Write ALL bytes in buffer immediately */
- if (guac_socket_wsa_write(socket, data->out_buf, data->written))
- return 1;
+ int flush_failed = guac_socket_wsa_write(socket, data->out_buf, data->written);
+ /* All outstanding content in the buffer is either fully written or no
+ * longer valid (failed/partial write) */
data->written = 0;
+
+ if (flush_failed)
+ return 1;
+
}
return 0;
@@ -233,6 +259,15 @@ static ssize_t guac_socket_wsa_write_buffered(guac_socket* socket,
const char* current = buf;
guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data;
+ /* Refuse further writes/buffering if the stream is already broken */
+ if (data->failed_wsa_error) {
+ WSASetLastError(data->failed_wsa_error);
+ errno = data->failed_wsa_error;
+ guac_error = GUAC_STATUS_SEE_ERRNO;
+ guac_error_message = "Error writing data to socket";
+ return -1;
+ }
+
/* Append to buffer, flush if necessary */
while (count > 0) {
@@ -368,6 +403,18 @@ static int guac_socket_wsa_select_handler(guac_socket* socket,
}
+/**
+ * Cancels all pending and future operations on the given socket by shutting
+ * down the underlying Windows socket in both directions.
+ *
+ * @param socket
+ * The guac_socket whose operations should be canceled.
+ */
+static void guac_socket_wsa_shutdown_handler(guac_socket* socket) {
+ guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data;
+ shutdown(data->sock, SD_BOTH);
+}
+
/**
* Frees all implementation-specific data associated with the given socket, but
* not the socket object itself.
@@ -436,6 +483,7 @@ guac_socket* guac_socket_open_wsa(SOCKET sock) {
/* Store socket as socket data */
data->sock = sock;
data->written = 0;
+ data->failed_wsa_error = 0;
socket->data = data;
pthread_mutexattr_init(&lock_attributes);
@@ -446,13 +494,14 @@ guac_socket* guac_socket_open_wsa(SOCKET sock) {
pthread_mutex_init(&(data->buffer_lock), &lock_attributes);
/* Set read/write handlers */
- socket->read_handler = guac_socket_wsa_read_handler;
- socket->write_handler = guac_socket_wsa_write_handler;
- socket->select_handler = guac_socket_wsa_select_handler;
- socket->lock_handler = guac_socket_wsa_lock_handler;
- socket->unlock_handler = guac_socket_wsa_unlock_handler;
- socket->flush_handler = guac_socket_wsa_flush_handler;
- socket->free_handler = guac_socket_wsa_free_handler;
+ socket->read_handler = guac_socket_wsa_read_handler;
+ socket->write_handler = guac_socket_wsa_write_handler;
+ socket->select_handler = guac_socket_wsa_select_handler;
+ socket->lock_handler = guac_socket_wsa_lock_handler;
+ socket->unlock_handler = guac_socket_wsa_unlock_handler;
+ socket->flush_handler = guac_socket_wsa_flush_handler;
+ socket->free_handler = guac_socket_wsa_free_handler;
+ socket->shutdown_handler = guac_socket_wsa_shutdown_handler;
return socket;
diff --git a/src/libguac/socket.c b/src/libguac/socket.c
index 1d51168a..d908721e 100644
--- a/src/libguac/socket.c
+++ b/src/libguac/socket.c
@@ -166,13 +166,14 @@ guac_socket* guac_socket_alloc() {
socket->__keep_alive_enabled = 0;
/* No handlers yet */
- socket->read_handler = NULL;
- socket->write_handler = NULL;
- socket->select_handler = NULL;
- socket->free_handler = NULL;
- socket->flush_handler = NULL;
- socket->lock_handler = NULL;
- socket->unlock_handler = NULL;
+ socket->read_handler = NULL;
+ socket->write_handler = NULL;
+ socket->select_handler = NULL;
+ socket->free_handler = NULL;
+ socket->shutdown_handler = NULL;
+ socket->flush_handler = NULL;
+ socket->lock_handler = NULL;
+ socket->unlock_handler = NULL;
return socket;
@@ -203,13 +204,15 @@ void guac_socket_instruction_end(guac_socket* socket) {
}
-void guac_socket_free(guac_socket* socket) {
+void guac_socket_shutdown(guac_socket* socket) {
- guac_socket_flush(socket);
+ /* Call shutdown handler if defined */
+ if (socket->shutdown_handler)
+ socket->shutdown_handler(socket);
- /* Call free handler if defined */
- if (socket->free_handler)
- socket->free_handler(socket);
+}
+
+void guac_socket_free(guac_socket* socket) {
/* Mark as closed */
socket->state = GUAC_SOCKET_CLOSED;
@@ -220,6 +223,12 @@ void guac_socket_free(guac_socket* socket) {
pthread_join(socket->__keep_alive_thread, NULL);
}
+ guac_socket_flush(socket);
+
+ /* Call free handler if defined */
+ if (socket->free_handler)
+ socket->free_handler(socket);
+
guac_mem_free(socket);
}