cscg24-guacamole

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

recording.c (7420B)


      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 "guacamole/mem.h"
     21#include "guacamole/client.h"
     22#include "guacamole/protocol.h"
     23#include "guacamole/recording.h"
     24#include "guacamole/socket.h"
     25#include "guacamole/timestamp.h"
     26
     27#ifdef __MINGW32__
     28#include <direct.h>
     29#endif
     30
     31#include <sys/stat.h>
     32#include <sys/types.h>
     33#include <errno.h>
     34#include <fcntl.h>
     35#include <stdlib.h>
     36#include <stdio.h>
     37#include <string.h>
     38#include <unistd.h>
     39
     40/**
     41 * Attempts to open a new recording within the given path and having the given
     42 * name. If such a file already exists, sequential numeric suffixes (.1, .2,
     43 * .3, etc.) are appended until a filename is found which does not exist (or
     44 * until the maximum number of numeric suffixes has been tried). If the file
     45 * absolutely cannot be opened due to an error, -1 is returned and errno is set
     46 * appropriately.
     47 *
     48 * @param path
     49 *     The full path to the directory in which the data file should be created.
     50 *
     51 * @param name
     52 *     The name of the data file which should be crated within the given path.
     53 *
     54 * @param basename
     55 *     A buffer in which the path, a path separator, the filename, any
     56 *     necessary suffix, and a NULL terminator will be stored. If insufficient
     57 *     space is available, -1 will be returned, and errno will be set to
     58 *     ENAMETOOLONG.
     59 *
     60 * @param basename_size
     61 *     The number of bytes available within the provided basename buffer.
     62 *
     63 * @return
     64 *     The file descriptor of the open data file if open succeeded, or -1 on
     65 *     failure.
     66 */
     67static int guac_recording_open(const char* path,
     68        const char* name, char* basename, int basename_size) {
     69
     70    int i;
     71
     72    /* Concatenate path and name (separated by a single slash) */
     73    int basename_length = snprintf(basename,
     74            basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH,
     75            "%s/%s", path, name);
     76
     77    /* Abort if maximum length reached */
     78    if (basename_length ==
     79            basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH) {
     80        errno = ENAMETOOLONG;
     81        return -1;
     82    }
     83
     84    /* Attempt to open recording */
     85    int fd = open(basename,
     86            O_CREAT | O_EXCL | O_WRONLY,
     87            S_IRUSR | S_IWUSR | S_IRGRP);
     88
     89    /* Continuously retry with alternate names on failure */
     90    if (fd == -1) {
     91
     92        /* Prepare basename for additional suffix */
     93        basename[basename_length] = '.';
     94        char* suffix = &(basename[basename_length + 1]);
     95
     96        /* Continue retrying alternative suffixes if file already exists */
     97        for (i = 1; fd == -1 && errno == EEXIST
     98                && i <= GUAC_COMMON_RECORDING_MAX_SUFFIX; i++) {
     99
    100            /* Append new suffix */
    101            sprintf(suffix, "%i", i);
    102
    103            /* Retry with newly-suffixed filename */
    104            fd = open(basename,
    105                    O_CREAT | O_EXCL | O_WRONLY,
    106                    S_IRUSR | S_IWUSR | S_IRGRP);
    107
    108        }
    109
    110        /* Abort if we've run out of filenames */
    111        if (fd == -1)
    112            return -1;
    113
    114    } /* end if open succeeded */
    115
    116/* Explicit file locks are required only on POSIX platforms */
    117#ifndef __MINGW32__
    118    /* Lock entire output file for writing by the current process */
    119    struct flock file_lock = {
    120        .l_type   = F_WRLCK,
    121        .l_whence = SEEK_SET,
    122        .l_start  = 0,
    123        .l_len    = 0,
    124        .l_pid    = getpid()
    125    };
    126
    127    /* Abort if file cannot be locked for reading */
    128    if (fcntl(fd, F_SETLK, &file_lock) == -1) {
    129        close(fd);
    130        return -1;
    131    }
    132#endif
    133
    134    return fd;
    135
    136}
    137
    138guac_recording* guac_recording_create(guac_client* client,
    139        const char* path, const char* name, int create_path,
    140        int include_output, int include_mouse, int include_touch,
    141        int include_keys) {
    142
    143    char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];
    144
    145    /* Create path if it does not exist, fail if impossible */
    146#ifndef __MINGW32__
    147    if (create_path && mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP)
    148            && errno != EEXIST) {
    149#else
    150    if (create_path && _mkdir(path) && errno != EEXIST) {
    151#endif
    152        guac_client_log(client, GUAC_LOG_ERROR,
    153                "Creation of recording failed: %s", strerror(errno));
    154        return NULL;
    155    }
    156
    157    /* Attempt to open recording file */
    158    int fd = guac_recording_open(path, name, filename, sizeof(filename));
    159    if (fd == -1) {
    160        guac_client_log(client, GUAC_LOG_ERROR,
    161                "Creation of recording failed: %s", strerror(errno));
    162        return NULL;
    163    }
    164
    165    /* Create recording structure with reference to underlying socket */
    166    guac_recording* recording = guac_mem_alloc(sizeof(guac_recording));
    167    recording->socket = guac_socket_open(fd);
    168    recording->include_output = include_output;
    169    recording->include_mouse = include_mouse;
    170    recording->include_touch = include_touch;
    171    recording->include_keys = include_keys;
    172
    173    /* Replace client socket with wrapped recording socket only if including
    174     * output within the recording */
    175    if (include_output)
    176        client->socket = guac_socket_tee(client->socket, recording->socket);
    177
    178    /* Recording creation succeeded */
    179    guac_client_log(client, GUAC_LOG_INFO,
    180            "Recording of session will be saved to \"%s\".",
    181            filename);
    182
    183    return recording;
    184
    185}
    186
    187void guac_recording_free(guac_recording* recording) {
    188
    189    /* If not including broadcast output, the output socket is not associated
    190     * with the client, and must be freed manually */
    191    if (!recording->include_output)
    192        guac_socket_free(recording->socket);
    193
    194    /* Free recording itself */
    195    guac_mem_free(recording);
    196
    197}
    198
    199void guac_recording_report_mouse(guac_recording* recording,
    200        int x, int y, int button_mask) {
    201
    202    /* Report mouse location only if recording should contain mouse events */
    203    if (recording->include_mouse)
    204        guac_protocol_send_mouse(recording->socket, x, y, button_mask,
    205                guac_timestamp_current());
    206
    207}
    208
    209void guac_recording_report_touch(guac_recording* recording,
    210        int id, int x, int y, int x_radius, int y_radius,
    211        double angle, double force) {
    212
    213    /* Report touches only if recording should contain touch events */
    214    if (recording->include_touch)
    215        guac_protocol_send_touch(recording->socket, id, x, y,
    216                x_radius, y_radius, angle, force, guac_timestamp_current());
    217
    218}
    219
    220void guac_recording_report_key(guac_recording* recording,
    221        int keysym, int pressed) {
    222
    223    /* Report key state only if recording should contain key events */
    224    if (recording->include_keys)
    225        guac_protocol_send_key(recording->socket, keysym, pressed,
    226                guac_timestamp_current());
    227
    228}
    229