guacamole-spice-protocol/src/libguac/socket-broadcast.c

378 lines
11 KiB
C

/*
* 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 "config.h"
#include "guacamole/client.h"
#include "guacamole/error.h"
#include "guacamole/socket.h"
#include "guacamole/user.h"
#include <pthread.h>
#include <stdlib.h>
/**
* Data associated with an open socket which writes to all connected users of
* a particular guac_client.
*/
typedef struct guac_socket_broadcast_data {
/**
* The guac_client whose connected users should receive all instructions
* written to this socket.
*/
guac_client* client;
/**
* Lock which is acquired when an instruction is being written, and
* released when the instruction is finished being written.
*/
pthread_mutex_t socket_lock;
} guac_socket_broadcast_data;
/**
* Single chunk of data, to be broadcast to all users.
*/
typedef struct __write_chunk {
/**
* The buffer to write.
*/
const void* buffer;
/**
* The number of bytes in the buffer.
*/
size_t length;
} __write_chunk;
/**
* Callback which handles read requests on the broadcast socket. This callback
* always fails, as the broadcast socket is write-only; it cannot be read.
*
* @param socket
* The broadcast socket to read from.
*
* @param buf
* The buffer into which data should be read.
*
* @param count
* The number of bytes to attempt to read.
*
* @return
* The number of bytes read, or -1 if an error occurs. This implementation
* always returns -1, as the broadcast socket is write-only and cannot be
* read.
*/
static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
void* buf, size_t count) {
/* Broadcast socket reads are not allowed */
return -1;
}
/**
* Callback invoked by guac_client_foreach_user() 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().
*
* @param user
* The user that the chunk of data should be written to.
*
* @param data
* A pointer to a __write_chunk which describes the data to be written.
*
* @return
* Always NULL.
*/
static void* __write_chunk_callback(guac_user* user, void* data) {
__write_chunk* chunk = (__write_chunk*) data;
/* Attempt write, disconnect on failure */
if (guac_socket_write(user->socket, chunk->buffer, chunk->length))
guac_user_stop(user);
return NULL;
}
/**
* Socket write handler which operates on each of the sockets of all connected
* users. This write handler will always succeed, but any failing user-specific
* writes will invoke guac_user_stop() on the failing user.
*
* @param socket
* The socket to which the given data must be written.
*
* @param buf
* The buffer containing the data to write.
*
* @param count
* The number of bytes to attempt to write from the given buffer.
*
* @return
* The number of bytes written, or -1 if an error occurs. This handler will
* always succeed, and thus will always return the exact number of bytes
* specified by count.
*/
static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
const void* buf, size_t count) {
guac_socket_broadcast_data* data =
(guac_socket_broadcast_data*) socket->data;
/* Build chunk */
__write_chunk chunk;
chunk.buffer = buf;
chunk.length = count;
/* Broadcast chunk to all users */
guac_client_foreach_user(data->client, __write_chunk_callback, &chunk);
return count;
}
/**
* Callback which is invoked by guac_client_foreach_user() 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().
*
* @param user
* The user whose socket should be flushed.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* by this callback, and should be left as NULL.
*
* @return
* Always NULL.
*/
static void* __flush_callback(guac_user* user, void* data) {
/* Attempt flush, disconnect on failure */
if (guac_socket_flush(user->socket))
guac_user_stop(user);
return NULL;
}
/**
* Socket flush handler which operates on each of the sockets of all connected
* users. This flush handler will always succeed, but any failing user-specific
* flush will invoke guac_user_stop() on the failing user.
*
* @param socket
* The broadcast socket to flush.
*
* @return
* Zero if the flush operation succeeds, non-zero if the operation fails.
* This handler will always succeed, and thus will always return zero.
*/
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);
return 0;
}
/**
* Callback which is invoked by guac_client_foreach_user() to lock the given
* user's socket in preparation for the beginning of a Guacamole protocol
* instruction.
*
* @param user
* The user whose socket should be locked.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* by this callback, and should be left as NULL.
*
* @return
* Always NULL.
*/
static void* __lock_callback(guac_user* user, void* data) {
/* Lock socket */
guac_socket_instruction_begin(user->socket);
return NULL;
}
/**
* Socket lock handler which acquires the socket locks of all connected users.
* Socket-level locks are acquired in preparation for the beginning of a new
* Guacamole instruction to ensure that parallel writes are only interleaved at
* instruction boundaries.
*
* @param socket
* The broadcast socket to lock.
*/
static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
guac_socket_broadcast_data* data =
(guac_socket_broadcast_data*) socket->data;
/* 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);
}
/**
* Callback which is invoked by guac_client_foreach_user() 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
* by this callback, and should be left as NULL.
*
* @return
* Always NULL.
*/
static void* __unlock_callback(guac_user* user, void* data) {
/* Unlock socket */
guac_socket_instruction_end(user->socket);
return NULL;
}
/**
* Socket unlock handler which releases the socket locks of all connected users.
* Socket-level locks are released after a Guacamole instruction has finished
* being written.
*
* @param socket
* The broadcast socket to unlock.
*/
static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
guac_socket_broadcast_data* data =
(guac_socket_broadcast_data*) socket->data;
/* Unlock sockets of all users */
guac_client_foreach_user(data->client, __unlock_callback, NULL);
/* Relinquish exclusive access to socket */
pthread_mutex_unlock(&(data->socket_lock));
}
/**
* Callback which handles select operations on the broadcast socket, waiting
* for data to become available such that the next read operation will not
* block. This callback always fails, as the broadcast socket is write-only; it
* cannot be read.
*
* @param socket
* The broadcast socket to wait for.
*
* @param usec_timeout
* The maximum amount of time to wait for data, in microseconds, or -1 to
* potentially wait forever.
*
* @return
* A positive value on success, zero if the timeout elapsed and no data is
* available, or a negative value if an error occurs. This implementation
* always returns -1, as the broadcast socket is write-only and cannot be
* read.
*/
static int __guac_socket_broadcast_select_handler(guac_socket* socket,
int usec_timeout) {
/* Selecting the broadcast socket is not possible */
return -1;
}
/**
* Frees all implementation-specific data associated with the given socket, but
* not the socket object itself.
*
* @param socket
* The guac_socket whose associated data should be freed.
*
* @return
* Zero if the data was successfully freed, non-zero otherwise. This
* implementation always succeeds, and will always return zero.
*/
static int __guac_socket_broadcast_free_handler(guac_socket* socket) {
guac_socket_broadcast_data* data =
(guac_socket_broadcast_data*) socket->data;
/* Destroy locks */
pthread_mutex_destroy(&(data->socket_lock));
free(data);
return 0;
}
guac_socket* guac_socket_broadcast(guac_client* client) {
pthread_mutexattr_t lock_attributes;
/* Allocate socket and associated data */
guac_socket* socket = guac_socket_alloc();
guac_socket_broadcast_data* data =
malloc(sizeof(guac_socket_broadcast_data));
/* Store client as socket data */
data->client = client;
socket->data = data;
pthread_mutexattr_init(&lock_attributes);
pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
/* 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;
socket->select_handler = __guac_socket_broadcast_select_handler;
socket->flush_handler = __guac_socket_broadcast_flush_handler;
socket->lock_handler = __guac_socket_broadcast_lock_handler;
socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
socket->free_handler = __guac_socket_broadcast_free_handler;
return socket;
}