GUACAMOLE-1846: Synchronize new users with the connection state in batches.

This commit is contained in:
James Muehlner 2023-08-11 04:03:56 +00:00
parent 1f2ecdf694
commit 2cb75e8618
14 changed files with 955 additions and 67 deletions

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,25 @@
#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 <stdatomic.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
/**
* Empty NULL-terminated array of argument names.
*/
@ -128,10 +138,73 @@ 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;
/* Do not start if the previous promotion event is still running */
if (atomic_flag_test_and_set(&(client->__pending_timer_event_active)))
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)
client->join_pending_handler(client);
/* 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 non-pending user list
* to ensure that all users are always on exactly one of these lists) */
guac_release_lock(&(client->__pending_users_lock));
/* Mark the timer event as complete so the next instance can run */
atomic_flag_clear(&(client->__pending_timer_event_active));
}
guac_client* guac_client_alloc() {
int i;
pthread_rwlockattr_t lock_attributes;
/* Allocate new client */
guac_client* client = malloc(sizeof(guac_client));
@ -169,22 +242,33 @@ 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);
/* Ensure the timer is constructed only once */
pthread_mutex_init(&(client->__pending_users_timer_mutex), NULL);
/* Set up socket to broadcast to all users */
client->socket = guac_socket_broadcast(client);
/* Set the timer event thread as initially inactive, since it hasn't run */
atomic_flag_clear(&(client->__pending_timer_event_active));
return client;
}
void guac_client_free(guac_client* client) {
/* 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);
@ -215,7 +299,15 @@ 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));
/* Destroy the pending users timer */
pthread_mutex_destroy(&(client->__pending_users_timer_mutex));
if (client->__pending_users_timer_running != 0)
timer_delete(client->__pending_users_timer);
/* Destroy the reenrant 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 +369,125 @@ 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_running != 0) {
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;
}
client->__pending_users_timer_running = 1;
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 +495,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 +505,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->__users_lock));
guac_acquire_write_lock(&(client->__pending_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 +526,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->__pending_users_lock));
guac_release_lock(&(client->__users_lock));
/* Update owner of user having left the connection. */
if (!user->owner)
@ -353,7 +545,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 +554,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 +581,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 +601,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 +623,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

@ -48,6 +48,16 @@
*/
typedef int guac_client_free_handler(guac_client* client);
/**
* Handler that will run before pending users are promoted to full users.
* Any required operations for pending users should be applied using
* guac_client_foreach_pending_user().
*
* @param client
* The client whose handler was invoked.
*/
typedef void 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"
@ -40,8 +41,10 @@
#include <cairo/cairo.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <time.h>
struct guac_client {
@ -162,7 +165,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 +173,43 @@ 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;
/**
* Non-zero if the pending users timer is configured and running, or zero
* otherwise.
*/
int __pending_users_timer_running;
/**
* A mutex that must be acquired before modifying the pending users timer.
*/
pthread_mutex_t __pending_users_timer_mutex;
/**
* A flag that indicates whether the pending users timer event thread is
* currently running.
*/
volatile atomic_flag __pending_timer_event_active;
/**
* 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 +246,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 applied
* guac_client_foreach_pending_user().
*
* Example:
* @code
* void 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 +502,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

@ -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

@ -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,47 @@ static void guac_kubernetes_log(int level, const char* line) {
}
/**
* Synchronize the connection state for the given pending user.
*
* @param user
* The pending user whose connection state should be synced.
*
* @param data
* Unused.
*
* @return
* Always NULL.
*/
static void* guac_kubernetes_sync_pending_user(guac_user* user, void* data) {
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
guac_terminal_dup(kubernetes_client->term, user, user->socket);
guac_kubernetes_send_current_argv(user, kubernetes_client);
guac_socket_flush(user->socket);
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.
*/
static void guac_kubernetes_join_pending_handler(guac_client* client) {
/* Synchronize each user one at a time */
guac_client_foreach_pending_user(
client, guac_kubernetes_sync_pending_user, NULL);
}
int guac_client_init(guac_client* client) {
/* Ensure reference to main guac_client remains available in all
@ -96,6 +138,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

@ -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,53 @@ static int is_writable_directory(const char* path) {
}
/**
* Synchronize the connection state for the given pending user.
*
* @param user
* The pending user whose connection state should be synced.
*
* @param data
* Unused.
*
* @return
* Always NULL.
*/
static void* guac_rdp_sync_pending_user(guac_user* user, void* data) {
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
/* 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);
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.
*/
static void guac_rdp_join_pending_handler(guac_client* client) {
/* Synchronize each user one at a time */
guac_client_foreach_pending_user(
client, guac_rdp_sync_pending_user, NULL);
}
int guac_client_init(guac_client* client, int argc, char** argv) {
/* Automatically set HOME environment variable if unset (FreeRDP's
@ -164,6 +212,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

@ -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

@ -34,6 +34,47 @@
#include <guacamole/argv.h>
#include <guacamole/client.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
/**
* Synchronize the connection state for the given pending user.
*
* @param user
* The pending user whose connection state should be synced.
*
* @param data
* Unused.
*
* @return
* Always NULL.
*/
static void* guac_ssh_sync_pending_user(guac_user* user, void* data) {
guac_client* client = user->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
guac_terminal_dup(ssh_client->term, user, user->socket);
guac_ssh_send_current_argv(user, ssh_client);
guac_socket_flush(user->socket);
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.
*/
static void guac_ssh_join_pending_handler(guac_client* client) {
/* Synchronize each user one at a time */
guac_client_foreach_pending_user(
client, guac_ssh_sync_pending_user, NULL);
}
int guac_client_init(guac_client* client) {
@ -46,6 +87,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

@ -34,6 +34,47 @@
#include <guacamole/argv.h>
#include <guacamole/client.h>
#include <guacamole/recording.h>
#include <guacamole/socket.h>
/**
* Synchronize the connection state for the given pending user.
*
* @param user
* The pending user whose connection state should be synced.
*
* @param data
* Unused.
*
* @return
* Always NULL.
*/
static void* guac_telnet_sync_pending_user(guac_user* user, void* data) {
guac_client* client = user->client;
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_terminal_dup(telnet_client->term, user, user->socket);
guac_telnet_send_current_argv(user, telnet_client);
guac_socket_flush(user->socket);
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.
*/
static void guac_telnet_join_pending_handler(guac_client* client) {
/* Synchronize each user one at a time */
guac_client_foreach_pending_user(
client, guac_telnet_sync_pending_user, NULL);
}
int guac_client_init(guac_client* client) {
@ -51,6 +92,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

@ -40,6 +40,51 @@
#include <stdlib.h>
#include <string.h>
/**
* Synchronize the connection state for the given pending user.
*
* @param user
* The pending user whose connection state should be synced.
*
* @param data
* Unused.
*
* @return
* Always NULL.
*/
static void* guac_vnc_sync_pending_user(guac_user* user, void* data) {
guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data;
#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);
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.
*/
static void guac_vnc_join_pending_handler(guac_client* client) {
/* Synchronize each user one at a time */
guac_client_foreach_pending_user(
client, guac_vnc_sync_pending_user, NULL);
}
int guac_client_init(guac_client* client) {
/* Set client args */
@ -59,6 +104,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;