GUACAMOLE-86: Merge removal of terminal emulator's STDOUT pipe.
diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c
index db3cea9..4bdf76f 100644
--- a/src/protocols/ssh/ssh.c
+++ b/src/protocols/ssh/ssh.c
@@ -339,7 +339,7 @@
/* Attempt to write data received. Exit on failure. */
if (bytes_read > 0) {
- int written = guac_terminal_write_stdout(ssh_client->term, buffer, bytes_read);
+ int written = guac_terminal_write(ssh_client->term, buffer, bytes_read);
if (written < 0)
break;
diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c
index 6ca9aba..531819f 100644
--- a/src/protocols/telnet/telnet.c
+++ b/src/protocols/telnet/telnet.c
@@ -150,7 +150,7 @@
/* Terminal output received */
case TELNET_EV_DATA:
- guac_terminal_write_stdout(telnet_client->term, event->data.buffer, event->data.size);
+ guac_terminal_write(telnet_client->term, event->data.buffer, event->data.size);
/* Continue search for username prompt */
if (settings->username_regex != NULL) {
@@ -267,7 +267,7 @@
while ((bytes_read = guac_terminal_read_stdin(telnet_client->term, buffer, sizeof(buffer))) > 0) {
telnet_send(telnet_client->telnet, buffer, bytes_read);
if (telnet_client->echo_enabled)
- guac_terminal_write_stdout(telnet_client->term, buffer, bytes_read);
+ guac_terminal_write(telnet_client->term, buffer, bytes_read);
}
return NULL;
diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am
index 19cbf94..59bcd12 100644
--- a/src/terminal/Makefile.am
+++ b/src/terminal/Makefile.am
@@ -27,7 +27,6 @@
char_mappings.h \
common.h \
display.h \
- packet.h \
scrollbar.h \
terminal.h \
terminal_handlers.h \
@@ -39,7 +38,6 @@
char_mappings.c \
common.c \
display.c \
- packet.c \
scrollbar.c \
terminal.c \
terminal_handlers.c \
diff --git a/src/terminal/common.c b/src/terminal/common.c
index cc98276..4018e77 100644
--- a/src/terminal/common.c
+++ b/src/terminal/common.c
@@ -105,23 +105,3 @@
}
-int guac_terminal_fill_buffer(int fd, char* buffer, int size) {
-
- int remaining = size;
- while (remaining > 0) {
-
- /* Attempt to read data */
- int ret_val = read(fd, buffer, remaining);
- if (ret_val <= 0)
- return -1;
-
- /* If successful, continue with what space remains (if any) */
- remaining -= ret_val;
- buffer += ret_val;
-
- }
-
- return size;
-
-}
-
diff --git a/src/terminal/common.h b/src/terminal/common.h
index 495bf30..f7eda61 100644
--- a/src/terminal/common.h
+++ b/src/terminal/common.h
@@ -49,26 +49,5 @@
*/
int guac_terminal_write_all(int fd, const char* buffer, int size);
-/**
- * Similar to read, but automatically retries the read until an error occurs,
- * filling all available space within the buffer. Unless it is known that the
- * given amount of space is available on the file descriptor, there is a good
- * chance this function will block.
- *
- * @param fd
- * The file descriptor to read data from.
- *
- * @param buffer
- * The buffer to store data within.
- *
- * @param size
- * The number of bytes available within the buffer.
- *
- * @return
- * The number of bytes read if successful, or a negative value if an error
- * occurs.
- */
-int guac_terminal_fill_buffer(int fd, char* buffer, int size);
-
#endif
diff --git a/src/terminal/packet.c b/src/terminal/packet.c
deleted file mode 100644
index ad828ce..0000000
--- a/src/terminal/packet.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include "common.h"
-#include "packet.h"
-
-#include <string.h>
-
-int guac_terminal_packet_write(int fd, const void* data, int length) {
-
- guac_terminal_packet out;
-
- /* Do not attempt to write packets beyond maximum size */
- if (length > GUAC_TERMINAL_PACKET_SIZE)
- return -1;
-
- /* Calculate final packet length */
- int packet_length = sizeof(int) + length;
-
- /* Copy data into packet */
- out.length = length;
- memcpy(out.data, data, length);
-
- /* Write packet */
- return guac_terminal_write_all(fd, (const char*) &out, packet_length);
-
-}
-
-int guac_terminal_packet_read(int fd, void* data, int length) {
-
- int bytes;
-
- /* Read buffers MUST be at least GUAC_TERMINAL_PACKET_SIZE */
- if (length < GUAC_TERMINAL_PACKET_SIZE)
- return -1;
-
- /* Read length */
- if (guac_terminal_fill_buffer(fd, (char*) &bytes, sizeof(int)) < 0)
- return -1;
-
- /* Read body */
- if (guac_terminal_fill_buffer(fd, (char*) data, bytes) < 0)
- return -1;
-
- return bytes;
-
-}
-
diff --git a/src/terminal/packet.h b/src/terminal/packet.h
deleted file mode 100644
index 00d3052..0000000
--- a/src/terminal/packet.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#ifndef GUAC_TERMINAL_PACKET_H
-#define GUAC_TERMINAL_PACKET_H
-
-/**
- * The maximum size of a packet written or read by the
- * guac_terminal_packet_write() or guac_terminal_packet_read() functions.
- */
-#define GUAC_TERMINAL_PACKET_SIZE 4096
-
-/**
- * An arbitrary data packet with minimal framing.
- */
-typedef struct guac_terminal_packet {
-
- /**
- * The number of bytes in the data portion of this packet.
- */
- int length;
-
- /**
- * Arbitrary data.
- */
- char data[GUAC_TERMINAL_PACKET_SIZE];
-
-} guac_terminal_packet;
-
-/**
- * Writes a single packet of data to the given file descriptor. The provided
- * length MUST be no greater than GUAC_TERMINAL_PACKET_SIZE. Zero-length
- * writes are legal and do result in a packet being written to the file
- * descriptor.
- *
- * @param fd
- * The file descriptor to write to.
- *
- * @param data
- * A buffer containing the data to write.
- *
- * @param length
- * The number of bytes to write to the file descriptor.
- *
- * @return
- * The number of bytes written on success, which may be zero if the data
- * length is zero, or a negative value on error.
- */
-int guac_terminal_packet_write(int fd, const void* data, int length);
-
-/**
- * Reads a single packet of data from the given file descriptor. The provided
- * length MUST be at least GUAC_TERMINAL_PACKET_SIZE to ensure any packet
- * read will fit in the buffer. Zero-length reads are possible if a zero-length
- * packet was written.
- *
- * @param fd
- * The file descriptor to read from.
- *
- * @param data
- * The buffer to store data within.
- *
- * @param length
- * The number of bytes available within the buffer.
- *
- * @return
- * The number of bytes read on success, which may be zero if the read
- * packet had a length of zero, or a negative value on error.
- */
-int guac_terminal_packet_read(int fd, void* data, int length);
-
-#endif
-
diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c
index 598a385..7488a31 100644
--- a/src/terminal/terminal.c
+++ b/src/terminal/terminal.c
@@ -24,7 +24,6 @@
#include "display.h"
#include "guac_clipboard.h"
#include "guac_cursor.h"
-#include "packet.h"
#include "scrollbar.h"
#include "terminal.h"
#include "terminal_handlers.h"
@@ -32,7 +31,6 @@
#include "typescript.h"
#include <errno.h>
-#include <poll.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
@@ -318,6 +316,11 @@
term->upload_path_handler = NULL;
term->file_download_handler = NULL;
+ /* Init modified flag and conditional */
+ term->modified = 0;
+ pthread_cond_init(&(term->modified_cond), NULL);
+ pthread_mutex_init(&(term->modified_lock), NULL);
+
/* Init buffer */
term->buffer = guac_terminal_buffer_alloc(1000, &default_char);
@@ -349,14 +352,6 @@
term->term_width = available_width / term->display->char_width;
term->term_height = height / term->display->char_height;
- /* Open STDOUT pipe */
- if (pipe(term->stdout_pipe_fd)) {
- guac_error = GUAC_STATUS_SEE_ERRNO;
- guac_error_message = "Unable to open pipe for STDOUT";
- free(term);
- return NULL;
- }
-
/* Open STDIN pipe */
if (pipe(term->stdin_pipe_fd)) {
guac_error = GUAC_STATUS_SEE_ERRNO;
@@ -414,10 +409,6 @@
void guac_terminal_free(guac_terminal* term) {
- /* Close terminal output pipe */
- close(term->stdout_pipe_fd[1]);
- close(term->stdout_pipe_fd[0]);
-
/* Close user input pipe */
close(term->stdin_pipe_fd[1]);
close(term->stdin_pipe_fd[0]);
@@ -449,84 +440,110 @@
}
/**
- * Waits for data to become available on the given file descriptor.
+ * Populate the given timespec with the current time, plus the given offset.
*
- * @param fd
- * The file descriptor to wait on.
+ * @param ts
+ * The timespec structure to populate.
+ *
+ * @param offset_sec
+ * The offset from the current time to use when populating the given
+ * timespec, in seconds.
+ *
+ * @param offset_usec
+ * The offset from the current time to use when populating the given
+ * timespec, in microseconds.
+ */
+static void guac_terminal_get_absolute_time(struct timespec* ts,
+ int offset_sec, int offset_usec) {
+
+ /* Get timeval */
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ /* Update with offset */
+ tv.tv_sec += offset_sec;
+ tv.tv_usec += offset_usec;
+
+ /* Wrap to next second if necessary */
+ if (tv.tv_usec >= 1000000) {
+ tv.tv_sec++;
+ tv.tv_usec -= 1000000;
+ }
+
+ /* Convert to timespec */
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = tv.tv_usec * 1000;
+
+}
+
+/**
+ * Waits for the terminal state to be modified, returning only when the
+ * specified timeout has elapsed or a frame flush is desired. Note that the
+ * modified flag of the terminal will only be reset if no data remains to be
+ * read from STDOUT.
+ *
+ * @param terminal
+ * The terminal to wait on.
*
* @param msec_timeout
* The maximum amount of time to wait, in milliseconds.
*
* @return
- * A positive if data is available, zero if the timeout has elapsed without
- * data becoming available, or negative if an error occurred.
+ * Non-zero if the terminal has been modified, zero if the timeout has
+ * elapsed without the terminal being modified.
*/
-static int guac_terminal_wait_for_data(int fd, int msec_timeout) {
+static int guac_terminal_wait(guac_terminal* terminal, int msec_timeout) {
- /* Build array of file descriptors */
- struct pollfd fds[] = {{
- .fd = fd,
- .events = POLLIN,
- .revents = 0,
- }};
+ int retval = 1;
- /* Wait for data */
- return poll(fds, 1, msec_timeout);
+ pthread_mutex_t* mod_lock = &(terminal->modified_lock);
+ pthread_cond_t* mod_cond = &(terminal->modified_cond);
+
+ /* Split provided milliseconds into microseconds and whole seconds */
+ int secs = msec_timeout / 1000;
+ int usecs = (msec_timeout % 1000) * 1000;
+
+ /* Calculate absolute timestamp from provided relative timeout */
+ struct timespec timeout;
+ guac_terminal_get_absolute_time(&timeout, secs, usecs);
+
+ /* Test for terminal modification */
+ pthread_mutex_lock(mod_lock);
+ if (terminal->modified)
+ goto wait_complete;
+
+ /* If not yet modified, wait for modification condition to be signaled */
+ retval = pthread_cond_timedwait(mod_cond, mod_lock, &timeout) != ETIMEDOUT;
+
+wait_complete:
+
+ /* Terminal is no longer modified */
+ terminal->modified = 0;
+ pthread_mutex_unlock(mod_lock);
+ return retval;
}
int guac_terminal_render_frame(guac_terminal* terminal) {
- guac_client* client = terminal->client;
- char buffer[GUAC_TERMINAL_PACKET_SIZE];
-
int wait_result;
- int fd = terminal->stdout_pipe_fd[0];
/* Wait for data to be available */
- wait_result = guac_terminal_wait_for_data(fd, 1000);
- if (wait_result > 0) {
+ wait_result = guac_terminal_wait(terminal, 1000);
+ if (wait_result) {
- guac_terminal_lock(terminal);
guac_timestamp frame_start = guac_timestamp_current();
do {
- guac_timestamp frame_end;
- int frame_remaining;
-
- int bytes_read;
-
- /* Read data, write to terminal */
- if ((bytes_read = guac_terminal_packet_read(fd,
- buffer, sizeof(buffer))) > 0) {
-
- if (guac_terminal_write(terminal, buffer, bytes_read)) {
- guac_client_abort(client,
- GUAC_PROTOCOL_STATUS_SERVER_ERROR,
- "Error writing data");
- guac_terminal_unlock(terminal);
- return 1;
- }
-
- }
-
- /* Notify on error */
- if (bytes_read < 0) {
- guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
- "Error reading data");
- guac_terminal_unlock(terminal);
- return 1;
- }
-
/* Calculate time remaining in frame */
- frame_end = guac_timestamp_current();
- frame_remaining = frame_start + GUAC_TERMINAL_FRAME_DURATION
- - frame_end;
+ guac_timestamp frame_end = guac_timestamp_current();
+ int frame_remaining = frame_start + GUAC_TERMINAL_FRAME_DURATION
+ - frame_end;
/* Wait again if frame remaining */
if (frame_remaining > 0)
- wait_result = guac_terminal_wait_for_data(fd,
+ wait_result = guac_terminal_wait(terminal,
GUAC_TERMINAL_FRAME_TIMEOUT);
else
break;
@@ -534,18 +551,12 @@
} while (wait_result > 0);
/* Flush terminal */
+ guac_terminal_lock(terminal);
guac_terminal_flush(terminal);
guac_terminal_unlock(terminal);
}
- /* Notify of any errors */
- if (wait_result < 0) {
- guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
- "Error waiting for data");
- return 1;
- }
-
return 0;
}
@@ -555,28 +566,19 @@
return read(stdin_fd, c, size);
}
-int guac_terminal_write_stdout(guac_terminal* terminal, const char* c,
- int size) {
+void guac_terminal_notify(guac_terminal* terminal) {
- /* Write maximally-sized packets until only one packet remains */
- while (size > GUAC_TERMINAL_PACKET_SIZE) {
+ pthread_mutex_t* mod_lock = &(terminal->modified_lock);
+ pthread_cond_t* mod_cond = &(terminal->modified_cond);
- /* Write maximally-sized packet */
- if (guac_terminal_packet_write(terminal->stdout_pipe_fd[1], c,
- GUAC_TERMINAL_PACKET_SIZE) < 0)
- return -1;
+ pthread_mutex_lock(mod_lock);
- /* Advance to next packet */
- c += GUAC_TERMINAL_PACKET_SIZE;
- size -= GUAC_TERMINAL_PACKET_SIZE;
+ /* Signal modification */
+ terminal->modified = 1;
+ pthread_cond_signal(mod_cond);
- }
+ pthread_mutex_unlock(mod_lock);
- return guac_terminal_packet_write(terminal->stdout_pipe_fd[1], c, size);
-}
-
-int guac_terminal_notify(guac_terminal* terminal) {
- return guac_terminal_packet_write(terminal->stdout_pipe_fd[1], NULL, 0);
}
int guac_terminal_printf(guac_terminal* terminal, const char* format, ...) {
@@ -595,7 +597,7 @@
return written;
/* Write to STDOUT */
- return guac_terminal_write_stdout(terminal, buffer, written);
+ return guac_terminal_write(terminal, buffer, written);
}
@@ -711,6 +713,7 @@
int guac_terminal_write(guac_terminal* term, const char* c, int size) {
+ guac_terminal_lock(term);
while (size > 0) {
/* Read and advance to next character */
@@ -725,7 +728,9 @@
term->char_handler(term, current);
}
+ guac_terminal_unlock(term);
+ guac_terminal_notify(term);
return 0;
}
diff --git a/src/terminal/terminal.h b/src/terminal/terminal.h
index 7598fce..62312b0 100644
--- a/src/terminal/terminal.h
+++ b/src/terminal/terminal.h
@@ -159,13 +159,24 @@
pthread_mutex_t lock;
/**
- * Pipe which should be written to (and read from) to provide output to
- * this terminal. Another thread should read from this pipe when writing
- * data to the terminal. It would make sense for the terminal to provide
- * this thread, but for simplicity, that logic is left to the guac
- * message handler (to give the message handler something to block with).
+ * The mutex associated with the modified condition and flag, locked
+ * whenever a thread is waiting on the modified condition, the modified
+ * condition is being signalled, or the modified flag is being changed.
*/
- int stdout_pipe_fd[2];
+ pthread_mutex_t modified_lock;
+
+ /**
+ * Flag set whenever an operation has affected the terminal in a way that
+ * will require a frame flush. When this flag is set, the modified_cond
+ * condition will be signalled. The modified_lock will always be
+ * acquired before this flag is altered.
+ */
+ int modified;
+
+ /**
+ * Condition which is signalled when the modified flag has been set
+ */
+ pthread_cond_t modified_cond;
/**
* Pipe which will be the source of user input. When a terminal code
@@ -474,23 +485,13 @@
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
/**
- * Writes to this terminal's STDOUT. This function may block until space
- * is freed in the output buffer by guac_terminal_render_frame().
- */
-int guac_terminal_write_stdout(guac_terminal* terminal, const char* c, int size);
-
-/**
* Notifies the terminal that an event has occurred and the terminal should
* flush itself when reasonable.
*
* @param terminal
* The terminal to notify.
- *
- * @return
- * Zero if notification succeeded, non-zero if an error occurred while
- * notifying the terminal.
*/
-int guac_terminal_notify(guac_terminal* terminal);
+void guac_terminal_notify(guac_terminal* terminal);
/**
* Reads a single line from this terminal's STDIN, storing the result in a