cscg24-guacamole

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

typescript.c (7711B)


      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 "common/io.h"
     21#include "terminal/typescript.h"
     22
     23#include <guacamole/mem.h>
     24#include <guacamole/timestamp.h>
     25
     26#include <errno.h>
     27#include <stdlib.h>
     28#include <stdio.h>
     29#include <unistd.h>
     30
     31#include <sys/types.h>
     32#include <sys/stat.h>
     33#include <fcntl.h>
     34
     35/**
     36 * Attempts to open a new typescript data file within the given path and having
     37 * the given name. If such a file already exists, sequential numeric suffixes
     38 * (.1, .2, .3, etc.) are appended until a filename is found which does not
     39 * exist (or until the maximum number of numeric suffixes has been tried). If
     40 * the file absolutely cannot be opened due to an error, -1 is returned and
     41 * errno is set appropriately.
     42 *
     43 * @param path
     44 *     The full path to the directory in which the data file should be created.
     45 *
     46 * @param name
     47 *     The name of the data file which should be crated within the given path.
     48 *
     49 * @param basename
     50 *     A buffer in which the path, a path separator, the filename, any
     51 *     necessary suffix, and a NULL terminator will be stored. If insufficient
     52 *     space is available, -1 will be returned, and errno will be set to
     53 *     ENAMETOOLONG.
     54 *
     55 * @param basename_size
     56 *     The number of bytes available within the provided basename buffer.
     57 *
     58 * @return
     59 *     The file descriptor of the open data file if open succeeded, or -1 on
     60 *     failure.
     61 */
     62static int guac_terminal_typescript_open_data_file(const char* path,
     63        const char* name, char* basename, int basename_size) {
     64
     65    int i;
     66
     67    /* Concatenate path and name (separated by a single slash) */
     68    int basename_length = snprintf(basename,
     69            basename_size - GUAC_TERMINAL_TYPESCRIPT_MAX_SUFFIX_LENGTH,
     70            "%s/%s", path, name);
     71
     72    /* Abort if maximum length reached */
     73    if (basename_length ==
     74            basename_size - GUAC_TERMINAL_TYPESCRIPT_MAX_SUFFIX_LENGTH) {
     75        errno = ENAMETOOLONG;
     76        return -1;
     77    }
     78
     79    /* Attempt to open typescript data file */
     80    int data_fd = open(basename,
     81            O_CREAT | O_EXCL | O_WRONLY,
     82            S_IRUSR | S_IWUSR | S_IRGRP);
     83
     84    /* Continuously retry with alternate names on failure */
     85    if (data_fd == -1) {
     86
     87        /* Prepare basename for additional suffix */
     88        basename[basename_length] = '.';
     89        char* suffix = &(basename[basename_length + 1]);
     90
     91        /* Continue retrying alternative suffixes if file already exists */
     92        for (i = 1; data_fd == -1 && errno == EEXIST
     93                && i <= GUAC_TERMINAL_TYPESCRIPT_MAX_SUFFIX; i++) {
     94
     95            /* Append new suffix */
     96            sprintf(suffix, "%i", i);
     97
     98            /* Retry with newly-suffixed filename */
     99            data_fd = open(basename,
    100                    O_CREAT | O_EXCL | O_WRONLY,
    101                    S_IRUSR | S_IWUSR | S_IRGRP);
    102
    103        }
    104
    105    }
    106
    107    return data_fd;
    108
    109}
    110
    111guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path,
    112        const char* name, int create_path) {
    113
    114    /* Create path if it does not exist, fail if impossible */
    115    if (create_path && mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP)
    116            && errno != EEXIST)
    117        return NULL;
    118
    119    /* Allocate space for new typescript */
    120    guac_terminal_typescript* typescript =
    121        guac_mem_alloc(sizeof(guac_terminal_typescript));
    122
    123    /* Attempt to open typescript data file */
    124    typescript->data_fd = guac_terminal_typescript_open_data_file(
    125            path, name, typescript->data_filename,
    126            sizeof(typescript->data_filename)
    127                - sizeof(GUAC_TERMINAL_TYPESCRIPT_TIMING_SUFFIX));
    128    if (typescript->data_fd == -1) {
    129        guac_mem_free(typescript);
    130        return NULL;
    131    }
    132
    133    /* Append suffix to basename */
    134    if (snprintf(typescript->timing_filename, sizeof(typescript->timing_filename),
    135                "%s.%s", typescript->data_filename, GUAC_TERMINAL_TYPESCRIPT_TIMING_SUFFIX)
    136            >= sizeof(typescript->timing_filename)) {
    137        close(typescript->data_fd);
    138        guac_mem_free(typescript);
    139        return NULL;
    140    }
    141
    142    /* Attempt to open typescript timing file */
    143    typescript->timing_fd = open(typescript->timing_filename,
    144            O_CREAT | O_EXCL | O_WRONLY,
    145            S_IRUSR | S_IWUSR | S_IRGRP);
    146    if (typescript->timing_fd == -1) {
    147        close(typescript->data_fd);
    148        guac_mem_free(typescript);
    149        return NULL;
    150    }
    151
    152    /* Typescript starts out flushed */
    153    typescript->length = 0;
    154    typescript->last_flush = guac_timestamp_current();
    155
    156    /* Write header */
    157    guac_common_write(typescript->data_fd, GUAC_TERMINAL_TYPESCRIPT_HEADER,
    158            sizeof(GUAC_TERMINAL_TYPESCRIPT_HEADER) - 1);
    159
    160    return typescript;
    161
    162}
    163
    164void guac_terminal_typescript_write(guac_terminal_typescript* typescript,
    165        char c) {
    166
    167    /* Flush buffer if no space is available */
    168    if (typescript->length == sizeof(typescript->buffer))
    169        guac_terminal_typescript_flush(typescript);
    170
    171    /* Append single byte to buffer */
    172    typescript->buffer[typescript->length++] = c;
    173
    174}
    175
    176void guac_terminal_typescript_flush(guac_terminal_typescript* typescript) {
    177
    178    /* Do nothing if nothing to flush */
    179    if (typescript->length == 0)
    180        return;
    181
    182    /* Get timestamps of previous and current flush */
    183    guac_timestamp this_flush = guac_timestamp_current();
    184    guac_timestamp last_flush = typescript->last_flush;
    185
    186    /* Calculate time since last flush */
    187    int elapsed_time = this_flush - last_flush;
    188    if (elapsed_time > GUAC_TERMINAL_TYPESCRIPT_MAX_DELAY)
    189        elapsed_time = GUAC_TERMINAL_TYPESCRIPT_MAX_DELAY;
    190
    191    /* Produce single line of timestamp output */
    192    char timestamp_buffer[32];
    193    int timestamp_length = snprintf(timestamp_buffer, sizeof(timestamp_buffer),
    194            "%0.6f %i\n", elapsed_time / 1000.0, typescript->length);
    195
    196    /* Calculate actual length of timestamp line */
    197    if (timestamp_length > sizeof(timestamp_buffer))
    198        timestamp_length = sizeof(timestamp_buffer);
    199
    200    /* Write timestamp to timing file */
    201    guac_common_write(typescript->timing_fd,
    202            timestamp_buffer, timestamp_length);
    203
    204    /* Empty buffer into data file */
    205    guac_common_write(typescript->data_fd,
    206            typescript->buffer, typescript->length);
    207
    208    /* Buffer is now flushed */
    209    typescript->length = 0;
    210    typescript->last_flush = this_flush;
    211
    212}
    213
    214void guac_terminal_typescript_free(guac_terminal_typescript* typescript) {
    215
    216    /* Do nothing if no typescript provided */
    217    if (typescript == NULL)
    218        return;
    219
    220    /* Flush any pending data */
    221    guac_terminal_typescript_flush(typescript);
    222
    223    /* Write footer */
    224    guac_common_write(typescript->data_fd, GUAC_TERMINAL_TYPESCRIPT_FOOTER,
    225            sizeof(GUAC_TERMINAL_TYPESCRIPT_FOOTER) - 1);
    226
    227    /* Close file descriptors */
    228    close(typescript->data_fd);
    229    close(typescript->timing_fd);
    230
    231    /* Free allocated typescript data */
    232    guac_mem_free(typescript);
    233
    234}
    235