cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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