Merge 1.5.4 changes back to master.

This commit is contained in:
Mike Jumper 2023-08-29 10:51:51 -07:00
commit eae24284c5
46 changed files with 1652 additions and 211 deletions

View File

@ -123,6 +123,14 @@ typedef struct guac_common_cursor {
*/
guac_timestamp timestamp;
/**
* Lock which restricts simultaneous access to the cursor, guaranteeing
* ordered modifications to the cursor and that incompatible operations
* do not occur simultaneously. This lock is for internal use within the
* cursor only.
*/
pthread_mutex_t _lock;
} guac_common_cursor;
/**
@ -153,14 +161,14 @@ void guac_common_cursor_free(guac_common_cursor* cursor);
* @param cursor
* The cursor to send.
*
* @param user
* @param client
* The user receiving the updated cursor.
*
* @param socket
* The socket over which the updated cursor should be sent.
*/
void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
guac_socket* socket);
void guac_common_cursor_dup(
guac_common_cursor* cursor, guac_client* client, guac_socket* socket);
/**
* Updates the current position and button state of the mouse cursor, marking

View File

@ -151,13 +151,14 @@ void guac_common_display_free(guac_common_display* display);
* @param display
* The display whose state should be sent along the given socket.
*
* @param user
* The user receiving the display state.
* @param client
* The client associated with the users receiving the display state.
*
* @param socket
* The socket over which the display state should be sent.
*/
void guac_common_display_dup(guac_common_display* display, guac_user* user,
void guac_common_display_dup(
guac_common_display* display, guac_client* client,
guac_socket* socket);
/**

View File

@ -75,12 +75,26 @@ typedef struct guac_common_list {
*/
guac_common_list* guac_common_list_alloc();
/**
* A handler that will be invoked with the data pointer of each element of
* the list when guac_common_list_free() is invoked.
*
* @param data
* The arbitrary data pointed to by the list element.
*/
typedef void guac_common_list_element_free_handler(void* data);
/**
* Frees the given list.
*
* @param list The list to free.
*
* @param free_element_handler
* A handler that will be invoked with each arbitrary data pointer in the
* list, if not NULL.
*/
void guac_common_list_free(guac_common_list* list);
void guac_common_list_free(guac_common_list* list,
guac_common_list_element_free_handler* free_element_handler);
/**
* Adds the given data to the list as a new element, returning the created

View File

@ -490,14 +490,14 @@ void guac_common_surface_flush(guac_common_surface* surface);
* @param surface
* The surface to duplicate.
*
* @param user
* The user receiving the surface.
* @param client
* The client whos users are receiving the surface.
*
* @param socket
* The socket over which the surface contents should be sent.
*/
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
guac_socket* socket);
void guac_common_surface_dup(guac_common_surface* surface,
guac_client* client, guac_socket* socket);
/**
* Declares that the given surface should receive touch events. By default,

View File

@ -31,6 +31,7 @@
#include <guacamole/timestamp.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
@ -74,12 +75,16 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
cursor->x = 0;
cursor->y = 0;
pthread_mutex_init(&(cursor->_lock), NULL);
return cursor;
}
void guac_common_cursor_free(guac_common_cursor* cursor) {
pthread_mutex_destroy(&(cursor->_lock));
guac_client* client = cursor->client;
guac_layer* buffer = cursor->buffer;
cairo_surface_t* surface = cursor->surface;
@ -99,8 +104,10 @@ void guac_common_cursor_free(guac_common_cursor* cursor) {
}
void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
guac_socket* socket) {
void guac_common_cursor_dup(
guac_common_cursor* cursor, guac_client* client, guac_socket* socket) {
pthread_mutex_lock(&(cursor->_lock));
/* Synchronize location */
guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask,
@ -111,7 +118,7 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
guac_protocol_send_size(socket, cursor->buffer,
cursor->width, cursor->height);
guac_user_stream_png(user, socket, GUAC_COMP_SRC,
guac_client_stream_png(client, socket, GUAC_COMP_SRC,
cursor->buffer, 0, 0, cursor->surface);
guac_protocol_send_cursor(socket,
@ -119,6 +126,8 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
cursor->buffer, 0, 0, cursor->width, cursor->height);
}
pthread_mutex_unlock(&(cursor->_lock));
guac_socket_flush(socket);
}
@ -154,6 +163,8 @@ static void* guac_common_cursor_broadcast_state(guac_user* user,
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
int x, int y, int button_mask) {
pthread_mutex_lock(&(cursor->_lock));
/* Update current user of cursor */
cursor->user = user;
@ -169,6 +180,8 @@ void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
guac_client_foreach_user(cursor->client,
guac_common_cursor_broadcast_state, cursor);
pthread_mutex_unlock(&(cursor->_lock));
}
/**
@ -212,6 +225,8 @@ static void guac_common_cursor_resize(guac_common_cursor* cursor,
void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy,
unsigned const char* data, int width, int height, int stride) {
pthread_mutex_lock(&(cursor->_lock));
/* Copy image data */
guac_common_cursor_resize(cursor, width, height, stride);
memcpy(cursor->image_buffer, data, height * stride);
@ -242,6 +257,8 @@ void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy,
guac_socket_flush(cursor->client->socket);
pthread_mutex_unlock(&(cursor->_lock));
}
void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy,
@ -298,9 +315,13 @@ void guac_common_cursor_set_blank(guac_common_cursor* cursor) {
void guac_common_cursor_remove_user(guac_common_cursor* cursor,
guac_user* user) {
pthread_mutex_lock(&(cursor->_lock));
/* Disassociate from given user */
if (cursor->user == user)
cursor->user = NULL;
pthread_mutex_unlock(&(cursor->_lock));
}

View File

@ -37,20 +37,20 @@
* The head element of the linked list of layers to synchronize, which may
* be NULL if the list is currently empty.
*
* @param user
* The user receiving the layers.
* @param client
* The client associated with the users receiving the layers.
*
* @param socket
* The socket over which each layer should be sent.
*/
static void guac_common_display_dup_layers(guac_common_display_layer* layers,
guac_user* user, guac_socket* socket) {
guac_client* client, guac_socket* socket) {
guac_common_display_layer* current = layers;
/* Synchronize all surfaces in given list */
while (current != NULL) {
guac_common_surface_dup(current->surface, user, socket);
guac_common_surface_dup(current->surface, client, socket);
current = current->next;
}
@ -163,7 +163,8 @@ void guac_common_display_free(guac_common_display* display) {
}
void guac_common_display_dup(guac_common_display* display, guac_user* user,
void guac_common_display_dup(
guac_common_display* display, guac_client* client,
guac_socket* socket) {
guac_client* client = user->client;
@ -171,14 +172,14 @@ void guac_common_display_dup(guac_common_display* display, guac_user* user,
pthread_mutex_lock(&display->_lock);
/* Sunchronize shared cursor */
guac_common_cursor_dup(display->cursor, user, socket);
guac_common_cursor_dup(display->cursor, client, socket);
/* Synchronize default surface */
guac_common_surface_dup(display->default_surface, user, socket);
guac_common_surface_dup(display->default_surface, client, socket);
/* Synchronize all layers and buffers */
guac_common_display_dup_layers(display->layers, user, socket);
guac_common_display_dup_layers(display->buffers, user, socket);
guac_common_display_dup_layers(display->layers, client, socket);
guac_common_display_dup_layers(display->buffers, client, socket);
/* Sends a sync instruction to mark the boundary of the first frame */
guac_protocol_send_sync(socket, client->last_sent_timestamp, 1);

View File

@ -34,8 +34,26 @@ guac_common_list* guac_common_list_alloc() {
}
void guac_common_list_free(guac_common_list* list) {
void guac_common_list_free(
guac_common_list* list,
guac_common_list_element_free_handler* free_element_handler) {
/* Free every element of the list */
guac_common_list_element* element = list->head;
while(element != NULL) {
guac_common_list_element* next = element->next;
if (free_element_handler != NULL)
free_element_handler(element->data);
free(element);
element = next;
}
/* Free the list itself */
free(list);
}
guac_common_list_element* guac_common_list_add(guac_common_list* list,

View File

@ -1989,8 +1989,8 @@ void guac_common_surface_flush(guac_common_surface* surface) {
}
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
guac_socket* socket) {
void guac_common_surface_dup(guac_common_surface* surface,
guac_client* client, guac_socket* socket) {
pthread_mutex_lock(&surface->_lock);
@ -2028,7 +2028,7 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
surface->width, surface->height, surface->stride);
/* Send PNG for rect */
guac_user_stream_png(user, socket, GUAC_COMP_OVER, surface->layer,
guac_client_stream_png(client, socket, GUAC_COMP_OVER, surface->layer,
0, 0, rect);
cairo_surface_destroy(rect);
@ -2038,4 +2038,3 @@ complete:
pthread_mutex_unlock(&surface->_lock);
}

View File

@ -67,13 +67,14 @@
static int __write_all(int fd, char* buffer, int length) {
/* Repeatedly write() until all data is written */
while (length > 0) {
int remaining_length = length;
while (remaining_length > 0) {
int written = write(fd, buffer, length);
int written = write(fd, buffer, remaining_length);
if (written < 0)
return -1;
length -= written;
remaining_length -= written;
buffer += written;
}

View File

@ -43,6 +43,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define GUACD_DEV_NULL "/dev/null"
@ -245,6 +246,49 @@ static void guacd_openssl_free_locks(int count) {
#endif
#endif
/**
* A flag that, if non-zero, indicates that the daemon should immediately stop
* accepting new connections.
*/
int stop_everything = 0;
/**
* A signal handler that will set a flag telling the daemon to immediately stop
* accepting new connections. Note that the signal itself will cause any pending
* accept() calls to be interrupted, causing the daemon to unlock and begin
* cleaning up.
*
* @param signal
* The signal that was received. Unused in this function since only
* signals that should result in stopping the daemon should invoke this.
*/
static void signal_stop_handler(int signal) {
/* Instruct the daemon to stop accepting new connections */
stop_everything = 1;
}
/**
* A callback for guacd_proc_map_foreach which will stop every process in the
* map.
*
* @param proc
* The guacd process to stop.
*
* @param data
* Unused.
*/
static void stop_process_callback(guacd_proc* proc, void* data) {
guacd_log(GUAC_LOG_DEBUG,
"Killing connection %s (%i)\n",
proc->client->connection_id, (int) proc->pid);
guacd_proc_stop(proc);
}
int main(int argc, char* argv[]) {
/* Server */
@ -457,6 +501,12 @@ int main(int argc, char* argv[]) {
"Child processes may pile up in the process table.");
}
/* Clean up and exit if SIGINT or SIGTERM signals are caught */
struct sigaction signal_stop_action = { 0 };
signal_stop_action.sa_handler = signal_stop_handler;
sigaction(SIGINT, &signal_stop_action, NULL);
sigaction(SIGTERM, &signal_stop_action, NULL);
/* Log listening status */
guacd_log(GUAC_LOG_INFO, "Listening on host %s, port %s", bound_address, bound_port);
@ -470,7 +520,7 @@ int main(int argc, char* argv[]) {
}
/* Daemon loop */
for (;;) {
while (!stop_everything) {
pthread_t child_thread;
@ -480,7 +530,10 @@ int main(int argc, char* argv[]) {
(struct sockaddr*) &client_addr, &client_addr_len);
if (connected_socket_fd < 0) {
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno));
if (errno == EINTR)
guacd_log(GUAC_LOG_DEBUG, "Accepting of further client connection(s) interrupted by signal.");
else
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno));
continue;
}
@ -504,6 +557,26 @@ int main(int argc, char* argv[]) {
}
/* Stop all connections */
if (map != NULL) {
guacd_proc_map_foreach(map, stop_process_callback, NULL);
/*
* FIXME: Clean up the proc map. This is not as straightforward as it
* might seem, since the detached connection threads will attempt to
* remove the connection proccesses from the map when they complete,
* which will also happen upon shutdown. So there's a good chance that
* this map cleanup will happen at the same time as the thread cleanup.
* The map _does_ have locking mechanisms in place for ensuring thread
* safety, but cleaning up the map also requires destroying those locks,
* making them unusable for this case. One potential fix could be to
* join every one of the connection threads instead of detaching them,
* but that does complicate the cleanup of thread resources.
*/
}
/* Close socket */
if (close(socket_fd) < 0) {
guacd_log(GUAC_LOG_ERROR, "Could not close socket: %s", strerror(errno));

View File

@ -27,6 +27,24 @@
#include <stdlib.h>
#include <string.h>
/**
* A value to be stored in the buckets, containing the guacd proc itself,
* as well as a link to the element in the list of all guacd processes.
*/
typedef struct guacd_proc_map_entry {
/**
* The guacd process itself.
*/
guacd_proc* proc;
/**
* A pointer to the corresponding entry in the list of all processes.
*/
guac_common_list_element* element;
} guacd_proc_map_entry;
/**
* Returns a hash code based on the given connection ID.
*
@ -98,7 +116,7 @@ static guac_common_list_element* __guacd_proc_find(guac_common_list* bucket,
while (current != NULL) {
/* Check connection ID */
guacd_proc* proc = (guacd_proc*) current->data;
guacd_proc* proc = ((guacd_proc_map_entry*) current->data)->proc;
if (strcmp(proc->client->connection_id, id) == 0)
break;
@ -112,6 +130,7 @@ static guac_common_list_element* __guacd_proc_find(guac_common_list* bucket,
guacd_proc_map* guacd_proc_map_alloc() {
guacd_proc_map* map = malloc(sizeof(guacd_proc_map));
map->processes = guac_common_list_alloc();
guac_common_list** current;
int i;
@ -140,8 +159,18 @@ int guacd_proc_map_add(guacd_proc_map* map, guacd_proc* proc) {
/* If no such element, we can add the new client successfully */
if (found == NULL) {
guac_common_list_add(bucket, proc);
guacd_proc_map_entry* entry = malloc(sizeof(guacd_proc_map_entry));
guac_common_list_lock(map->processes);
entry->element = guac_common_list_add(map->processes, proc);
guac_common_list_unlock(map->processes);
entry->proc = proc;
guac_common_list_add(bucket, entry);
guac_common_list_unlock(bucket);
return 0;
}
@ -168,7 +197,7 @@ guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id) {
return NULL;
}
proc = (guacd_proc*) found->data;
proc = ((guacd_proc_map_entry*) found->data)->proc;
guac_common_list_unlock(bucket);
return proc;
@ -192,11 +221,50 @@ guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id) {
return NULL;
}
proc = (guacd_proc*) found->data;
guacd_proc_map_entry* entry = (guacd_proc_map_entry*) found->data;
/* Find and remove the key from the process list */
guac_common_list_lock(map->processes);
guac_common_list_remove(map->processes, entry->element);
guac_common_list_unlock(map->processes);
proc = entry->proc;
guac_common_list_remove(bucket, found);
free (entry);
guac_common_list_unlock(bucket);
return proc;
}
void guacd_proc_map_foreach(guacd_proc_map* map,
guacd_proc_map_foreach_callback* callback, void* data) {
guac_common_list* list = map->processes;
guac_common_list_lock(list);
/* Invoke the callback for every element in the list */
guac_common_list_element* element;
for (element = list->head; element != NULL; element = element->next)
callback((guacd_proc*) element->data, data);
guac_common_list_unlock(list);
}
void guacd_proc_map_free(guacd_proc_map* map) {
/* Free the list of all processes */
guac_common_list_free(map->processes, NULL);
/* Free each bucket */
guac_common_list** buckets = map->__buckets;
int i;
for (i = 0; i < GUACD_PROC_MAP_BUCKETS; i++) {
guac_common_list_free(*(buckets + i), free);
}
}

View File

@ -49,6 +49,12 @@ typedef struct guacd_proc_map {
*/
guac_common_list* __buckets[GUACD_PROC_MAP_BUCKETS];
/**
* All processes present in the map. For internal use only. To operate on these
* keys, use guacd_proc_map_foreach().
*/
guac_common_list* processes;
} guacd_proc_map;
/**
@ -60,6 +66,16 @@ typedef struct guacd_proc_map {
*/
guacd_proc_map* guacd_proc_map_alloc();
/**
* Free all resources allocated for the provided map. Note that this function
* will _not_ clean up the processes contained within the map, only the map
* itself.
*
* @param map
* The guacd proc map to free.
*/
void guacd_proc_map_free(guacd_proc_map* map);
/**
* Adds the given process to the client process map. On success, zero is
* returned. If adding the client fails (due to lack of space, or duplicate
@ -112,5 +128,36 @@ guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id);
*/
guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id);
/**
* A callback function that will be invoked with every guacd_proc stored
* in the provided map, when provided to guacd_proc_map_foreach(), along with
* any provided arbitrary data.
*
* @param proc
* The current guacd process.
*
* @param data
* The arbitrary data provided to guacd_proc_map_foreach().
*/
typedef void guacd_proc_map_foreach_callback(guacd_proc* proc, void* data);
/**
* Invoke the provided callback with any provided arbitrary data and each guacd
* proc contained in the provided map, once each and in no particular order.
*
* @param map
* The map from which all guacd processes should be extracted and provided
* to the callback.
*
* @param callback
* The callback function to be invoked once with each guacd process
* contained in the provided map.
*
* @param data
* Arbitrary data to be provided to the callback function.
*/
void guacd_proc_map_foreach(guacd_proc_map* map,
guacd_proc_map_foreach_callback* callback, void* data);
#endif

View File

@ -287,6 +287,26 @@ static int guacd_timed_client_free(guac_client* client, int timeout) {
return !free_operation.completed;
}
/**
* A reference to the current guacd process.
*/
guacd_proc* guacd_proc_self = NULL;
/**
* A signal handler that will be invoked when a signal is caught telling this
* guacd process to immediately exit.
*
* @param signal
* The signal that was received. Unused in this function since only
* signals that should result in stopping the proc should invoke this.
*/
static void signal_stop_handler(int signal) {
/* Stop the current guacd proc */
guacd_proc_stop(guacd_proc_self);
}
/**
* Starts protocol-specific handling on the given process by loading the client
* plugin for that protocol. This function does NOT return. It initializes the
@ -333,6 +353,14 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
/* Enable keep alive on the broadcast socket */
guac_socket_require_keep_alive(client->socket);
guacd_proc_self = proc;
/* Clean up and exit if SIGINT or SIGTERM signals are caught */
struct sigaction signal_stop_action = { 0 };
signal_stop_action.sa_handler = signal_stop_handler;
sigaction(SIGINT, &signal_stop_action, NULL);
sigaction(SIGTERM, &signal_stop_action, NULL);
/* Add each received file descriptor as a new user */
int received_fd;
while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) {
@ -458,8 +486,43 @@ guacd_proc* guacd_create_proc(const char* protocol) {
}
/**
* Kill the provided child guacd process. This function must be called by the
* parent process, and will block until all processes associated with the
* child process have terminated.
*
* @param proc
* The child guacd process to kill.
*/
static void guacd_proc_kill(guacd_proc* proc) {
/* Request orderly termination of process */
if (kill(proc->pid, SIGTERM))
guacd_log(GUAC_LOG_DEBUG, "Unable to request termination of "
"client process: %s ", strerror(errno));
/* Wait for all processes within process group to terminate */
pid_t child_pid;
while ((child_pid = waitpid(-proc->pid, NULL, 0)) > 0 || errno == EINTR) {
guacd_log(GUAC_LOG_DEBUG, "Child process %i of connection \"%s\" has terminated",
child_pid, proc->client->connection_id);
}
guacd_log(GUAC_LOG_DEBUG, "All child processes for connection \"%s\" have been terminated.",
proc->client->connection_id);
}
void guacd_proc_stop(guacd_proc* proc) {
/* A non-zero PID means that this is the parent process */
if (proc->pid != 0) {
guacd_proc_kill(proc);
return;
}
/* Otherwise, this is the child process */
/* Signal client to stop */
guac_client_stop(proc->client);
@ -473,4 +536,3 @@ void guacd_proc_stop(guacd_proc* proc) {
close(proc->fd_socket);
}

View File

@ -78,13 +78,14 @@ libguacinc_HEADERS = \
guacamole/wol.h \
guacamole/wol-constants.h
noinst_HEADERS = \
id.h \
encode-jpeg.h \
encode-png.h \
palette.h \
user-handlers.h \
raw_encoder.h \
noinst_HEADERS = \
id.h \
encode-jpeg.h \
encode-png.h \
reentrant-rwlock.h \
palette.h \
user-handlers.h \
raw_encoder.h \
wait-fd.h
libguac_la_SOURCES = \
@ -97,6 +98,7 @@ libguac_la_SOURCES = \
fips.c \
hash.c \
id.c \
reentrant-rwlock.c \
palette.c \
parser.c \
pool.c \

View File

@ -34,15 +34,42 @@
#include "guacamole/timestamp.h"
#include "guacamole/user.h"
#include "id.h"
#include "reentrant-rwlock.h"
#include <dlfcn.h>
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* The number of nanoseconds between times that the pending users list will be
* synchronized and emptied (250 milliseconds aka 1/4 second).
*/
#define GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL 250000000
/**
* A value that indicates that the pending users timer has yet to be
* initialized and started.
*/
#define GUAC_CLIENT_PENDING_TIMER_UNREGISTERED 0
/**
* A value that indicates that the pending users timer has been initialized
* and started, but that the timer handler is not currently running.
*/
#define GUAC_CLIENT_PENDING_TIMER_REGISTERED 1
/**
* A value that indicates that the pending users timer has been initialized
* and started, and that the timer handler is currently running.
*/
#define GUAC_CLIENT_PENDING_TIMER_TRIGGERED 2
/**
* Empty NULL-terminated array of argument names.
*/
@ -128,10 +155,104 @@ void guac_client_free_stream(guac_client* client, guac_stream* stream) {
}
/**
* Promote all pending users to full users, calling the join pending handler
* before, if any.
*
* @param data
* The client for which all pending users should be promoted.
*/
static void guac_client_promote_pending_users(union sigval data) {
guac_client* client = (guac_client*) data.sival_ptr;
pthread_mutex_lock(&(client->__pending_users_timer_mutex));
/* Check if the previous instance of this handler is still running */
int already_running = (
client->__pending_users_timer_state
== GUAC_CLIENT_PENDING_TIMER_TRIGGERED);
/* Mark the handler as running if it isn't already */
client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_TRIGGERED;
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
/* Do not start the handler if the previous instance is still running */
if (already_running)
return;
/* Acquire the lock for reading and modifying the list of pending users */
guac_acquire_write_lock(&(client->__pending_users_lock));
/* Run the pending join handler, if one is defined */
if (client->join_pending_handler) {
/* If an error occurs in the pending handler */
if(client->join_pending_handler(client)) {
guac_release_lock(&(client->__pending_users_lock));
/* Mark the handler as not running */
pthread_mutex_lock(&(client->__pending_users_timer_mutex));
client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED;
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
/* Log a warning and abort the promotion of the pending users */
guac_client_log(client, GUAC_LOG_WARNING,
"join_pending_handler did not successfully complete;"
" any pending users have not been promoted.\n");
return;
}
}
/* The first pending user in the list, if any */
guac_user* first_user = client->__pending_users;
/* The final user in the list, if any */
guac_user* last_user = first_user;
/* Iterate through the pending users to find the final user */
guac_user* user = first_user;
while (user != NULL) {
last_user = user;
user = user->__next;
}
/* Mark the list as empty */
client->__pending_users = NULL;
/* Acquire the lock for reading and modifying the list of full users. */
guac_acquire_write_lock(&(client->__users_lock));
/* If any users were removed from the pending list, promote them now */
if (last_user != NULL) {
/* Add all formerly-pending users to the start of the user list */
if (client->__users != NULL)
client->__users->__prev = last_user;
last_user->__next = client->__users;
client->__users = first_user;
}
guac_release_lock(&(client->__users_lock));
/* Release the lock (this is done AFTER updating the connected user list
* to ensure that all users are always on exactly one of these lists) */
guac_release_lock(&(client->__pending_users_lock));
/* Mark the handler as complete so the next instance can run */
pthread_mutex_lock(&(client->__pending_users_timer_mutex));
client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED;
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
}
guac_client* guac_client_alloc() {
int i;
pthread_rwlockattr_t lock_attributes;
/* Allocate new client */
guac_client* client = malloc(sizeof(guac_client));
@ -169,15 +290,23 @@ guac_client* guac_client_alloc() {
client->__output_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX;
}
/* Init locks */
pthread_rwlockattr_init(&lock_attributes);
pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
guac_init_reentrant_rwlock(&(client->__users_lock));
guac_init_reentrant_rwlock(&(client->__pending_users_lock));
pthread_rwlock_init(&(client->__users_lock), &lock_attributes);
/* Initialize the write lock flags to 0, as threads won't have yet */
pthread_key_create(&(client->__users_lock.key), (void *) 0);
pthread_key_create(&(client->__pending_users_lock.key), (void *) 0);
/* Set up socket to broadcast to all users */
/* The timer will be lazily created in the child process */
client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_UNREGISTERED;
/* Set up the pending user promotion mutex */
pthread_mutex_init(&(client->__pending_users_timer_mutex), NULL);
/* Set up broadcast sockets */
client->socket = guac_socket_broadcast(client);
client->pending_socket = guac_socket_broadcast_pending(client);
return client;
@ -185,10 +314,22 @@ guac_client* guac_client_alloc() {
void guac_client_free(guac_client* client) {
/* Acquire write locks before referencing user pointers */
guac_acquire_write_lock(&(client->__pending_users_lock));
guac_acquire_write_lock(&(client->__users_lock));
/* Remove all pending users */
while (client->__pending_users != NULL)
guac_client_remove_user(client, client->__pending_users);
/* Remove all users */
while (client->__users != NULL)
guac_client_remove_user(client, client->__users);
/* Release the locks */
guac_release_lock(&(client->__users_lock));
guac_release_lock(&(client->__pending_users_lock));
if (client->free_handler) {
/* FIXME: Errors currently ignored... */
@ -196,8 +337,9 @@ void guac_client_free(guac_client* client) {
}
/* Free socket */
/* Free sockets */
guac_socket_free(client->socket);
guac_socket_free(client->pending_socket);
/* Free layer pools */
guac_pool_free(client->__buffer_pool);
@ -215,7 +357,23 @@ void guac_client_free(guac_client* client) {
guac_client_log(client, GUAC_LOG_ERROR, "Unable to close plugin: %s", dlerror());
}
pthread_rwlock_destroy(&(client->__users_lock));
/* Find out if the pending user promotion timer was ever started */
pthread_mutex_lock(&(client->__pending_users_timer_mutex));
int was_started = (
client->__pending_users_timer_state
!= GUAC_CLIENT_PENDING_TIMER_UNREGISTERED);
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
/* If the timer was registered, stop it before destroying the lock */
if (was_started)
timer_delete(client->__pending_users_timer);
pthread_mutex_destroy(&(client->__pending_users_timer_mutex));
/* Destroy the reentrant read-write locks */
guac_destroy_reentrant_rwlock(&(client->__users_lock));
guac_destroy_reentrant_rwlock(&(client->__pending_users_lock));
free(client->connection_id);
free(client);
}
@ -277,27 +435,128 @@ void guac_client_abort(guac_client* client, guac_protocol_status status,
}
/**
* Add the provided user to the list of pending users who have yet to have
* their connection state synchronized after joining, for the connection
* associated with the given guac client.
*
* @param client
* The client associated with the connection for which the provided user
* is pending a connection state synchronization after joining.
*
* @param user
* The user to add to the pending list.
*/
static void guac_client_add_pending_user(
guac_client* client, guac_user* user) {
/* Acquire the lock for modifying the list of pending users */
guac_acquire_write_lock(&(client->__pending_users_lock));
user->__prev = NULL;
user->__next = client->__pending_users;
if (client->__pending_users != NULL)
client->__pending_users->__prev = user;
client->__pending_users = user;
/* Increment the user count */
client->connected_users++;
/* Release the lock */
guac_release_lock(&(client->__pending_users_lock));
}
/**
* Periodically promote pending users to full users. Returns zero if the timer
* is already running, or successfully created, or a non-zero value if the
* timer could not be created and started.
*
* @param client
* The guac client for which the new timer should be started, if not
* already running.
*
* @return
* Zero if the timer was successfully created and started, or a negative
* value otherwise.
*/
static int guac_client_start_pending_users_timer(guac_client* client) {
pthread_mutex_lock(&(client->__pending_users_timer_mutex));
/* Return success if the timer is already created and running */
if (client->__pending_users_timer_state
!= GUAC_CLIENT_PENDING_TIMER_UNREGISTERED) {
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
return 0;
}
/* Configure the timer to synchronize and clear the pending users */
struct sigevent signal_config = { 0 };
signal_config.sigev_notify = SIGEV_THREAD;
signal_config.sigev_notify_function = guac_client_promote_pending_users;
signal_config.sigev_value.sival_ptr = client;
/* Create a timer to synchronize any pending users periodically */
if (timer_create(
CLOCK_MONOTONIC,
&signal_config,
&(client->__pending_users_timer))) {
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
return 1;
}
/* Configure the pending users timer to run on the defined interval */
struct itimerspec time_config = { 0 };
time_config.it_interval.tv_nsec = GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL;
time_config.it_value.tv_nsec = GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL;
/* Start the timer */
if (timer_settime(
client->__pending_users_timer, 0, &time_config, NULL) < 0) {
timer_delete(client->__pending_users_timer);
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
return 1;
}
/* Mark the timer as registered but not yet running */
client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED;
pthread_mutex_unlock(&(client->__pending_users_timer_mutex));
return 0;
}
int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv) {
/* Create and start the timer if it hasn't already been initialized */
if (guac_client_start_pending_users_timer(client)) {
/**
*
* If the timer could not be created, do not add the user - they cannot
* be synchronized without the timer.
*/
guac_client_log(client, GUAC_LOG_ERROR,
"Could not start pending user timer: %s.", strerror(errno));
return 1;
}
int retval = 0;
/* Call handler, if defined */
if (client->join_handler)
retval = client->join_handler(user, argc, argv);
pthread_rwlock_wrlock(&(client->__users_lock));
/* Add to list if join was successful */
if (retval == 0) {
user->__prev = NULL;
user->__next = client->__users;
if (client->__users != NULL)
client->__users->__prev = user;
client->__users = user;
client->connected_users++;
/*
* Add the user to the list of pending users, to have their connection
* state synchronized asynchronously.
*/
guac_client_add_pending_user(client, user);
/* Update owner pointer if user is owner */
if (user->owner)
@ -305,8 +564,6 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
}
pthread_rwlock_unlock(&(client->__users_lock));
/* Notify owner of user joining connection. */
if (retval == 0 && !user->owner)
guac_client_owner_notify_join(client, user);
@ -317,13 +574,16 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
void guac_client_remove_user(guac_client* client, guac_user* user) {
pthread_rwlock_wrlock(&(client->__users_lock));
guac_acquire_write_lock(&(client->__pending_users_lock));
guac_acquire_write_lock(&(client->__users_lock));
/* Update prev / head */
if (user->__prev != NULL)
user->__prev->__next = user->__next;
else
else if (client->__users == user)
client->__users = user->__next;
else if (client->__pending_users == user)
client->__pending_users = user->__next;
/* Update next */
if (user->__next != NULL)
@ -335,7 +595,8 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
if (user->owner)
client->__owner = NULL;
pthread_rwlock_unlock(&(client->__users_lock));
guac_release_lock(&(client->__users_lock));
guac_release_lock(&(client->__pending_users_lock));
/* Update owner of user having left the connection. */
if (!user->owner)
@ -353,7 +614,7 @@ void guac_client_foreach_user(guac_client* client, guac_user_callback* callback,
guac_user* current;
pthread_rwlock_rdlock(&(client->__users_lock));
guac_acquire_read_lock(&(client->__users_lock));
/* Call function on each user */
current = client->__users;
@ -362,7 +623,25 @@ void guac_client_foreach_user(guac_client* client, guac_user_callback* callback,
current = current->__next;
}
pthread_rwlock_unlock(&(client->__users_lock));
guac_release_lock(&(client->__users_lock));
}
void guac_client_foreach_pending_user(
guac_client* client, guac_user_callback* callback, void* data) {
guac_user* current;
guac_acquire_read_lock(&(client->__pending_users_lock));
/* Call function on each pending user */
current = client->__pending_users;
while (current != NULL) {
callback(current, data);
current = current->__next;
}
guac_release_lock(&(client->__pending_users_lock));
}
@ -371,12 +650,12 @@ void* guac_client_for_owner(guac_client* client, guac_user_callback* callback,
void* retval;
pthread_rwlock_rdlock(&(client->__users_lock));
guac_acquire_read_lock(&(client->__users_lock));
/* Invoke callback with current owner */
retval = callback(client->__owner, data);
pthread_rwlock_unlock(&(client->__users_lock));
guac_release_lock(&(client->__users_lock));
/* Return value from callback */
return retval;
@ -391,7 +670,7 @@ void* guac_client_for_user(guac_client* client, guac_user* user,
int user_valid = 0;
void* retval;
pthread_rwlock_rdlock(&(client->__users_lock));
guac_acquire_read_lock(&(client->__users_lock));
/* Loop through all users, searching for a pointer to the given user */
current = client->__users;
@ -413,7 +692,7 @@ void* guac_client_for_user(guac_client* client, guac_user* user,
/* Invoke callback with requested user (if they exist) */
retval = callback(user, data);
pthread_rwlock_unlock(&(client->__users_lock));
guac_release_lock(&(client->__users_lock));
/* Return value from callback */
return retval;

View File

@ -30,7 +30,9 @@
#include "client-types.h"
#include "object-types.h"
#include "protocol-types.h"
#include "socket.h"
#include "stream-types.h"
#include "user-fntypes.h"
#include "user-types.h"
#include <stdarg.h>
@ -48,6 +50,20 @@
*/
typedef int guac_client_free_handler(guac_client* client);
/**
* Handler that will run before immediately before pending users are promoted
* to full users. The pending user socket should be used to communicate to the
* pending users.
*
* @param client
* The client whose handler was invoked.
*
* @return
* Zero if the pending handler ran successfuly, or a non-zero value if an
* error occured.
*/
typedef int guac_client_join_pending_handler(guac_client* client);
/**
* Handler for logging messages related to a given guac_client instance.
*

View File

@ -30,6 +30,7 @@
#include "client-types.h"
#include "client-constants.h"
#include "layer-types.h"
#include "reentrant-rwlock.h"
#include "object-types.h"
#include "pool-types.h"
#include "socket-types.h"
@ -42,20 +43,29 @@
#include <pthread.h>
#include <stdarg.h>
#include <time.h>
struct guac_client {
/**
* The guac_socket structure to be used to communicate with all connected
* web-clients (users). Unlike the user-level guac_socket, this guac_socket
* will broadcast instructions to all connected users simultaneously. It
* is expected that the implementor of any Guacamole proxy client will
* provide their own mechanism of I/O for their protocol. The guac_socket
* structure is used only to communicate conveniently with the Guacamole
* web-client.
* The guac_socket structure to be used to communicate with all non-pending
* connected web-clients (users). Unlike the user-level guac_socket, this
* guac_socket will broadcast instructions to all non-pending connected users
* simultaneously. It is expected that the implementor of any Guacamole proxy
* client will provide their own mechanism of I/O for their protocol. The
* guac_socket structure is used only to communicate conveniently with the
* Guacamole web-client.
*/
guac_socket* socket;
/**
* The guac_socket structure to be used to communicate with all pending
* connected web-clients (users). Aside from operating on a different
* subset of users, this socket has all the same behavior and semantics as
* the non-pending socket.
*/
guac_socket* pending_socket;
/**
* The current state of the client. When the client is first allocated,
* this will be initialized to GUAC_CLIENT_RUNNING. It will remain at
@ -162,7 +172,7 @@ struct guac_client {
* Lock which is acquired when the users list is being manipulated, or when
* the users list is being iterated.
*/
pthread_rwlock_t __users_lock;
guac_reentrant_rwlock __users_lock;
/**
* The first user within the list of all connected users, or NULL if no
@ -170,6 +180,37 @@ struct guac_client {
*/
guac_user* __users;
/**
* Lock which is acquired when the pending users list is being manipulated,
* or when the pending users list is being iterated.
*/
guac_reentrant_rwlock __pending_users_lock;
/**
* A timer that will periodically synchronize the list of pending users,
* emptying the list once synchronization is complete. Only for internal
* use within the client. This will be NULL until the first user joins
* the connection, as it is lazily instantiated at that time.
*/
timer_t __pending_users_timer;
/**
* A flag storing the current state of the pending users timer.
*/
int __pending_users_timer_state;
/**
* A mutex that must be acquired before modifying or checking the value of
* the timer state.
*/
pthread_mutex_t __pending_users_timer_mutex;
/**
* The first user within the list of connected users who have not yet had
* their connection states synchronized after joining.
*/
guac_user* __pending_users;
/**
* The user that first created this connection. This user will also have
* their "owner" flag set to a non-zero value. If the owner has left the
@ -206,6 +247,22 @@ struct guac_client {
*/
guac_user_join_handler* join_handler;
/**
* A handler that will be run prior to pending users being promoted to full
* users. Any required pending user operations should be performed using
* the client's pending user socket.
*
* Example:
* @code
* int join_pending_handler(guac_client* client);
*
* int guac_client_init(guac_client* client) {
* client->join_pending_handler = join_pending_handler;
* }
* @endcode
*/
guac_client_join_pending_handler* join_pending_handler;
/**
* Handler for leave events, called whenever a new user is leaving an
* active connection.
@ -446,6 +503,26 @@ void guac_client_remove_user(guac_client* client, guac_user* user);
void guac_client_foreach_user(guac_client* client,
guac_user_callback* callback, void* data);
/**
* Calls the given function on all pending users of the given client. The
* function will be given a reference to a guac_user and the specified
* arbitrary data. The value returned by the callback will be ignored.
*
* This function is reentrant, but the pending user list MUST NOT be manipulated
* within the same thread as a callback to this function.
*
* @param client
* The client whose users should be iterated.
*
* @param callback
* The function to call for each pending user.
*
* @param data
* Arbitrary data to pass to the callback each time it is invoked.
*/
void guac_client_foreach_pending_user(guac_client* client,
guac_user_callback* callback, void* data);
/**
* Calls the given function with the currently-connected user that is marked as
* the owner. The owner of a connection is the user that established the

View File

@ -235,10 +235,10 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary);
/**
* Allocates and initializes a new guac_socket which duplicates all
* instructions written across the sockets of each connected user of the given
* guac_client. The returned socket is a write-only socket. Attempts to read
* from the socket will fail. If a write occurs while no users are connected,
* that write will simply be dropped.
* instructions written across the sockets of each connected user of the
* given guac_client. The returned socket is a write-only socket. Attempts
* to read from the socket will fail. If a write occurs while no users are
* connected, that write will simply be dropped.
*
* Return values (error codes) from each user's socket will not affect the
* in-progress write, but each failing user will be forcibly stopped with
@ -253,12 +253,38 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary);
*
* @return
* A write-only guac_socket object which broadcasts copies of all
* instructions written across all connected users of the given
* instructions written across all non-pending connected users of the given
* guac_client, or NULL if an error occurs while allocating the guac_socket
* object.
*/
guac_socket* guac_socket_broadcast(guac_client* client);
/**
* Allocates and initializes a new guac_socket which duplicates all
* instructions written across the sockets of each pending connected
* user of the given guac_client. The returned socket is a write-only socket.
* Attempts to read from the socket will fail. If a write occurs while no
* users are connected, that write will simply be dropped.
*
* Return values (error codes) from each user's socket will not affect the
* in-progress write, but each failing user will be forcibly stopped with
* guac_user_stop().
*
* If an error occurs while allocating the guac_socket object, NULL is returned,
* and guac_error is set appropriately.
*
* @param client
* The client associated with the group of pending users across which
* duplicates of all instructions should be written.
*
* @return
* A write-only guac_socket object which broadcasts copies of all
* instructions written across all pending connected users of the given
* guac_client, or NULL if an error occurs while allocating the guac_socket
* object.
*/
guac_socket* guac_socket_broadcast_pending(guac_client* client);
/**
* Writes the given unsigned int to the given guac_socket object. The data
* written may be buffered until the buffer is flushed automatically or

View File

@ -0,0 +1,254 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <pthread.h>
#include <stdint.h>
#include "reentrant-rwlock.h"
/**
* The value indicating that the current thread holds neither the read or write
* locks.
*/
#define GUAC_REENTRANT_LOCK_NO_LOCK 0
/**
* The value indicating that the current thread holds the read lock.
*/
#define GUAC_REENTRANT_LOCK_READ_LOCK 1
/**
* The value indicating that the current thread holds the write lock.
*/
#define GUAC_REENTRANT_LOCK_WRITE_LOCK 2
void guac_init_reentrant_rwlock(guac_reentrant_rwlock* lock) {
/* Configure to allow sharing this lock with child processes */
pthread_rwlockattr_t lock_attributes;
pthread_rwlockattr_init(&lock_attributes);
pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
/* Initialize the rwlock */
pthread_rwlock_init(&(lock->lock), &lock_attributes);
/* Initialize the flags to 0, as threads won't have acquired it yet */
pthread_key_create(&(lock->key), (void *) 0);
}
void guac_destroy_reentrant_rwlock(guac_reentrant_rwlock* lock) {
/* Destroy the rwlock */
pthread_rwlock_destroy(&(lock->lock));
/* Destroy the thread-local key */
pthread_key_delete(lock->key);
}
/**
* Clean up and destroy the provided guac reentrant rwlock.
*
* @param lock
* The guac reentrant rwlock to be destroyed.
*/
void guac_destroy_reentrant_rwlock(guac_reentrant_rwlock* lock);
/**
* Extract and return the flag indicating which lock is held, if any, from the
* provided key value. The flag is always stored in the least-significant
* nibble of the value.
*
* @param value
* The key value containing the flag.
*
* @return
* The flag indicating which lock is held, if any.
*/
static uintptr_t get_lock_flag(uintptr_t value) {
return value & 0xF;
}
/**
* Extract and return the lock count from the provided key. This returned value
* is the difference between the number of lock and unlock requests made by the
* current thread. This count is always stored in the remaining value after the
* least-significant nibble where the flag is stored.
*
* @param value
* The key value containing the count.
*
* @return
* The difference between the number of lock and unlock requests made by
* the current thread.
*/
static uintptr_t get_lock_count(uintptr_t value) {
return value >> 4;
}
/**
* Given a flag indicating if and how the current thread controls a lock, and
* a count of the depth of lock requests, return a value containing the flag
* in the least-significant nibble, and the count in the rest.
*
* @param flag
* A flag indiciating which lock, if any, is held by the current thread.
*
* @param count
* The depth of the lock attempt by the current thread, i.e. the number of
* lock requests minus unlock requests.
*
* @return
* A value containing the flag in the least-significant nibble, and the
* count in the rest, cast to a void* for thread-local storage.
*/
static void* get_value_from_flag_and_count(
uintptr_t flag, uintptr_t count) {
return (void*) ((flag & 0xF) | count << 4);
}
/**
* Return zero if adding one to the current count would overflow the storage
* allocated to the count, or a non-zero value otherwise.
*
* @param current_count
* The current count for a lock that the current thread is trying to
* reentrantly acquire.
*
* @return
* Zero if adding one to the current count would overflow the storage
* allocated to the count, or a non-zero value otherwise.
*/
static int would_overflow_count(uintptr_t current_count) {
/**
* The count will overflow if it's already equal or greated to the maximum
* possible value that can be stored in a uintptr_t excluding the first nibble.
*/
return current_count >= (UINTPTR_MAX >> 4);
}
int guac_acquire_write_lock(guac_reentrant_rwlock* reentrant_rwlock) {
uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key);
uintptr_t flag = get_lock_flag(key_value);
uintptr_t count = get_lock_count(key_value);
/* If acquiring this lock again would overflow the counter storage */
if (would_overflow_count(count))
return GUAC_REEANTRANT_LOCK_ERROR_TOO_MANY;
/* If the current thread already holds the write lock, increment the count */
if (flag == GUAC_REENTRANT_LOCK_WRITE_LOCK) {
pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
flag, count + 1));
/* This thread already has the lock */
return 0;
}
/*
* The read lock must be released before the write lock can be acquired.
* This is a little odd because it may mean that a function further down
* the stack may have requested a read lock, which will get upgraded to a
* write lock by another function without the caller knowing about it. This
* shouldn't cause any issues, however.
*/
if (key_value == GUAC_REENTRANT_LOCK_READ_LOCK)
pthread_rwlock_unlock(&(reentrant_rwlock->lock));
/* Acquire the write lock */
pthread_rwlock_wrlock(&(reentrant_rwlock->lock));
/* Mark that the current thread has the lock, and increment the count */
pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
GUAC_REENTRANT_LOCK_WRITE_LOCK, count + 1));
return 0;
}
int guac_acquire_read_lock(guac_reentrant_rwlock* reentrant_rwlock) {
uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key);
uintptr_t flag = get_lock_flag(key_value);
uintptr_t count = get_lock_count(key_value);
/* If acquiring this lock again would overflow the counter storage */
if (would_overflow_count(count))
return GUAC_REEANTRANT_LOCK_ERROR_TOO_MANY;
/* The current thread may read if either the read or write lock is held */
if (
flag == GUAC_REENTRANT_LOCK_READ_LOCK ||
flag == GUAC_REENTRANT_LOCK_WRITE_LOCK
) {
/* Increment the depth counter */
pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
flag, count + 1));
/* This thread already has the lock */
return 0;
}
/* Acquire the lock */
pthread_rwlock_rdlock(&(reentrant_rwlock->lock));
/* Set the flag that the current thread has the read lock */
pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
GUAC_REENTRANT_LOCK_READ_LOCK, 1));
return 0;
}
int guac_release_lock(guac_reentrant_rwlock* reentrant_rwlock) {
uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key);
uintptr_t flag = get_lock_flag(key_value);
uintptr_t count = get_lock_count(key_value);
/*
* Return an error if an attempt is made to release a lock that the current
* thread does not control.
*/
if (count <= 0)
return GUAC_REEANTRANT_LOCK_ERROR_DOUBLE_RELEASE;
/* Release the lock if this is the last locked level */
if (count == 1) {
pthread_rwlock_unlock(&(reentrant_rwlock->lock));
/* Set the flag that the current thread holds no locks */
pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
GUAC_REENTRANT_LOCK_NO_LOCK, 0));
return 0;
}
/* Do not release the lock since it's still in use - just decrement */
pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count(
flag, count - 1));
return 0;
}

View File

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef __GUAC_REENTRANT_LOCK_H
#define __GUAC_REENTRANT_LOCK_H
#include <pthread.h>
/**
* This file implements reentrant read-write locks using thread-local storage
* to keep track of how locks are held and released by the current thread,
* since the pthread locks do not support reentrant behavior.
*
* A thread will attempt to acquire the requested lock on the first acquire
* function call, and will release it once the number of unlock requests
* matches the number of lock requests. Therefore, it is safe to aquire a lock
* and then call a function that also acquires the same lock, provided that
* the caller and the callee request to unlock the lock when done with it.
*
* Any lock that's locked using one of the functions defined in this file
* must _only_ be unlocked using the unlock function defined here to avoid
* unexpected behavior.
*/
/**
* An error code indicating that the calling thread is attempting to release a
* lock that it does not control.
*/
#define GUAC_REEANTRANT_LOCK_ERROR_DOUBLE_RELEASE 1
/**
* The lock cannot be acquired because the lock has been already been
* reentrantly acquired too many times, exhausting the capacity of this library
* to track this lock. The lock must be released using guac_release_lock()
* before it can be reacquired.
*/
#define GUAC_REEANTRANT_LOCK_ERROR_TOO_MANY 2
/**
* A structure packaging together a pthread rwlock along with a key to a
* thread-local property to keep track of the current status of the lock,
* allowing the functions defined in this header to provide reentrant behavior.
* Note that both the lock and key must be initialized before being provided
* to any of these functions.
*/
typedef struct guac_reentrant_rwlock {
/**
* A non-reentrant pthread rwlock to be wrapped by the local lock,
* functions providing reentrant behavior.
*/
pthread_rwlock_t lock;
/**
* A key to access a thread-local property tracking any ownership of the
* lock by the current thread.
*/
pthread_key_t key;
} guac_reentrant_rwlock;
/**
* Initialize the provided guac reentrant rwlock. The lock will be configured to be
* visible to child processes.
*
* @param lock
* The guac reentrant rwlock to be initialized.
*/
void guac_init_reentrant_rwlock(guac_reentrant_rwlock* lock);
/**
* Clean up and destroy the provided guac reentrant rwlock.
*
* @param lock
* The guac reentrant rwlock to be destroyed.
*/
void guac_destroy_reentrant_rwlock(guac_reentrant_rwlock* lock);
/**
* Aquire the write lock for the provided guac reentrant rwlock, if the key does not
* indicate that the write lock is already acquired. If the key indicates that
* the read lock is already acquired, the read lock will be dropped before the
* write lock is acquired. The thread local property associated with the key
* will be updated as necessary to track the thread's ownership of the lock.
*
* @param reentrant_rwlock
* The guac reentrant rwlock for which the write lock should be acquired
* reentrantly.
*
* @return
* Zero if the lock is succesfully acquired, or an error code defined above
* by a GUAC_REEANTRANT_LOCK_ERROR_* constant if the lock cannot be acquired.
*/
int guac_acquire_write_lock(guac_reentrant_rwlock* reentrant_rwlock);
/**
* Aquire the read lock for the provided guac reentrant rwlock, if the key does not
* indicate that the read or write lock is already acquired. The thread local
* property associated with the key will be updated as necessary to track the
* thread's ownership of the lock.
*
* @param reentrant_rwlock
* The guac reentrant rwlock for which the read lock should be acquired
* reentrantly.
*
* @return
* Zero if the lock is succesfully acquired, or an error code defined above
* by a GUAC_REEANTRANT_LOCK_ERROR_* constant if the lock cannot be acquired.
*/
int guac_acquire_read_lock(guac_reentrant_rwlock* reentrant_rwlock);
/**
* Release the the rwlock associated with the provided guac reentrant rwlock if this
* is the last level of the lock held by this thread. Otherwise, the thread
* local property associated with the key will be updated as needed to ensure
* that the correct number of release requests will finally release the lock.
*
* @param reentrant_rwlock
* The guac reentrant rwlock that should be released.
*
* @return
* Zero if the lock is succesfully released, or an error code defined above
* by a GUAC_REEANTRANT_LOCK_ERROR_* constant if the lock cannot be released.
*/
int guac_release_lock(guac_reentrant_rwlock* reentrant_rwlock);
#endif

View File

@ -28,8 +28,25 @@
#include <stdlib.h>
/**
* Data associated with an open socket which writes to all connected users of
* a particular guac_client.
* A function that will broadcast arbitrary data to a subset of users for
* the provided client, using the provided user callback for any user-specific
* operations.
*
* @param client
* The guac_client associated with the users to broadcast to.
*
* @param callback
* A callback that should be invoked with each broadcasted user.
*
* @param data
* Arbitrary data that may be used to broadcast to the subset of users.
*/
typedef void guac_socket_broadcast_handler(
guac_client* client, guac_user_callback* callback, void* data);
/**
* Data associated with an open socket which writes to a subset of connected
* users of a particular guac_client.
*/
typedef struct guac_socket_broadcast_data {
@ -45,6 +62,11 @@ typedef struct guac_socket_broadcast_data {
*/
pthread_mutex_t socket_lock;
/**
* The function to broadcast
*/
guac_socket_broadcast_handler* broadcast_handler;
} guac_socket_broadcast_data;
/**
@ -91,7 +113,7 @@ static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
}
/**
* Callback invoked by guac_client_foreach_user() which write a given chunk of
* Callback invoked by the broadcast handler which write a given chunk of
* data to that user's socket. If the write attempt fails, the user is
* signalled to stop with guac_user_stop().
*
@ -146,15 +168,15 @@ static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
chunk.buffer = buf;
chunk.length = count;
/* Broadcast chunk to all users */
guac_client_foreach_user(data->client, __write_chunk_callback, &chunk);
/* Broadcast chunk to the users */
data->broadcast_handler(data->client, __write_chunk_callback, &chunk);
return count;
}
/**
* Callback which is invoked by guac_client_foreach_user() to flush all
* Callback which is invoked by the broadcast handler to flush all
* pending data on the given user's socket. If an error occurs while flushing
* a user's socket, that user is signalled to stop with guac_user_stop().
*
@ -162,7 +184,7 @@ static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
* The user whose socket should be flushed.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* Arbitrary data passed to the broadcast handler. This is not needed
* by this callback, and should be left as NULL.
*
* @return
@ -195,15 +217,15 @@ static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
guac_socket_broadcast_data* data =
(guac_socket_broadcast_data*) socket->data;
/* Flush all users */
guac_client_foreach_user(data->client, __flush_callback, NULL);
/* Flush the users */
data->broadcast_handler(data->client, __flush_callback, NULL);
return 0;
}
/**
* Callback which is invoked by guac_client_foreach_user() to lock the given
* Callback which is invoked by the broadcast handler to lock the given
* user's socket in preparation for the beginning of a Guacamole protocol
* instruction.
*
@ -211,7 +233,7 @@ static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
* The user whose socket should be locked.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* Arbitrary data passed to the broadcast handler. This is not needed
* by this callback, and should be left as NULL.
*
* @return
@ -243,20 +265,20 @@ static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
/* Acquire exclusive access to socket */
pthread_mutex_lock(&(data->socket_lock));
/* Lock sockets of all users */
guac_client_foreach_user(data->client, __lock_callback, NULL);
/* Lock sockets of the users */
data->broadcast_handler(data->client, __lock_callback, NULL);
}
/**
* Callback which is invoked by guac_client_foreach_user() to unlock the given
* Callback which is invoked by the broadcast handler to unlock the given
* user's socket at the end of a Guacamole protocol instruction.
*
* @param user
* The user whose socket should be unlocked.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* Arbitrary data passed to the broadcast handler. This is not needed
* by this callback, and should be left as NULL.
*
* @return
@ -285,7 +307,7 @@ static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
(guac_socket_broadcast_data*) socket->data;
/* Unlock sockets of all users */
guac_client_foreach_user(data->client, __unlock_callback, NULL);
data->broadcast_handler(data->client, __unlock_callback, NULL);
/* Relinquish exclusive access to socket */
pthread_mutex_unlock(&(data->socket_lock));
@ -343,7 +365,22 @@ static int __guac_socket_broadcast_free_handler(guac_socket* socket) {
}
guac_socket* guac_socket_broadcast(guac_client* client) {
/**
* Construct and return a socket that will broadcast to the users given by
* by the provided broadcast handler.
*
* @param client
* The client who's users are being broadcast to.
*
* @param broadcast_handler
* The handler that will peform the broadcast against a subset of users
* of the provided client.
*
* @return
* The newly constructed broadcast socket
*/
static guac_socket* __guac_socket_init(
guac_client* client, guac_socket_broadcast_handler* broadcast_handler) {
pthread_mutexattr_t lock_attributes;
@ -352,6 +389,9 @@ guac_socket* guac_socket_broadcast(guac_client* client) {
guac_socket_broadcast_data* data =
malloc(sizeof(guac_socket_broadcast_data));
/* Set the provided broadcast handler */
data->broadcast_handler = broadcast_handler;
/* Store client as socket data */
data->client = client;
socket->data = data;
@ -361,7 +401,7 @@ guac_socket* guac_socket_broadcast(guac_client* client) {
/* Init lock */
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;
@ -375,3 +415,17 @@ guac_socket* guac_socket_broadcast(guac_client* client) {
}
guac_socket* guac_socket_broadcast(guac_client* client) {
/* Broadcast to all connected non-pending users*/
return __guac_socket_init(client, guac_client_foreach_user);
}
guac_socket* guac_socket_broadcast_pending(guac_client* client) {
/* Broadcast to all connected pending users*/
return __guac_socket_init(client, guac_client_foreach_pending_user);
}

View File

@ -63,23 +63,31 @@ int guac_kubernetes_argv_callback(guac_user* user, const char* mimetype,
void* guac_kubernetes_send_current_argv(guac_user* user, void* data) {
guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) data;
/* Defer to the batch handler, using the user socket */
return guac_kubernetes_send_current_argv_batch(user->client, user->socket);
}
void* guac_kubernetes_send_current_argv_batch(
guac_client* client, guac_socket* socket) {
guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data;
guac_terminal* terminal = kubernetes_client->term;
/* Send current color scheme */
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_KUBERNETES_ARGV_COLOR_SCHEME,
guac_terminal_get_color_scheme(terminal));
/* Send current font name */
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_KUBERNETES_ARGV_FONT_NAME,
guac_terminal_get_font_name(terminal));
/* Send current font size */
char font_size[64];
sprintf(font_size, "%i", guac_terminal_get_font_size(terminal));
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_KUBERNETES_ARGV_FONT_SIZE, font_size);
return NULL;

View File

@ -22,6 +22,7 @@
#define GUAC_KUBERNETES_ARGV_H
#include "config.h"
#include "kubernetes.h"
#include <guacamole/argv.h>
#include <guacamole/user.h>
@ -55,7 +56,7 @@ guac_argv_callback guac_kubernetes_argv_callback;
* while the connection is running to the given user. Note that the user
* receiving these values will not necessarily be able to set new values
* themselves if their connection is read-only. This function can be used as
* the callback for guac_client_foreach_user() and guac_client_for_owner()
* the callback for guac_client_foreach_user() and guac_client_for_owner().
*
* @param user
* The user that should receive the values of all non-sensitive parameters
@ -70,5 +71,23 @@ guac_argv_callback guac_kubernetes_argv_callback;
*/
void* guac_kubernetes_send_current_argv(guac_user* user, void* data);
/**
* Sends the current values of all non-sensitive parameters which may be set
* while the connection is running to the all users associated with the
* provided socket. Note that the users receiving these values will not
* necessarily be able to set new values themselves if their connection is
* read-only.
*
* @param client
* The client associated with the users who should receive the values of
* all non-sensitive parameters which may be set while the connection is
* running.
*
* @return
* Always NULL.
*/
void* guac_kubernetes_send_current_argv_batch(
guac_client* client, guac_socket* socket);
#endif

View File

@ -25,6 +25,7 @@
#include <guacamole/argv.h>
#include <guacamole/client.h>
#include <guacamole/socket.h>
#include <libwebsockets.h>
#include <langinfo.h>
@ -77,6 +78,32 @@ static void guac_kubernetes_log(int level, const char* line) {
}
/**
* A pending join handler implementation that will synchronize the connection
* state for all pending users prior to them being promoted to full user.
*
* @param client
* The client whose pending users are about to be promoted to full users,
* and therefore need their connection state synchronized.
*
* @return
* Always zero.
*/
static int guac_kubernetes_join_pending_handler(guac_client* client) {
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Synchronize the terminal state to all pending users */
guac_socket* broadcast_socket = client->pending_socket;
guac_terminal_sync_users(kubernetes_client->term, client, broadcast_socket);
guac_kubernetes_send_current_argv_batch(client, broadcast_socket);
guac_socket_flush(broadcast_socket);
return 0;
}
int guac_client_init(guac_client* client) {
/* Ensure reference to main guac_client remains available in all
@ -96,6 +123,7 @@ int guac_client_init(guac_client* client) {
/* Set handlers */
client->join_handler = guac_kubernetes_user_join_handler;
client->join_pending_handler = guac_kubernetes_join_pending_handler;
client->free_handler = guac_kubernetes_client_free_handler;
client->leave_handler = guac_kubernetes_user_leave_handler;

View File

@ -71,13 +71,6 @@ int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) {
}
/* If not owner, synchronize with current display */
else {
guac_terminal_dup(kubernetes_client->term, user, user->socket);
guac_kubernetes_send_current_argv(user, kubernetes_client);
guac_socket_flush(user->socket);
}
/* Only handle events if not read-only */
if (!settings->read_only) {

View File

@ -41,9 +41,10 @@ void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* pipe_sv
}
void guac_rdp_pipe_svc_send_pipes(guac_user* user) {
guac_client* client = user->client;
void guac_rdp_pipe_svc_send_pipes(
guac_client* client, guac_socket* socket) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_common_list_lock(rdp_client->available_svc);
@ -51,12 +52,11 @@ void guac_rdp_pipe_svc_send_pipes(guac_user* user) {
/* Send pipe for each allocated SVC's output stream */
guac_common_list_element* current = rdp_client->available_svc->head;
while (current != NULL) {
guac_rdp_pipe_svc_send_pipe(user->socket, (guac_rdp_pipe_svc*) current->data);
guac_rdp_pipe_svc_send_pipe(socket, (guac_rdp_pipe_svc*) current->data);
current = current->next;
}
guac_common_list_unlock(rdp_client->available_svc);
}
void guac_rdp_pipe_svc_add(guac_client* client, guac_rdp_pipe_svc* pipe_svc) {

View File

@ -95,14 +95,18 @@ void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* svc);
/**
* Sends the "pipe" instructions describing all static virtual channels
* available to the given user along that user's socket. Each pipe instruction
* will relate the associated SVC's underlying output stream with the SVC's
* name and the mimetype "application/octet-stream".
* available to the all users associated with the provided socket. Each pipe
* instruction will relate the associated SVC's underlying output stream with
* the SVC's name and the mimetype "application/octet-stream".
*
* @param user
* The user to send the "pipe" instructions to.
* @param client
* The client associated with the users being sent the pipe instruction.
*
* @param socket
* The socket to send the pipe instruction accross.
*/
void guac_rdp_pipe_svc_send_pipes(guac_user* user);
void guac_rdp_pipe_svc_send_pipes(
guac_client* client, guac_socket* socket);
/**
* Add the given SVC to the list of all available SVCs. This function must be

View File

@ -21,6 +21,7 @@
#include "channels/audio-input/audio-buffer.h"
#include "channels/cliprdr.h"
#include "channels/disp.h"
#include "channels/pipe-svc.h"
#include "config.h"
#include "fs.h"
#include "log.h"
@ -78,6 +79,60 @@ static int is_writable_directory(const char* path) {
}
/**
* Add the provided user to the provided audio stream.
*
* @param user
* The pending user who should be added to the audio stream.
*
* @param data
* The audio stream that the user should be added to.
*
* @return
* Always NULL.
*/
static void* guac_rdp_sync_pending_user_audio(guac_user* user, void* data) {
/* Add the user to the stream */
guac_audio_stream* audio = (guac_audio_stream*) data;
guac_audio_stream_add_user(audio, user);
return NULL;
}
/**
* A pending join handler implementation that will synchronize the connection
* state for all pending users prior to them being promoted to full user.
*
* @param client
* The client whose pending users are about to be promoted.
*
* @return
* Always zero.
*/
static int guac_rdp_join_pending_handler(guac_client* client) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_socket* broadcast_socket = client->pending_socket;
/* Synchronize any audio stream for each pending user */
if (rdp_client->audio)
guac_client_foreach_pending_user(
client, guac_rdp_sync_pending_user_audio, rdp_client->audio);
/* Bring user up to date with any registered static channels */
guac_rdp_pipe_svc_send_pipes(client, broadcast_socket);
/* Synchronize with current display */
guac_common_display_dup(rdp_client->display, client, broadcast_socket);
guac_socket_flush(broadcast_socket);
return 0;
}
int guac_client_init(guac_client* client, int argc, char** argv) {
/* Automatically set HOME environment variable if unset (FreeRDP's
@ -164,6 +219,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
/* Set handlers */
client->join_handler = guac_rdp_user_join_handler;
client->join_pending_handler = guac_rdp_join_pending_handler;
client->free_handler = guac_rdp_client_free_handler;
client->leave_handler = guac_rdp_user_leave_handler;

View File

@ -650,7 +650,7 @@ static int guac_rdp_handle_connection(guac_client* client) {
rdp_client->rdp_inst = NULL;
/* Free SVC list */
guac_common_list_free(rdp_client->available_svc);
guac_common_list_free(rdp_client->available_svc, NULL);
rdp_client->available_svc = NULL;
/* Free RDP keyboard state */

View File

@ -82,22 +82,6 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
}
/* If not owner, synchronize with current state */
else {
/* Synchronize any audio stream */
if (rdp_client->audio)
guac_audio_stream_add_user(rdp_client->audio, user);
/* Bring user up to date with any registered static channels */
guac_rdp_pipe_svc_send_pipes(user);
/* Synchronize with current display */
guac_common_display_dup(rdp_client->display, user, user->socket);
guac_socket_flush(user->socket);
}
/* Only handle events if not read-only */
if (!settings->read_only) {

View File

@ -70,26 +70,33 @@ int guac_ssh_argv_callback(guac_user* user, const char* mimetype,
void* guac_ssh_send_current_argv(guac_user* user, void* data) {
guac_ssh_client* ssh_client = (guac_ssh_client*) data;
/* Defer to the batch handler, using the user's socket to send the data */
guac_ssh_send_current_argv_batch(user->client, user->socket);
return NULL;
}
void guac_ssh_send_current_argv_batch(guac_client* client, guac_socket* socket) {
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal* terminal = ssh_client->term;
/* Send current color scheme */
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_SSH_ARGV_COLOR_SCHEME,
guac_terminal_get_color_scheme(terminal));
/* Send current font name */
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_SSH_ARGV_FONT_NAME,
guac_terminal_get_font_name(terminal));
/* Send current font size */
char font_size[64];
sprintf(font_size, "%i", guac_terminal_get_font_size(terminal));
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_SSH_ARGV_FONT_SIZE, font_size);
return NULL;
}

View File

@ -69,5 +69,23 @@ guac_argv_callback guac_ssh_argv_callback;
*/
void* guac_ssh_send_current_argv(guac_user* user, void* data);
/**
* Sends the current values of all non-sensitive parameters which may be set
* while the connection is running to the users associated with the provided
* socket. Note that the users receiving these values will not necessarily be
* able to set new values themselves if their connection is read-only.
*
* @param client
* The client associated with the users that should receive the values of
* all non-sensitive parameters which may be set while the connection is running.
*
* @param socket
* The socket to the arguments to the batch of users along.
*
* @return
* Always NULL.
*/
void guac_ssh_send_current_argv_batch(guac_client* client, guac_socket* socket);
#endif

View File

@ -34,6 +34,32 @@
#include <guacamole/argv.h>
#include <guacamole/client.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
/**
* A pending join handler implementation that will synchronize the connection
* state for all pending users prior to them being promoted to full user.
*
* @param client
* The client whose pending users are about to be promoted.
*
* @return
* Always zero.
*/
static int guac_ssh_join_pending_handler(guac_client* client) {
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
/* Synchronize the terminal state to all pending users */
guac_socket* broadcast_socket = client->pending_socket;
guac_terminal_sync_users(ssh_client->term, client, broadcast_socket);
guac_ssh_send_current_argv_batch(client, broadcast_socket);
guac_socket_flush(broadcast_socket);
return 0;
}
int guac_client_init(guac_client* client) {
@ -46,6 +72,7 @@ int guac_client_init(guac_client* client) {
/* Set handlers */
client->join_handler = guac_ssh_user_join_handler;
client->join_pending_handler = guac_ssh_join_pending_handler;
client->free_handler = guac_ssh_client_free_handler;
client->leave_handler = guac_ssh_user_leave_handler;

View File

@ -73,13 +73,6 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) {
}
/* If not owner, synchronize with current display */
else {
guac_terminal_dup(ssh_client->term, user, user->socket);
guac_ssh_send_current_argv(user, ssh_client);
guac_socket_flush(user->socket);
}
/* Only handle events if not read-only */
if (!settings->read_only) {

View File

@ -65,26 +65,34 @@ int guac_telnet_argv_callback(guac_user* user, const char* mimetype,
void* guac_telnet_send_current_argv(guac_user* user, void* data) {
guac_telnet_client* telnet_client = (guac_telnet_client*) data;
/* Defer to the batch handler, using the user's socket to send the data */
guac_telnet_send_current_argv_batch(user->client, user->socket);
return NULL;
}
void guac_telnet_send_current_argv_batch(
guac_client* client, guac_socket* socket) {
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_terminal* terminal = telnet_client->term;
/* Send current color scheme */
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_TELNET_ARGV_COLOR_SCHEME,
guac_terminal_get_color_scheme(terminal));
/* Send current font name */
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_TELNET_ARGV_FONT_NAME,
guac_terminal_get_font_name(terminal));
/* Send current font size */
char font_size[64];
sprintf(font_size, "%i", guac_terminal_get_font_size(terminal));
guac_user_stream_argv(user, user->socket, "text/plain",
guac_client_stream_argv(client, socket, "text/plain",
GUAC_TELNET_ARGV_FONT_SIZE, font_size);
return NULL;
}

View File

@ -69,5 +69,24 @@ guac_argv_callback guac_telnet_argv_callback;
*/
void* guac_telnet_send_current_argv(guac_user* user, void* data);
/**
* Sends the current values of all non-sensitive parameters which may be set
* while the connection is running to the users associated with the provided
* socket. Note that the users receiving these values will not necessarily be
* able to set new values themselves if their connection is read-only.
*
* @param client
* The client associated with the users that should receive the values of
* all non-sensitive parameters which may be set while the connection is running.
*
* @param socket
* The socket to the arguments to the batch of users along.
*
* @return
* Always NULL.
*/
void guac_telnet_send_current_argv_batch(
guac_client* client, guac_socket* socket);
#endif

View File

@ -34,6 +34,31 @@
#include <guacamole/argv.h>
#include <guacamole/client.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
/**
* A pending join handler implementation that will synchronize the connection
* state for all pending users prior to them being promoted to full user.
*
* @param client
* The client whose pending users are about to be promoted.
*
* @return
* Always zero.
*/
static int guac_telnet_join_pending_handler(guac_client* client) {
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
/* Synchronize the terminal state to all pending users */
guac_socket* broadcast_socket = client->pending_socket;
guac_terminal_sync_users(telnet_client->term, client, broadcast_socket);
guac_telnet_send_current_argv_batch(client, broadcast_socket);
guac_socket_flush(broadcast_socket);
return 0;
}
int guac_client_init(guac_client* client) {
@ -51,6 +76,7 @@ int guac_client_init(guac_client* client) {
/* Set handlers */
client->join_handler = guac_telnet_user_join_handler;
client->join_pending_handler = guac_telnet_join_pending_handler;
client->free_handler = guac_telnet_client_free_handler;
client->leave_handler = guac_telnet_user_leave_handler;

View File

@ -70,13 +70,6 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) {
}
/* If not owner, synchronize with current display */
else {
guac_terminal_dup(telnet_client->term, user, user->socket);
guac_telnet_send_current_argv(user, telnet_client);
guac_socket_flush(user->socket);
}
/* Only handle events if not read-only */
if (!settings->read_only) {

View File

@ -40,6 +40,60 @@
#include <stdlib.h>
#include <string.h>
#ifdef ENABLE_PULSE
/**
* Add the provided user to the provided audio stream.
*
* @param user
* The pending user who should be added to the audio stream.
*
* @param data
* The audio stream that the user should be added to.
*
* @return
* Always NULL.
*/
static void* guac_vnc_sync_pending_user_audio(guac_user* user, void* data) {
/* Add the user to the stream */
guac_pa_stream* audio = (guac_pa_stream*) data;
guac_pa_stream_add_user(audio, user);
return NULL;
}
#endif
/**
* A pending join handler implementation that will synchronize the connection
* state for all pending users prior to them being promoted to full user.
*
* @param client
* The client whose pending users are about to be promoted.
*
* @return
* Always zero.
*/
static int guac_vnc_join_pending_handler(guac_client* client) {
guac_vnc_client* vnc_client = (guac_vnc_client*) client->data;
guac_socket* broadcast_socket = client->pending_socket;
#ifdef ENABLE_PULSE
/* Synchronize any audio stream for each pending user */
if (vnc_client->audio)
guac_client_foreach_pending_user(
client, guac_vnc_sync_pending_user_audio, vnc_client->audio);
#endif
/* Synchronize with current display */
guac_common_display_dup(vnc_client->display, client, broadcast_socket);
guac_socket_flush(broadcast_socket);
return 0;
}
int guac_client_init(guac_client* client) {
/* Set client args */
@ -59,6 +113,7 @@ int guac_client_init(guac_client* client) {
/* Set handlers */
client->join_handler = guac_vnc_user_join_handler;
client->join_pending_handler = guac_vnc_join_pending_handler;
client->leave_handler = guac_vnc_user_leave_handler;
client->free_handler = guac_vnc_client_free_handler;

View File

@ -74,21 +74,6 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) {
}
/* If not owner, synchronize with current state */
else {
#ifdef ENABLE_PULSE
/* Synchronize an audio stream */
if (vnc_client->audio)
guac_pa_stream_add_user(vnc_client->audio, user);
#endif
/* Synchronize with current display */
guac_common_display_dup(vnc_client->display, user, user->socket);
guac_socket_flush(user->socket);
}
/* Only handle events if not read-only */
if (!settings->read_only) {

View File

@ -842,11 +842,11 @@ void guac_terminal_display_flush(guac_terminal_display* display) {
}
void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user,
guac_socket* socket) {
void guac_terminal_display_dup(
guac_terminal_display* display, guac_client* client, guac_socket* socket) {
/* Create default surface */
guac_common_surface_dup(display->display_surface, user, socket);
guac_common_surface_dup(display->display_surface, client, socket);
/* Select layer is a child of the display layer */
guac_protocol_send_move(socket, display->select_layer,
@ -1051,4 +1051,4 @@ int guac_terminal_display_set_font(guac_terminal_display* display,
return 0;
}
}

View File

@ -23,6 +23,7 @@
#include <guacamole/layer.h>
#include <guacamole/socket.h>
#include <guacamole/protocol.h>
#include <guacamole/user.h>
#include <stdlib.h>
@ -332,7 +333,7 @@ static void calculate_state(guac_terminal_scrollbar* scrollbar,
}
void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar,
guac_user* user, guac_socket* socket) {
guac_client* client, guac_socket* socket) {
/* Get old state */
guac_terminal_scrollbar_render_state* state = &scrollbar->render_state;

View File

@ -2053,18 +2053,47 @@ int guac_terminal_create_typescript(guac_terminal* term, const char* path,
}
void guac_terminal_dup(guac_terminal* term, guac_user* user,
guac_socket* socket) {
/**
* Synchronize the state of the provided terminal to a subset of users of
* the provided guac_client using the provided socket.
*
* @param client
* The client whose users should be synchronized.
*
* @param term
* The terminal state that should be synchronized to the users.
*
* @param socket
* The socket that should be used to communicate with the users.
*/
static void __guac_terminal_sync_socket(
guac_client* client, guac_terminal* term, guac_socket* socket) {
/* Synchronize display state with new user */
guac_terminal_repaint_default_layer(term, socket);
guac_terminal_display_dup(term->display, user, socket);
guac_terminal_display_dup(term->display, client, socket);
/* Synchronize mouse cursor */
guac_common_cursor_dup(term->cursor, user, socket);
guac_common_cursor_dup(term->cursor, client, socket);
/* Paint scrollbar for joining user */
guac_terminal_scrollbar_dup(term->scrollbar, user, socket);
/* Paint scrollbar for joining users */
guac_terminal_scrollbar_dup(term->scrollbar, client, socket);
}
void guac_terminal_dup(guac_terminal* term, guac_user* user,
guac_socket* socket) {
/* Ignore the user and just use the provided socket directly */
__guac_terminal_sync_socket(user->client, term, socket);
}
void guac_terminal_sync_users(
guac_terminal* term, guac_client* client, guac_socket* socket) {
/* Use the provided socket to synchronize state to the users */
__guac_terminal_sync_socket(client, term, socket);
}

View File

@ -324,21 +324,23 @@ void guac_terminal_display_resize(guac_terminal_display* display, int width, int
void guac_terminal_display_flush(guac_terminal_display* display);
/**
* Initializes and syncs the current terminal display state for the given user
* that has just joined the connection, sending the necessary instructions to
* completely recreate and redraw the terminal rendering over the given socket.
* Initializes and syncs the current terminal display state for all joining
* users associated with the provided socket, sending the necessary instructions
* to completely recreate and redraw the terminal rendering over the given
* socket.
*
* @param display
* The terminal display to sync to the given user.
* The terminal display to sync to the users associated with the provided
* socket.
*
* @param user
* The user that has just joined the connection.
* @param client
* The client whose users are joining.
*
* @param socket
* The socket over which any necessary instructions should be sent.
*/
void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user,
guac_socket* socket);
void guac_terminal_display_dup(
guac_terminal_display* display, guac_client* client, guac_socket* socket);
/**
* Draws the text selection rectangle from the given coordinates to the given end coordinates.

View File

@ -255,22 +255,22 @@ void guac_terminal_scrollbar_free(guac_terminal_scrollbar* scrollbar);
void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar);
/**
* Forces a complete redraw / resync of scrollbar state for the given user that
* has just joined the connection, sending the necessary instructions to
* Forces a complete redraw / resync of scrollbar state for all joinging users
* associated with the provided socket, sending the necessary instructions to
* completely recreate and redraw the scrollbar rendering over the given
* socket.
*
* @param scrollbar
* The scrollbar to sync to the given user.
* The scrollbar to sync to the given users.
*
* @param user
* The user that has just joined the connection.
* @param client
* The client associated with the joining users.
*
* @param socket
* The socket over which any necessary instructions should be sent.
*/
void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar,
guac_user* user, guac_socket* socket);
guac_client* client, guac_socket* socket);
/**
* Sets the minimum and maximum allowed scroll values of the given scrollbar

View File

@ -619,6 +619,9 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...);
* connection. All instructions necessary to replicate state are sent over the
* given socket.
*
* @deprecated The guac_terminal_sync_users method should be used when
* duplicating display state to a set of users.
*
* @param term
* The terminal emulator associated with the connection being joined.
*
@ -632,6 +635,24 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...);
void guac_terminal_dup(guac_terminal* term, guac_user* user,
guac_socket* socket);
/**
* Replicates the current display state to one or more users that are joining
* the connection. All instructions necessary to replicate state are sent over
* the given socket. The set of users receiving these instructions is
* determined solely by the socket chosen.
*
* @param term
* The terminal whose state should be synchronized to the users.
*
* @param client
* The client associated with the users to be synchronized.
*
* @param socket
* The socket to which the terminal state will be broadcast.
*/
void guac_terminal_sync_users(
guac_terminal* term, guac_client* client, guac_socket* socket);
/**
* Resize the client display and terminal to the given pixel dimensions.
*