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