diff --git a/libguac/include/error.h b/libguac/include/error.h index af62dcfb..85eabf3c 100644 --- a/libguac/include/error.h +++ b/libguac/include/error.h @@ -116,4 +116,17 @@ const char* guac_status_string(guac_status status); guac_status* __guac_error(); +/** + * Returns a message describing the error which occurred during the last + * function call. If an error occurred, but no message is associated with it, + * NULL is returned. This value is undefined if no error occurred. + * + * The storage of this value is thread-local. Assignment of a message to + * guac_error_message in one thread will not affect its value in another + * thread. + */ +#define guac_error_message (*__guac_error_message()) + +const char** __guac_error_message(); + #endif diff --git a/libguac/src/client.c b/libguac/src/client.c index 22bb24c5..2607d12a 100644 --- a/libguac/src/client.c +++ b/libguac/src/client.c @@ -135,6 +135,7 @@ guac_client_plugin* guac_client_plugin_open(const char* protocol) { client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY); if (!client_plugin_handle) { guac_error = GUAC_STATUS_BAD_ARGUMENT; + guac_error_message = "Client plugin not found"; return NULL; } @@ -146,6 +147,7 @@ guac_client_plugin* guac_client_plugin_open(const char* protocol) { /* Fail if cannot find guac_client_init */ if (dlerror() != NULL) { guac_error = GUAC_STATUS_BAD_ARGUMENT; + guac_error_message = dlerror(); return NULL; } @@ -155,6 +157,7 @@ guac_client_plugin* guac_client_plugin_open(const char* protocol) { /* Fail if cannot find GUAC_CLIENT_ARGS */ if (dlerror() != NULL) { guac_error = GUAC_STATUS_BAD_ARGUMENT; + guac_error_message = dlerror(); return NULL; } @@ -162,6 +165,7 @@ guac_client_plugin* guac_client_plugin_open(const char* protocol) { plugin = malloc(sizeof(guac_client_plugin)); if (plugin == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for client plugin"; return NULL; } @@ -178,6 +182,7 @@ int guac_client_plugin_close(guac_client_plugin* plugin) { /* Unload client plugin */ if (dlclose(plugin->__client_plugin_handle)) { guac_error = GUAC_STATUS_BAD_STATE; + guac_error_message = dlerror(); return -1; } @@ -192,6 +197,7 @@ guac_client* guac_client_plugin_get_client(guac_client_plugin* plugin, guac_client* client = malloc(sizeof(guac_client)); if (client == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for client"; return NULL; } diff --git a/libguac/src/error.c b/libguac/src/error.c index 67ef367c..869259fe 100644 --- a/libguac/src/error.c +++ b/libguac/src/error.c @@ -107,17 +107,27 @@ const char* guac_status_string(guac_status status) { static pthread_key_t __guac_error_key; static pthread_once_t __guac_error_key_init = PTHREAD_ONCE_INIT; -static void __guac_free_error(void* status) { +static pthread_key_t __guac_error_message_key; +static pthread_once_t __guac_error_message_key_init = PTHREAD_ONCE_INIT; + +static void __guac_free_pointer(void* pointer) { /* Free memory allocated to status variable */ - free(status); + free(pointer); } static void __guac_alloc_error_key() { /* Create key, destroy any allocated variable on thread exit */ - pthread_key_create(&__guac_error_key, __guac_free_error); + pthread_key_create(&__guac_error_key, __guac_free_pointer); + +} + +static void __guac_alloc_error_message_key() { + + /* Create key, destroy any allocated variable on thread exit */ + pthread_key_create(&__guac_error_message_key, __guac_free_pointer); } @@ -142,15 +152,44 @@ guac_status* __guac_error() { } +const char** __guac_error_message() { + + /* Pointer for thread-local data */ + const char** message; + + /* Init error message key, if not already initialized */ + pthread_once( + &__guac_error_message_key_init, + __guac_alloc_error_message_key + ); + + /* Retrieve thread-local message variable */ + message = (const char**) pthread_getspecific(__guac_error_message_key); + + /* Allocate thread-local message variable if not already allocated */ + if (message == NULL) { + message = malloc(sizeof(const char*)); + pthread_setspecific(__guac_error_message_key, message); + } + + return message; + +} + #else /* Default (not-threadsafe) implementation */ static guac_status __guac_error_unsafe_storage; +static const char** __guac_error_message_unsafe_storage; guac_status* __guac_error() { return &__guac_error_unsafe_storage; } +const char** __guac_error_message() { + return &__guac_error_message_unsafe_storage; +} + /* Warn about threadsafety */ #warn No threadsafe implementation of __guac_error exists for your platform, so a default non-threadsafe implementation has been used instead. This may lead to incorrect status codes being reported for failures. Please consider adding support for your platform, or filing a bug report with the Guacamole project. diff --git a/libguac/src/protocol.c b/libguac/src/protocol.c index 1f9c7345..0ff61bbc 100644 --- a/libguac/src/protocol.c +++ b/libguac/src/protocol.c @@ -414,6 +414,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t parsed_instruction = malloc(sizeof(guac_instruction)); if (parsed_instruction == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for parsed instruction"; return NULL; } @@ -424,6 +425,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t /* Fail if memory could not be alloc'd for argv */ if (parsed_instruction->argv == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for arguments of parsed instruction"; free(parsed_instruction); return NULL; } @@ -434,6 +436,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t /* Fail if memory could not be alloc'd for opcode */ if (parsed_instruction->opcode == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for opcode of parsed instruction"; free(parsed_instruction->argv); free(parsed_instruction); return NULL; @@ -447,6 +450,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t /* Free memory and fail if out of mem */ if (parsed_instruction->argv[j] == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for single argument of parsed instruction"; /* Free all alloc'd argv values */ while (--j >= 0) @@ -474,6 +478,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t /* Error if expected comma is not present */ else if (terminator != ',') { guac_error = GUAC_STATUS_BAD_ARGUMENT; + guac_error_message = "Element terminator of instructioni was not ';' nor ','"; return NULL; } @@ -488,6 +493,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t /* Error if length is non-numeric or does not end in a period */ else { guac_error = GUAC_STATUS_BAD_ARGUMENT; + guac_error_message = "Non-numeric character in element length"; return NULL; } @@ -508,6 +514,7 @@ guac_instruction* guac_protocol_read_instruction(guac_socket* socket, int usec_t /* EOF */ if (retval == 0) { guac_error = GUAC_STATUS_NO_INPUT; + guac_error_message = "End of stream reached while reading instruction"; return NULL; } @@ -532,6 +539,7 @@ guac_instruction* guac_protocol_expect_instruction(guac_socket* socket, int usec /* Validate instruction */ if (strcmp(instruction->opcode, opcode) != 0) { guac_error = GUAC_STATUS_BAD_STATE; + guac_error_message = "Instruction read did not have expected opcode"; guac_instruction_free(instruction); return NULL; } diff --git a/libguac/src/socket.c b/libguac/src/socket.c index edda22ec..5b1a3f00 100644 --- a/libguac/src/socket.c +++ b/libguac/src/socket.c @@ -69,6 +69,7 @@ guac_socket* guac_socket_open(int fd) { /* If no memory available, return with error */ if (socket == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for socket"; return NULL; } @@ -83,6 +84,7 @@ guac_socket* guac_socket_open(int fd) { /* If no memory available, return with error */ if (socket->__instructionbuf == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for instruction buffer"; free(socket); return NULL; } @@ -116,8 +118,10 @@ ssize_t __guac_socket_write(guac_socket* socket, const char* buf, int count) { #endif /* Record errors in guac_error */ - if (retval < 0) + if (retval < 0) { guac_error = GUAC_STATUS_SEE_ERRNO; + guac_error_message = "Error writing data to socket"; + } return retval; } @@ -299,8 +303,15 @@ int guac_socket_select(guac_socket* socket, int usec_timeout) { } /* Properly set guac_error */ - if (retval < 0) guac_error = GUAC_STATUS_SEE_ERRNO; - if (retval == 0) guac_error = GUAC_STATUS_INPUT_TIMEOUT; + if (retval < 0) { + guac_error = GUAC_STATUS_SEE_ERRNO; + guac_error_message = "Error while waiting for data on socket"; + } + + if (retval == 0) { + guac_error = GUAC_STATUS_INPUT_TIMEOUT; + guac_error_message = "Timeout while waiting for data on socket"; + } return retval;