socket-broadcast.c (12411B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "guacamole/mem.h" 23#include "guacamole/client.h" 24#include "guacamole/error.h" 25#include "guacamole/socket.h" 26#include "guacamole/user.h" 27 28#include <pthread.h> 29#include <stdlib.h> 30 31/** 32 * A function that will broadcast arbitrary data to a subset of users for 33 * the provided client, using the provided user callback for any user-specific 34 * operations. 35 * 36 * @param client 37 * The guac_client associated with the users to broadcast to. 38 * 39 * @param callback 40 * A callback that should be invoked with each broadcasted user. 41 * 42 * @param data 43 * Arbitrary data that may be used to broadcast to the subset of users. 44 */ 45typedef void guac_socket_broadcast_handler( 46 guac_client* client, guac_user_callback* callback, void* data); 47 48/** 49 * Data associated with an open socket which writes to a subset of connected 50 * users of a particular guac_client. 51 */ 52typedef struct guac_socket_broadcast_data { 53 54 /** 55 * The guac_client whose connected users should receive all instructions 56 * written to this socket. 57 */ 58 guac_client* client; 59 60 /** 61 * Lock which is acquired when an instruction is being written, and 62 * released when the instruction is finished being written. 63 */ 64 pthread_mutex_t socket_lock; 65 66 /** 67 * The function to broadcast 68 */ 69 guac_socket_broadcast_handler* broadcast_handler; 70 71} guac_socket_broadcast_data; 72 73/** 74 * Single chunk of data, to be broadcast to all users. 75 */ 76typedef struct __write_chunk { 77 78 /** 79 * The buffer to write. 80 */ 81 const void* buffer; 82 83 /** 84 * The number of bytes in the buffer. 85 */ 86 size_t length; 87 88} __write_chunk; 89 90/** 91 * Callback which handles read requests on the broadcast socket. This callback 92 * always fails, as the broadcast socket is write-only; it cannot be read. 93 * 94 * @param socket 95 * The broadcast socket to read from. 96 * 97 * @param buf 98 * The buffer into which data should be read. 99 * 100 * @param count 101 * The number of bytes to attempt to read. 102 * 103 * @return 104 * The number of bytes read, or -1 if an error occurs. This implementation 105 * always returns -1, as the broadcast socket is write-only and cannot be 106 * read. 107 */ 108static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket, 109 void* buf, size_t count) { 110 111 /* Broadcast socket reads are not allowed */ 112 return -1; 113 114} 115 116/** 117 * Callback invoked by the broadcast handler which write a given chunk of 118 * data to that user's socket. If the write attempt fails, the user is 119 * signalled to stop with guac_user_stop(). 120 * 121 * @param user 122 * The user that the chunk of data should be written to. 123 * 124 * @param data 125 * A pointer to a __write_chunk which describes the data to be written. 126 * 127 * @return 128 * Always NULL. 129 */ 130static void* __write_chunk_callback(guac_user* user, void* data) { 131 132 __write_chunk* chunk = (__write_chunk*) data; 133 134 /* Attempt write, disconnect on failure */ 135 if (guac_socket_write(user->socket, chunk->buffer, chunk->length)) 136 guac_user_stop(user); 137 138 return NULL; 139 140} 141 142/** 143 * Socket write handler which operates on each of the sockets of all connected 144 * users. This write handler will always succeed, but any failing user-specific 145 * writes will invoke guac_user_stop() on the failing user. 146 * 147 * @param socket 148 * The socket to which the given data must be written. 149 * 150 * @param buf 151 * The buffer containing the data to write. 152 * 153 * @param count 154 * The number of bytes to attempt to write from the given buffer. 155 * 156 * @return 157 * The number of bytes written, or -1 if an error occurs. This handler will 158 * always succeed, and thus will always return the exact number of bytes 159 * specified by count. 160 */ 161static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket, 162 const void* buf, size_t count) { 163 164 guac_socket_broadcast_data* data = 165 (guac_socket_broadcast_data*) socket->data; 166 167 /* Build chunk */ 168 __write_chunk chunk; 169 chunk.buffer = buf; 170 chunk.length = count; 171 172 /* Broadcast chunk to the users */ 173 data->broadcast_handler(data->client, __write_chunk_callback, &chunk); 174 175 return count; 176 177} 178 179/** 180 * Callback which is invoked by the broadcast handler to flush all 181 * pending data on the given user's socket. If an error occurs while flushing 182 * a user's socket, that user is signalled to stop with guac_user_stop(). 183 * 184 * @param user 185 * The user whose socket should be flushed. 186 * 187 * @param data 188 * Arbitrary data passed to the broadcast handler. This is not needed 189 * by this callback, and should be left as NULL. 190 * 191 * @return 192 * Always NULL. 193 */ 194static void* __flush_callback(guac_user* user, void* data) { 195 196 /* Attempt flush, disconnect on failure */ 197 if (guac_socket_flush(user->socket)) 198 guac_user_stop(user); 199 200 return NULL; 201 202} 203 204/** 205 * Socket flush handler which operates on each of the sockets of all connected 206 * users. This flush handler will always succeed, but any failing user-specific 207 * flush will invoke guac_user_stop() on the failing user. 208 * 209 * @param socket 210 * The broadcast socket to flush. 211 * 212 * @return 213 * Zero if the flush operation succeeds, non-zero if the operation fails. 214 * This handler will always succeed, and thus will always return zero. 215 */ 216static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) { 217 218 guac_socket_broadcast_data* data = 219 (guac_socket_broadcast_data*) socket->data; 220 221 /* Flush the users */ 222 data->broadcast_handler(data->client, __flush_callback, NULL); 223 224 return 0; 225 226} 227 228/** 229 * Callback which is invoked by the broadcast handler to lock the given 230 * user's socket in preparation for the beginning of a Guacamole protocol 231 * instruction. 232 * 233 * @param user 234 * The user whose socket should be locked. 235 * 236 * @param data 237 * Arbitrary data passed to the broadcast handler. This is not needed 238 * by this callback, and should be left as NULL. 239 * 240 * @return 241 * Always NULL. 242 */ 243static void* __lock_callback(guac_user* user, void* data) { 244 245 /* Lock socket */ 246 guac_socket_instruction_begin(user->socket); 247 248 return NULL; 249 250} 251 252/** 253 * Socket lock handler which acquires the socket locks of all connected users. 254 * Socket-level locks are acquired in preparation for the beginning of a new 255 * Guacamole instruction to ensure that parallel writes are only interleaved at 256 * instruction boundaries. 257 * 258 * @param socket 259 * The broadcast socket to lock. 260 */ 261static void __guac_socket_broadcast_lock_handler(guac_socket* socket) { 262 263 guac_socket_broadcast_data* data = 264 (guac_socket_broadcast_data*) socket->data; 265 266 /* Acquire exclusive access to socket */ 267 pthread_mutex_lock(&(data->socket_lock)); 268 269 /* Lock sockets of the users */ 270 data->broadcast_handler(data->client, __lock_callback, NULL); 271 272} 273 274/** 275 * Callback which is invoked by the broadcast handler to unlock the given 276 * user's socket at the end of a Guacamole protocol instruction. 277 * 278 * @param user 279 * The user whose socket should be unlocked. 280 * 281 * @param data 282 * Arbitrary data passed to the broadcast handler. This is not needed 283 * by this callback, and should be left as NULL. 284 * 285 * @return 286 * Always NULL. 287 */ 288static void* __unlock_callback(guac_user* user, void* data) { 289 290 /* Unlock socket */ 291 guac_socket_instruction_end(user->socket); 292 293 return NULL; 294 295} 296 297/** 298 * Socket unlock handler which releases the socket locks of all connected users. 299 * Socket-level locks are released after a Guacamole instruction has finished 300 * being written. 301 * 302 * @param socket 303 * The broadcast socket to unlock. 304 */ 305static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) { 306 307 guac_socket_broadcast_data* data = 308 (guac_socket_broadcast_data*) socket->data; 309 310 /* Unlock sockets of all users */ 311 data->broadcast_handler(data->client, __unlock_callback, NULL); 312 313 /* Relinquish exclusive access to socket */ 314 pthread_mutex_unlock(&(data->socket_lock)); 315 316} 317 318/** 319 * Callback which handles select operations on the broadcast socket, waiting 320 * for data to become available such that the next read operation will not 321 * block. This callback always fails, as the broadcast socket is write-only; it 322 * cannot be read. 323 * 324 * @param socket 325 * The broadcast socket to wait for. 326 * 327 * @param usec_timeout 328 * The maximum amount of time to wait for data, in microseconds, or -1 to 329 * potentially wait forever. 330 * 331 * @return 332 * A positive value on success, zero if the timeout elapsed and no data is 333 * available, or a negative value if an error occurs. This implementation 334 * always returns -1, as the broadcast socket is write-only and cannot be 335 * read. 336 */ 337static int __guac_socket_broadcast_select_handler(guac_socket* socket, 338 int usec_timeout) { 339 340 /* Selecting the broadcast socket is not possible */ 341 return -1; 342 343} 344 345/** 346 * Frees all implementation-specific data associated with the given socket, but 347 * not the socket object itself. 348 * 349 * @param socket 350 * The guac_socket whose associated data should be freed. 351 * 352 * @return 353 * Zero if the data was successfully freed, non-zero otherwise. This 354 * implementation always succeeds, and will always return zero. 355 */ 356static int __guac_socket_broadcast_free_handler(guac_socket* socket) { 357 358 guac_socket_broadcast_data* data = 359 (guac_socket_broadcast_data*) socket->data; 360 361 /* Destroy locks */ 362 pthread_mutex_destroy(&(data->socket_lock)); 363 364 guac_mem_free(data); 365 return 0; 366 367} 368 369/** 370 * Construct and return a socket that will broadcast to the users given by 371 * by the provided broadcast handler. 372 * 373 * @param client 374 * The client who's users are being broadcast to. 375 * 376 * @param broadcast_handler 377 * The handler that will peform the broadcast against a subset of users 378 * of the provided client. 379 * 380 * @return 381 * The newly constructed broadcast socket 382 */ 383static guac_socket* __guac_socket_init( 384 guac_client* client, guac_socket_broadcast_handler* broadcast_handler) { 385 386 pthread_mutexattr_t lock_attributes; 387 388 /* Allocate socket and associated data */ 389 guac_socket* socket = guac_socket_alloc(); 390 guac_socket_broadcast_data* data = 391 guac_mem_alloc(sizeof(guac_socket_broadcast_data)); 392 393 /* Set the provided broadcast handler */ 394 data->broadcast_handler = broadcast_handler; 395 396 /* Store client as socket data */ 397 data->client = client; 398 socket->data = data; 399 400 pthread_mutexattr_init(&lock_attributes); 401 pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); 402 403 /* Init lock */ 404 pthread_mutex_init(&(data->socket_lock), &lock_attributes); 405 406 /* Set read/write handlers */ 407 socket->read_handler = __guac_socket_broadcast_read_handler; 408 socket->write_handler = __guac_socket_broadcast_write_handler; 409 socket->select_handler = __guac_socket_broadcast_select_handler; 410 socket->flush_handler = __guac_socket_broadcast_flush_handler; 411 socket->lock_handler = __guac_socket_broadcast_lock_handler; 412 socket->unlock_handler = __guac_socket_broadcast_unlock_handler; 413 socket->free_handler = __guac_socket_broadcast_free_handler; 414 415 return socket; 416 417} 418 419guac_socket* guac_socket_broadcast(guac_client* client) { 420 421 /* Broadcast to all connected non-pending users*/ 422 return __guac_socket_init(client, guac_client_foreach_user); 423 424} 425 426guac_socket* guac_socket_broadcast_pending(guac_client* client) { 427 428 /* Broadcast to all connected pending users*/ 429 return __guac_socket_init(client, guac_client_foreach_pending_user); 430 431} 432