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