diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 40aca246..f2cfb550 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -38,7 +38,8 @@ noinst_HEADERS = \ scrollbar.h \ terminal.h \ terminal_handlers.h \ - types.h + types.h \ + typescript.h libguac_terminal_la_SOURCES = \ blank.c \ @@ -52,7 +53,8 @@ libguac_terminal_la_SOURCES = \ pointer.c \ scrollbar.c \ terminal.c \ - terminal_handlers.c + terminal_handlers.c \ + typescript.c libguac_terminal_la_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 95a71806..e9a79c63 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -35,6 +35,7 @@ #include "terminal.h" #include "terminal_handlers.h" #include "types.h" +#include "typescript.h" #include #include @@ -329,6 +330,9 @@ guac_terminal* guac_terminal_create(guac_client* client, /* Init pipe stream (output to display by default) */ term->pipe_stream = NULL; + /* No typescript by default */ + term->typescript = NULL; + /* Init terminal lock */ pthread_mutex_init(&(term->lock), NULL); @@ -375,6 +379,9 @@ void guac_terminal_free(guac_terminal* term) { /* Close and flush any open pipe stream */ guac_terminal_pipe_stream_close(term); + /* Close and flush any active typescript */ + guac_terminal_typescript_free(term->typescript); + /* Close terminal output pipe */ close(term->stdout_pipe_fd[1]); close(term->stdout_pipe_fd[0]); @@ -1811,3 +1818,14 @@ void guac_terminal_pipe_stream_close(guac_terminal* term) { } +int guac_terminal_create_typescript(guac_terminal* term, const char* path, + const char* name, int create_path) { + + /* Create typescript */ + term->typescript = guac_terminal_typescript_alloc(path, name, create_path); + + /* Typescript creation failed if NULL */ + return term->typescript != NULL; + +} + diff --git a/src/terminal/terminal.h b/src/terminal/terminal.h index 91bebfa9..ea1ac1fe 100644 --- a/src/terminal/terminal.h +++ b/src/terminal/terminal.h @@ -32,6 +32,7 @@ #include "guac_clipboard.h" #include "scrollbar.h" #include "types.h" +#include "typescript.h" #include #include @@ -169,6 +170,12 @@ struct guac_terminal { */ int pipe_buffer_length; + /** + * The currently-active typescript recording all terminal output, or NULL + * if no typescript is being used for the terminal session. + */ + guac_terminal_typescript* typescript; + /** * Graphical representation of the current scroll state. */ @@ -729,5 +736,37 @@ void guac_terminal_pipe_stream_flush(guac_terminal* term); */ void guac_terminal_pipe_stream_close(guac_terminal* term); +/** + * Requests that the terminal write all output to a new pair of typescript + * files within the given path and using the given base name. Terminal output + * will be written to these new files, along with timing information. If the + * create_path flag is non-zero, the given path will be created if it does not + * yet exist. If creation of the typescript files or path fails, error messages + * will automatically be logged, and no typescript will be written. The + * typescript will automatically be closed once the terminal is freed. + * + * @param term + * The terminal whose output should be written to a typescript. + * + * @param path + * The full absolute path to a directory in which the typescript files + * should be created. + * + * @param name + * The base name to use for the typescript files created within the + * specified path. + * + * @param create_path + * Zero if the specified path MUST exist for typescript files to be + * written, or non-zero if the path should be created if it does not yet + * exist. + * + * @return + * Zero if the typescript files have been successfully created and a + * typescript will be written, non-zero otherwise. + */ +int guac_terminal_create_typescript(guac_terminal* term, const char* path, + const char* name, int create_path); + #endif diff --git a/src/terminal/typescript.c b/src/terminal/typescript.c new file mode 100644 index 00000000..0727d188 --- /dev/null +++ b/src/terminal/typescript.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "guac_io.h" +#include "typescript.h" + +#include +#include + +#include +#include +#include + +guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path, + const char* name, int create_path) { + + guac_terminal_typescript* typescript; + int data_fd, timing_fd; + + /* TODO: Determing data and timing filenames prior to open() calls. + * Be sure not to use open() itself to test for existence, as that could + * result in tons of typescript data files being unnecessarily created. + */ + + /* Attempt to open typescript data file */ + data_fd = open("/tmp/typescript-data", O_CREAT | O_EXCL | O_WRONLY); + if (data_fd == -1) + return NULL; + + /* Attempt to open typescript timing file */ + timing_fd = open("/tmp/typescript-timing", O_CREAT | O_EXCL | O_WRONLY); + if (timing_fd == -1) { + close(data_fd); + return NULL; + } + + /* Init newly-created typescript */ + typescript = malloc(sizeof(guac_terminal_typescript)); + typescript->data_fd = data_fd; + typescript->timing_fd = timing_fd; + typescript->length = 0; + + return typescript; + +} + +void guac_terminal_typescript_write(guac_terminal_typescript* typescript, + char c) { + + /* Flush buffer if no space is available */ + if (typescript->length == sizeof(typescript->buffer)) + guac_terminal_typescript_flush(typescript); + + /* Append single byte to buffer */ + typescript->buffer[typescript->length++] = c; + +} + +void guac_terminal_typescript_flush(guac_terminal_typescript* typescript) { + + /* Empty buffer into data file */ + guac_common_write(typescript->data_fd, + typescript->buffer, typescript->length); + typescript->length = 0; + + /* TODO: Write timestamp */ + +} + +void guac_terminal_typescript_free(guac_terminal_typescript* typescript) { + + /* Do nothing if no typescript provided */ + if (typescript == NULL) + return; + + /* Flush any pending data */ + guac_terminal_typescript_flush(typescript); + + /* Close file descriptors */ + close(typescript->data_fd); + close(typescript->timing_fd); + + /* Free allocated typescript data */ + free(typescript); + +} + diff --git a/src/terminal/typescript.h b/src/terminal/typescript.h new file mode 100644 index 00000000..aaa56e38 --- /dev/null +++ b/src/terminal/typescript.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef GUAC_TERMINAL_TYPESCRIPT_H +#define GUAC_TERMINAL_TYPESCRIPT_H + +#include "config.h" + +/** + * An active typescript, consisting of a data file (raw terminal output) and + * timing file (related timestamps and byte counts). + */ +typedef struct guac_terminal_typescript { + + /** + * Buffer of raw terminal output which has not yet been written to the + * data file. + */ + char buffer[4096]; + + /** + * The number of bytes currently stored in the buffer. + */ + int length; + + /** + * The file descriptor of the file into which raw terminal output should be + * written. + */ + int data_fd; + + /** + * The file descriptor of the file into which timing information + * (timestamps and byte counts) related to the raw terminal output in the + * data file should be written. + */ + int timing_fd; + +} guac_terminal_typescript; + +/** + * Creates a new pair of typescript files within the given path and using the + * given base name, returning an abstraction which represents those files. + * Terminal output will be written to these new files, along with timing + * information. If the create_path flag is non-zero, the given path will be + * created if it does not yet exist. + * + * @param path + * The full absolute path to a directory in which the typescript files + * should be created. + * + * @param name + * The base name to use for the typescript files created within the + * specified path. + * + * @param create_path + * Zero if the specified path MUST exist for typescript files to be + * written, or non-zero if the path should be created if it does not yet + * exist. + * + * @return + * A new guac_terminal_typescript representing the typescript files + * requested, or NULL if creation of the typescript files failed. + */ +guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path, + const char* name, int create_path); + +/** + * Writes a single byte of terminal data to the typescript, flushing and + * writing a new timestamp if necessary. + * + * @param typescript + * The typescript that the given byte of raw terminal data should be + * written to. + * + * @param c + * The single byte of raw terminal data to write to the typescript. + */ +void guac_terminal_typescript_write(guac_terminal_typescript* typescript, + char c); + +/** + * Flushes any pending data to the typescript, writing a new timestamp to the + * timing file if any data was flushed. + * + * @param typescript + * The typescript which should be flushed. + */ +void guac_terminal_typescript_flush(guac_terminal_typescript* typescript); + +/** + * Frees all resources associated with the given typescript, flushing and + * closing the data and timing files and freeing all related memory. If the + * provided typescript is NULL, this function has no effect. + * + * @param typescript + * The typescript to free. + */ +void guac_terminal_typescript_free(guac_terminal_typescript* typescript); + +#endif +