GUACAMOLE-574: Add support for reading STDIN from a pipe stream.

This commit is contained in:
Michael Jumper 2018-05-19 14:28:09 -07:00
parent f3d9c2f610
commit 97593958e4
4 changed files with 318 additions and 9 deletions

View File

@ -48,6 +48,7 @@ libguac_terminal_la_SOURCES = \
select.c \ select.c \
terminal.c \ terminal.c \
terminal_handlers.c \ terminal_handlers.c \
terminal-stdin-stream.c \
typescript.c \ typescript.c \
xparsecolor.c xparsecolor.c

View File

@ -0,0 +1,156 @@
/*
* 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 "config.h"
#include "terminal/common.h"
#include "terminal/terminal.h"
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
/**
* Handler for "blob" instructions which writes the data of received
* blobs to STDIN of the terminal associated with the stream.
*
* @see guac_user_blob_handler
*/
static int guac_terminal_input_stream_blob_handler(guac_user* user,
guac_stream* stream, void* data, int length) {
guac_terminal* term = (guac_terminal*) stream->data;
/* Attempt to write received data */
guac_terminal_lock(term);
int result = guac_terminal_write_all(term->stdin_pipe_fd[1], data, length);
guac_terminal_unlock(term);
/* Acknowledge receipt of data and result of write attempt */
if (result <= 0) {
guac_user_log(user, GUAC_LOG_DEBUG,
"Attempt to write to STDIN via an inbound stream failed.");
guac_protocol_send_ack(user->socket, stream,
"Attempt to write to STDIN failed.",
GUAC_PROTOCOL_STATUS_SUCCESS);
}
else {
guac_user_log(user, GUAC_LOG_DEBUG,
"%i bytes successfully written to STDIN from an inbound stream.",
length);
guac_protocol_send_ack(user->socket, stream,
"Data written to STDIN.",
GUAC_PROTOCOL_STATUS_SUCCESS);
}
guac_socket_flush(user->socket);
return 0;
}
/**
* Handler for "end" instructions which disassociates the given
* stream from the terminal, allowing user input to resume.
*
* @see guac_user_end_handler
*/
static int guac_terminal_input_stream_end_handler(guac_user* user,
guac_stream* stream) {
guac_terminal* term = (guac_terminal*) stream->data;
/* Reset input stream, unblocking user input */
guac_terminal_lock(term);
term->input_stream = NULL;
guac_terminal_unlock(term);
guac_user_log(user, GUAC_LOG_DEBUG, "Inbound stream closed. User input "
"will now resume affecting STDIN.");
return 0;
}
/**
* Internal implementation of guac_terminal_send_stream() which assumes
* that the guac_terminal has already been locked through a call to
* guac_terminal_lock(). The semantics of all parameters and the return
* value are identical to guac_terminal_send_stream().
*
* @see guac_terminal_send_stream()
*/
static int __guac_terminal_send_stream(guac_terminal* term, guac_user* user,
guac_stream* stream) {
/* If a stream is already being used for STDIN, deny creation of
* further streams */
if (term->input_stream != NULL) {
guac_user_log(user, GUAC_LOG_DEBUG, "Attempt to direct the contents "
"of an inbound stream to STDIN denied. STDIN is already "
"being read from an inbound stream.");
guac_protocol_send_ack(user->socket, stream,
"STDIN is already being read from a stream.",
GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT);
guac_socket_flush(user->socket);
return 1;
}
guac_user_log(user, GUAC_LOG_DEBUG, "Now reading STDIN from inbound "
"stream. User input will no longer affect STDIN until the "
"stream is closed.");
stream->blob_handler = guac_terminal_input_stream_blob_handler;
stream->end_handler = guac_terminal_input_stream_end_handler;
stream->data = term;
/* Block user input until stream is ended */
term->input_stream = stream;
/* Acknowledge redirection from stream */
guac_protocol_send_ack(user->socket, stream,
"Now reading STDIN from stream.",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}
int guac_terminal_send_stream(guac_terminal* term, guac_user* user,
guac_stream* stream) {
int result;
guac_terminal_lock(term);
result = __guac_terminal_send_stream(term, user, stream);
guac_terminal_unlock(term);
return result;
}

View File

@ -604,6 +604,9 @@ guac_terminal* guac_terminal_create(guac_client* client,
return NULL; return NULL;
} }
/* Read input from keyboard by default */
term->input_stream = NULL;
/* Init pipe stream (output to display by default) */ /* Init pipe stream (output to display by default) */
term->pipe_stream = NULL; term->pipe_stream = NULL;
@ -1565,11 +1568,23 @@ void guac_terminal_unlock(guac_terminal* terminal) {
} }
int guac_terminal_send_data(guac_terminal* term, const char* data, int length) { int guac_terminal_send_data(guac_terminal* term, const char* data, int length) {
/* Block all other sources of input if input is coming from a stream */
if (term->input_stream != NULL)
return 0;
return guac_terminal_write_all(term->stdin_pipe_fd[1], data, length); return guac_terminal_write_all(term->stdin_pipe_fd[1], data, length);
} }
int guac_terminal_send_string(guac_terminal* term, const char* data) { int guac_terminal_send_string(guac_terminal* term, const char* data) {
/* Block all other sources of input if input is coming from a stream */
if (term->input_stream != NULL)
return 0;
return guac_terminal_write_all(term->stdin_pipe_fd[1], data, strlen(data)); return guac_terminal_write_all(term->stdin_pipe_fd[1], data, strlen(data));
} }
static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
@ -1867,6 +1882,10 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...) {
va_list ap; va_list ap;
char buffer[1024]; char buffer[1024];
/* Block all other sources of input if input is coming from a stream */
if (term->input_stream != NULL)
return 0;
/* Print to buffer */ /* Print to buffer */
va_start(ap, format); va_start(ap, format);
written = vsnprintf(buffer, sizeof(buffer)-1, format, ap); written = vsnprintf(buffer, sizeof(buffer)-1, format, ap);

View File

@ -200,6 +200,14 @@ struct guac_terminal {
*/ */
int stdin_pipe_fd[2]; int stdin_pipe_fd[2];
/**
* The currently-open pipe stream from which all terminal input should be
* read, if any. If no pipe stream is open, terminal input will be received
* through keyboard, clipboard, and mouse events, and this value will be
* NULL.
*/
guac_stream* input_stream;
/** /**
* The currently-open pipe stream to which all terminal output should be * The currently-open pipe stream to which all terminal output should be
* written, if any. If no pipe stream is open, terminal output will be * written, if any. If no pipe stream is open, terminal output will be
@ -518,9 +526,9 @@ int guac_terminal_render_frame(guac_terminal* terminal);
/** /**
* Reads from this terminal's STDIN. Input comes from key and mouse events * Reads from this terminal's STDIN. Input comes from key and mouse events
* supplied by calls to guac_terminal_send_key() and * supplied by calls to guac_terminal_send_key(),
* guac_terminal_send_mouse(). If input is not yet available, this function * guac_terminal_send_mouse(), and guac_terminal_send_stream(). If input is not
* will block. * yet available, this function will block.
*/ */
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size); int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
@ -576,17 +584,94 @@ int guac_terminal_printf(guac_terminal* terminal, const char* format, ...);
/** /**
* Handles the given key event, sending data, scrolling, pasting clipboard * Handles the given key event, sending data, scrolling, pasting clipboard
* data, etc. as necessary. * data, etc. as necessary. If terminal input is currently coming from a
* stream due to a prior call to guac_terminal_send_stream(), any input
* which would normally result from the key event is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param keysym
* The X11 keysym of the key that was pressed or released.
*
* @param pressed
* Non-zero if the key represented by the given keysym is currently
* pressed, zero if it is released.
*
* @return
* Zero if the key event was handled successfully, non-zero otherwise.
*/ */
int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed); int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed);
/** /**
* Handles the given mouse event, sending data, scrolling, pasting clipboard * Handles the given mouse event, sending data, scrolling, pasting clipboard
* data, etc. as necessary. * data, etc. as necessary. If terminal input is currently coming from a
* stream due to a prior call to guac_terminal_send_stream(), any input
* which would normally result from the mouse event is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param user
* The user that originated the mouse event.
*
* @param x
* The X coordinate of the mouse within the display when the event
* occurred, in pixels. This value is not guaranteed to be within the
* bounds of the display area.
*
* @param y
* The Y coordinate of the mouse within the display when the event
* occurred, in pixels. This value is not guaranteed to be within the
* bounds of the display area.
*
* @param mask
* An integer value representing the current state of each button, where
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
* button is currently pressed. The lowest-order bit is the left mouse
* button, followed by the middle button, right button, and finally the up
* and down buttons of the scroll wheel.
*
* @see GUAC_CLIENT_MOUSE_LEFT
* @see GUAC_CLIENT_MOUSE_MIDDLE
* @see GUAC_CLIENT_MOUSE_RIGHT
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*
* @return
* Zero if the mouse event was handled successfully, non-zero otherwise.
*/ */
int guac_terminal_send_mouse(guac_terminal* term, guac_user* user, int guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
int x, int y, int mask); int x, int y, int mask);
/**
* Initializes the handlers of the given guac_stream such that it serves as the
* source of input to the terminal. Other input sources will be temporarily
* ignored until the stream is closed through receiving an "end" instruction.
* If input is already being read from a stream due to a prior call to
* guac_terminal_send_stream(), the prior call will remain in effect and this
* call will fail.
*
* Calling this function will overwrite the data member of the given
* guac_stream.
*
* @param term
* The terminal emulator which should receive input from the given stream.
*
* @param user
* The user that opened the stream.
*
* @param stream
* The guac_stream which should serve as the source of input for the
* terminal.
*
* @return
* Zero if the terminal input has successfully been configured to read from
* the given stream, non-zero otherwise.
*/
int guac_terminal_send_stream(guac_terminal* term, guac_user* user,
guac_stream* stream);
/** /**
* Handles a scroll event received from the scrollbar associated with a * Handles a scroll event received from the scrollbar associated with a
* terminal. * terminal.
@ -740,18 +825,66 @@ int guac_terminal_resize(guac_terminal* term, int width, int height);
void guac_terminal_flush(guac_terminal* terminal); void guac_terminal_flush(guac_terminal* terminal);
/** /**
* Sends the given string as if typed by the user. * Sends the given string as if typed by the user. If terminal input is
* currently coming from a stream due to a prior call to
* guac_terminal_send_stream(), any input which would normally result from
* invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param data
* The data the terminal should receive on STDIN.
*
* @param length
* The size of the given data, in bytes.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/ */
int guac_terminal_send_data(guac_terminal* term, const char* data, int length); int guac_terminal_send_data(guac_terminal* term, const char* data, int length);
/** /**
* Sends the given string as if typed by the user. * Sends the given string as if typed by the user. If terminal input is
* currently coming from a stream due to a prior call to
* guac_terminal_send_stream(), any input which would normally result from
* invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param data
* The data the terminal should receive on STDIN.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/ */
int guac_terminal_send_string(guac_terminal* term, const char* data); int guac_terminal_send_string(guac_terminal* term, const char* data);
/** /**
* Sends data through STDIN as if typed by the user, using the format * Sends data through STDIN as if typed by the user, using the format string
* string given and any args (similar to printf). * given and any args (similar to printf). If terminal input is currently
* coming from a stream due to a prior call to guac_terminal_send_stream(), any
* input which would normally result from invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param format
* A printf-style format string describing the data to be received on
* STDIN.
*
* @param ...
* Any srguments to use when filling the format string.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/ */
int guac_terminal_sendf(guac_terminal* term, const char* format, ...); int guac_terminal_sendf(guac_terminal* term, const char* format, ...);