| /* |
| * 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 "client.h" |
| #include "error.h" |
| #include "socket.h" |
| #include "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; |
| |
| } |
| |