GUACAMOLE-1846: Synchronize new users with the connection state in batches.
This commit is contained in:
parent
1f2ecdf694
commit
2cb75e8618
@ -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 \
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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
|
||||
|
||||
254
src/libguac/reentrant-rwlock.c
Normal file
254
src/libguac/reentrant-rwlock.c
Normal 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;
|
||||
|
||||
}
|
||||
144
src/libguac/reentrant-rwlock.h
Normal file
144
src/libguac/reentrant-rwlock.h
Normal 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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user