socket-fd.c (12396B)
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/error.h" 24#include "guacamole/socket.h" 25#include "wait-fd.h" 26 27#include <pthread.h> 28#include <stddef.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#include <sys/time.h> 33#include <unistd.h> 34 35#ifdef ENABLE_WINSOCK 36#include <winsock2.h> 37#endif 38 39/** 40 * Data associated with an open socket which writes to a file descriptor. 41 */ 42typedef struct guac_socket_fd_data { 43 44 /** 45 * The associated file descriptor; 46 */ 47 int fd; 48 49 /** 50 * The number of bytes currently in the main write buffer. 51 */ 52 int written; 53 54 /** 55 * The main write buffer. Bytes written go here before being flushed 56 * to the open file descriptor. 57 */ 58 char out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE]; 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 * Lock which protects access to the internal buffer of this socket, 68 * guaranteeing atomicity of writes and flushes. 69 */ 70 pthread_mutex_t buffer_lock; 71 72} guac_socket_fd_data; 73 74/** 75 * Writes the entire contents of the given buffer to the file descriptor 76 * associated with the given socket, retrying as necessary until the whole 77 * buffer is written, and aborting if an error occurs. 78 * 79 * @param socket 80 * The guac_socket associated with the file descriptor to which the given 81 * buffer should be written. 82 * 83 * @param buf 84 * The buffer of data to write to the given guac_socket. 85 * 86 * @param count 87 * The number of bytes within the given buffer. 88 * 89 * @return 90 * The number of bytes written, which will be exactly the size of the given 91 * buffer, or a negative value if an error occurs. 92 */ 93ssize_t guac_socket_fd_write(guac_socket* socket, 94 const void* buf, size_t count) { 95 96 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 97 const char* buffer = buf; 98 99 /* Write until completely written */ 100 while (count > 0) { 101 102 int retval; 103 104#ifdef ENABLE_WINSOCK 105 /* WSA only works with send() */ 106 retval = send(data->fd, buffer, count, 0); 107#else 108 /* Use write() for all other platforms */ 109 retval = write(data->fd, buffer, count); 110#endif 111 112 /* Record errors in guac_error */ 113 if (retval < 0) { 114 guac_error = GUAC_STATUS_SEE_ERRNO; 115 guac_error_message = "Error writing data to socket"; 116 return retval; 117 } 118 119 /* Advance buffer to next chunk */ 120 buffer += retval; 121 count -= retval; 122 123 } 124 125 return 0; 126 127} 128 129/** 130 * Attempts to read from the underlying file descriptor of the given 131 * guac_socket, populating the given buffer. 132 * 133 * @param socket 134 * The guac_socket being read from. 135 * 136 * @param buf 137 * The arbitrary buffer which we must populate with data. 138 * 139 * @param count 140 * The maximum number of bytes to read into the buffer. 141 * 142 * @return 143 * The number of bytes read, or -1 if an error occurs. 144 */ 145static ssize_t guac_socket_fd_read_handler(guac_socket* socket, 146 void* buf, size_t count) { 147 148 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 149 150 int retval; 151 152#ifdef ENABLE_WINSOCK 153 /* Winsock only works with recv() */ 154 retval = recv(data->fd, buf, count, 0); 155#else 156 /* Use read() for all other platforms */ 157 retval = read(data->fd, buf, count); 158#endif 159 160 /* Record errors in guac_error */ 161 if (retval < 0) { 162 guac_error = GUAC_STATUS_SEE_ERRNO; 163 guac_error_message = "Error reading data from socket"; 164 } 165 166 return retval; 167 168} 169 170/** 171 * Flushes the contents of the output buffer of the given socket immediately, 172 * without first locking access to the output buffer. This function must ONLY 173 * be called if the buffer lock has already been acquired. 174 * 175 * @param socket 176 * The guac_socket to flush. 177 * 178 * @return 179 * Zero if the flush operation was successful, non-zero otherwise. 180 */ 181static ssize_t guac_socket_fd_flush(guac_socket* socket) { 182 183 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 184 185 /* Flush remaining bytes in buffer */ 186 if (data->written > 0) { 187 188 /* Write ALL bytes in buffer immediately */ 189 if (guac_socket_fd_write(socket, data->out_buf, data->written)) 190 return 1; 191 192 data->written = 0; 193 } 194 195 return 0; 196 197} 198 199/** 200 * Flushes the internal buffer of the given guac_socket, writing all data 201 * to the underlying file descriptor. 202 * 203 * @param socket 204 * The guac_socket to flush. 205 * 206 * @return 207 * Zero if the flush operation was successful, non-zero otherwise. 208 */ 209static ssize_t guac_socket_fd_flush_handler(guac_socket* socket) { 210 211 int retval; 212 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 213 214 /* Acquire exclusive access to buffer */ 215 pthread_mutex_lock(&(data->buffer_lock)); 216 217 /* Flush contents of buffer */ 218 retval = guac_socket_fd_flush(socket); 219 220 /* Relinquish exclusive access to buffer */ 221 pthread_mutex_unlock(&(data->buffer_lock)); 222 223 return retval; 224 225} 226 227/** 228 * Writes the contents of the buffer to the output buffer of the given socket, 229 * flushing the output buffer as necessary, without first locking access to the 230 * output buffer. This function must ONLY be called if the buffer lock has 231 * already been acquired. 232 * 233 * @param socket 234 * The guac_socket to write the given buffer to. 235 * 236 * @param buf 237 * The buffer to write to the given socket. 238 * 239 * @param count 240 * The number of bytes in the given buffer. 241 * 242 * @return 243 * The number of bytes written, or a negative value if an error occurs 244 * during write. 245 */ 246static ssize_t guac_socket_fd_write_buffered(guac_socket* socket, 247 const void* buf, size_t count) { 248 249 size_t original_count = count; 250 const char* current = buf; 251 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 252 253 /* Append to buffer, flush if necessary */ 254 while (count > 0) { 255 256 int chunk_size; 257 int remaining = sizeof(data->out_buf) - data->written; 258 259 /* If no space left in buffer, flush and retry */ 260 if (remaining == 0) { 261 262 /* Abort if error occurs during flush */ 263 if (guac_socket_fd_flush(socket)) 264 return -1; 265 266 /* Retry buffer append */ 267 continue; 268 269 } 270 271 /* Calculate size of chunk to be written to buffer */ 272 chunk_size = count; 273 if (chunk_size > remaining) 274 chunk_size = remaining; 275 276 /* Update output buffer */ 277 memcpy(data->out_buf + data->written, current, chunk_size); 278 data->written += chunk_size; 279 280 /* Update provided buffer */ 281 current += chunk_size; 282 count -= chunk_size; 283 284 } 285 286 /* All bytes have been written, possibly some to the internal buffer */ 287 return original_count; 288 289} 290 291/** 292 * Appends the provided data to the internal buffer for future writing. The 293 * actual write attempt will occur only upon flush, or when the internal buffer 294 * is full. 295 * 296 * @param socket 297 * The guac_socket being write to. 298 * 299 * @param buf 300 * The arbitrary buffer containing the data to be written. 301 * 302 * @param count 303 * The number of bytes contained within the buffer. 304 * 305 * @return 306 * The number of bytes written, or -1 if an error occurs. 307 */ 308static ssize_t guac_socket_fd_write_handler(guac_socket* socket, 309 const void* buf, size_t count) { 310 311 int retval; 312 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 313 314 /* Acquire exclusive access to buffer */ 315 pthread_mutex_lock(&(data->buffer_lock)); 316 317 /* Write provided data to buffer */ 318 retval = guac_socket_fd_write_buffered(socket, buf, count); 319 320 /* Relinquish exclusive access to buffer */ 321 pthread_mutex_unlock(&(data->buffer_lock)); 322 323 return retval; 324 325} 326 327/** 328 * Waits for data on the underlying file desriptor of the given socket to 329 * become available such that the next read operation will not block. 330 * 331 * @param socket 332 * The guac_socket to wait for. 333 * 334 * @param usec_timeout 335 * The maximum amount of time to wait for data, in microseconds, or -1 to 336 * potentially wait forever. 337 * 338 * @return 339 * A positive value on success, zero if the timeout elapsed and no data is 340 * available, or a negative value if an error occurs. 341 */ 342static int guac_socket_fd_select_handler(guac_socket* socket, 343 int usec_timeout) { 344 345 /* Wait for data on socket */ 346 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 347 int retval = guac_wait_for_fd(data->fd, usec_timeout); 348 349 /* Properly set guac_error */ 350 if (retval < 0) { 351 guac_error = GUAC_STATUS_SEE_ERRNO; 352 guac_error_message = "Error while waiting for data on socket"; 353 } 354 355 else if (retval == 0) { 356 guac_error = GUAC_STATUS_TIMEOUT; 357 guac_error_message = "Timeout while waiting for data on socket"; 358 } 359 360 return retval; 361 362} 363 364/** 365 * Frees all implementation-specific data associated with the given socket, but 366 * not the socket object itself. 367 * 368 * @param socket 369 * The guac_socket whose associated data should be freed. 370 * 371 * @return 372 * Zero if the data was successfully freed, non-zero otherwise. This 373 * implementation always succeeds, and will always return zero. 374 */ 375static int guac_socket_fd_free_handler(guac_socket* socket) { 376 377 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 378 379 /* Destroy locks */ 380 pthread_mutex_destroy(&(data->socket_lock)); 381 pthread_mutex_destroy(&(data->buffer_lock)); 382 383 /* Close file descriptor */ 384 close(data->fd); 385 386 guac_mem_free(data); 387 return 0; 388 389} 390 391/** 392 * Acquires exclusive access to the given socket. 393 * 394 * @param socket 395 * The guac_socket to which exclusive access is required. 396 */ 397static void guac_socket_fd_lock_handler(guac_socket* socket) { 398 399 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 400 401 /* Acquire exclusive access to socket */ 402 pthread_mutex_lock(&(data->socket_lock)); 403 404} 405 406/** 407 * Relinquishes exclusive access to the given socket. 408 * 409 * @param socket 410 * The guac_socket to which exclusive access is no longer required. 411 */ 412static void guac_socket_fd_unlock_handler(guac_socket* socket) { 413 414 guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; 415 416 /* Relinquish exclusive access to socket */ 417 pthread_mutex_unlock(&(data->socket_lock)); 418 419} 420 421guac_socket* guac_socket_open(int fd) { 422 423 pthread_mutexattr_t lock_attributes; 424 425 /* Allocate socket and associated data */ 426 guac_socket* socket = guac_socket_alloc(); 427 guac_socket_fd_data* data = guac_mem_alloc(sizeof(guac_socket_fd_data)); 428 429 /* Store file descriptor as socket data */ 430 data->fd = fd; 431 data->written = 0; 432 socket->data = data; 433 434 pthread_mutexattr_init(&lock_attributes); 435 pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); 436 437 /* Init locks */ 438 pthread_mutex_init(&(data->socket_lock), &lock_attributes); 439 pthread_mutex_init(&(data->buffer_lock), &lock_attributes); 440 441 /* Set read/write handlers */ 442 socket->read_handler = guac_socket_fd_read_handler; 443 socket->write_handler = guac_socket_fd_write_handler; 444 socket->select_handler = guac_socket_fd_select_handler; 445 socket->lock_handler = guac_socket_fd_lock_handler; 446 socket->unlock_handler = guac_socket_fd_unlock_handler; 447 socket->flush_handler = guac_socket_fd_flush_handler; 448 socket->free_handler = guac_socket_fd_free_handler; 449 450 return socket; 451 452} 453