summaryrefslogtreecommitdiffstats
path: root/lib/vty_io_file.c
diff options
context:
space:
mode:
authorChris Hall <chris.hall@highwayman.com>2011-07-21 19:53:02 +0100
committerChris Hall <chris.hall@highwayman.com>2011-07-21 19:53:02 +0100
commit56da2a1c9b6361e302b7a39fe2740561a9012d88 (patch)
tree6b6543532133a0c618d0f4ec70a87cf3f96caf30 /lib/vty_io_file.c
parente535bc959729262480a9702e71334002edee3f8c (diff)
downloadquagga-56da2a1c9b6361e302b7a39fe2740561a9012d88.tar.bz2
quagga-56da2a1c9b6361e302b7a39fe2740561a9012d88.tar.xz
Update pipework and improve memory reporting.
Improve error handling for all new pipework inputs and outputs. Change behaviour of ^C from VTY Terminal, so that will interrupt output and terminate all running pipes -- including running shell commands. In pipe commands, recognise "~/..." and "~user/..." home directory forms. Changed "~/" to mean the usual home for the current user. "~~/" now means the configuration file directory. Introduced "shdir DIR" command to show what is (currently) what. Changed "<|" so that if the command has a path, it is expanded using Quagga's rules (including "~~/" and "~./") and the "here" directory is set to that path. Fixed collection of stderr output from all pipes so that is separate from stdout output, and is always sent to the base output (eg VTY Terminal). Increase amount of information about the heap that "show mem" shows -- particularly if the "memory_tracker" is enabled. Tested and applied resulting fixes.
Diffstat (limited to 'lib/vty_io_file.c')
-rw-r--r--lib/vty_io_file.c2057
1 files changed, 1443 insertions, 614 deletions
diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c
index b98dd694..d1d1a50a 100644
--- a/lib/vty_io_file.c
+++ b/lib/vty_io_file.c
@@ -32,22 +32,58 @@
#include "vty_command.h"
#include "network.h"
#include "sigevent.h"
+#include "pthread_safe.h"
+#include "qlib_init.h"
+
+/*==============================================================================
+ * Here we handle:
+ *
+ * * VIN_CONFIG/VOUT_CONFIG -- configuration file read/write
+ *
+ * * VIN_FILE/VOUT_FILE -- file pipe read/write
+ *
+ * * VIN_PIPE/VOUT_PIPE -- shell command pipe read/write
+ */
+
+#define PIPEFILE_MODE CONFIGFILE_MASK
+
+enum
+{
+ file_timeout = 20, /* for file read/write */
+
+ pipe_timeout = 30, /* for pipe read/write */
+
+ child_timeout = 10, /* for collecting child process */
+
+ config_buffer_size = 64 * 1024, /* for config reading */
+ file_buffer_size = 16 * 1024, /* for other file read/write */
+
+ pipe_buffer_size = 4 * 1024, /* for pipe read/write */
+} ;
/*==============================================================================
* VTY Configuration file I/O -- VIN_CONFIG and VOUT_CONFIG.
*
+ * The creation of configuration files is handled elsewhere, as is the actual
+ * opening of input configuration files.
*
+ * Once open, VIN_CONFIG and VOUT_CONFIG are read/written in the same way as
+ * VIN_FILE and VOUT_FILE.
*
+ * These files are handled as "blocking" because the parent vio is marked as
+ * "blocking".
*/
/*------------------------------------------------------------------------------
* Set up VTY on which to read configuration file -- using already open fd.
*
- * Sets TODO
+ * Sets the vout_base to be VOUT_STDERR.
*
- * NB: sets up a blocking vio -- so the vin_base and vout_base will "block"
- * (using local pselect() as required), as will any further vin/vout
- * opened for this vio.
+ * NB: sets up a blocking vio -- so that the vin_base, vout_base and all files
+ * and pipes will block waiting for I/O to complete (or to time out).
+ *
+ * NB: the name is XSTRDUP() into the vio -- so the caller is responsible for
+ * disposing of its copy, if required.
*/
extern vty
vty_config_read_open(int fd, const char* name, bool full_lex)
@@ -68,14 +104,17 @@ vty_config_read_open(int fd, const char* name, bool full_lex)
/* Create the vin_base/vout_base vf
*
- * NB: don't need to specify vfd_io_blocking, because the vio is set blocking.
+ * NB: don't need to specify vfd_io_ps_blocking, because the vio is set
+ * blocking.
*/
- vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ;
+ qassert(vio->blocking) ;
- uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ;
- uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ;
+ vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ;
- uty_vout_sync_depth(vio) ; /* vin & vout are at same level */
+ uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, config_buffer_size) ;
+ vf->read_timeout = file_timeout ;
+ uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, pipe_buffer_size, true) ;
+ vf->write_timeout = file_timeout ;
/* Deal with the possibility that while reading the configuration file, may
* use a pipe, and therefore may block waiting to collect a child process.
@@ -104,15 +143,61 @@ uty_config_read_close(vio_vf vf, bool final)
{
VTY_ASSERT_LOCKED() ;
+ qassert(vf->vin_state == vf_end) ;
+
uty_child_signal_nexus_clear(vf->vio) ;
return CMD_SUCCESS ;
} ;
+/*------------------------------------------------------------------------------
+ * Push the given fd as the VOUT_CONFIG.
+ *
+ * Note that the fd is assumed to be genuine I/O blocking, so this is a
+ * "blocking" vf, and so can be opened and closed in the cmd thread.
+ */
+extern void
+vty_config_write_open(vty vty, int fd)
+{
+ vty_io vio ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+
+ vio = vty->vio ;
+
+ vf = uty_vf_new(vio, "config write", fd, vfd_file,
+ vfd_io_read | vfd_io_blocking) ;
+ uty_vout_push(vio, vf, VOUT_CONFIG, NULL, NULL, file_buffer_size, false) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write away any pending stuff, and pop the VOUT_CONFIG.
+ *
+ * This is a blocking vf -- so any outstanding output will complete here.
+ */
+extern cmd_return_code_t
+vty_config_write_close(struct vty* vty)
+{
+ cmd_return_code_t ret ;
+ VTY_LOCK() ;
+
+ qassert(vty->vio->vout->vout_type == VOUT_CONFIG) ;
+
+ ret = uty_vout_pop(vty->vio, false) ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
/*==============================================================================
* VTY File I/O -- VIN_FILE and VOUT_FILE
+ * also read/write/close for VIN_CONFIG and VOUT_CONFIG.
*
- * This is for input and output of configuration files and piped stuff.
+ * This is for input and output of configuration and piped files.
*
* When reading the configuration (and piped stuff in the configuration) I/O
* is blocking... nothing else can run while this is going on. Otherwise,
@@ -120,18 +205,18 @@ uty_config_read_close(vio_vf vf, bool final)
* I/O is manufactured using a mini-pselect, so can time-out file writing
* before too long.
*/
-static void uty_file_read_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_file_read_timeout(vio_timer timer,
+static void vty_file_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_file_read_timeout(vio_timer timer,
void* action_info) ;
-static void uty_file_write_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_file_write_timeout(vio_timer timer,
+static void vty_file_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_file_write_timeout(vio_timer timer,
void* action_info) ;
static cmd_return_code_t uty_fifo_command_line(vio_vf vf, cmd_action action) ;
/*------------------------------------------------------------------------------
- * Open file for input, to be read as commands.
+ * Open file for input, to be read as commands -- VIN_FILE.
*
* If could not open, issues message to the vio.
*
@@ -154,7 +239,7 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
vio_vf vf ;
vfd_io_type_t iot ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_LOCKED() ;
assert(vio->vin != NULL) ; /* Not expected to be vin_base */
@@ -163,27 +248,30 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
pns = qpath_string(path) ;
- /* Do the basic file open. */
+ /* Do the basic file open. May be vfd_io_ps_blocking, but we don't care
+ * about that here.
+ */
iot = vfd_io_read ;
- fd = uty_vfd_file_open(pns, iot) ;
+ fd = uty_fd_file_open(pns, iot, (mode_t)0) ; /* cmode not required */
+ /* If failed, report.
+ * If OK save context & update "here" then create and push the vin.
+ */
if (fd < 0)
{
- uty_out(vio, "%% Could not open input file %s\n", pns) ;
-
- ret = CMD_WARNING ; /* TODO add errno to message ? */
+ uty_out(vio, "%% Could not open input file %s: %s\n", pns,
+ errtoa(errno, 0).str) ;
+ ret = CMD_WARNING ;
}
else
{
- /* We have a file, so now save context and update dir "here" */
uty_vin_new_context(vio, context, path) ;
- /* OK -- now push the new input onto the vin_stack. */
vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ;
- uty_vin_push(vio, vf, VIN_FILE, uty_file_read_ready,
- uty_file_read_timeout, 16 * 1024) ;
- uty_vf_set_read_timeout(vf, 30) ;
+ uty_vin_push(vio, vf, VIN_FILE, vty_file_read_ready,
+ vty_file_read_timeout, file_buffer_size) ;
+ vf->read_timeout = file_timeout ;
ret = CMD_SUCCESS ;
} ;
@@ -193,10 +281,13 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
} ;
/*------------------------------------------------------------------------------
- * Open file for output.
+ * Open file for output -- VOUT_FILE.
*
* If could not open, issues message to the vio.
*
+ * "after" <=> this vout is being opened after a vin being opened at the same
+ * time.
+ *
* Returns: CMD_SUCCESS -- all set to go
* CMD_WARNING -- failed to open
*
@@ -204,7 +295,8 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
* cli thread -- see uty_vfd_new().
*/
extern cmd_return_code_t
-uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
+uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context,
+ bool after)
{
cmd_return_code_t ret ;
qpath path ;
@@ -213,30 +305,36 @@ uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
vio_vf vf ;
vfd_io_type_t iot ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_LOCKED() ;
/* Now is the time to complete the name, if required. */
path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
pns = qpath_string(path) ;
- /* Do the basic file open. */
+ /* Do the basic file open. May be vfd_io_ps_blocking, but we don't care
+ * about that here.
+ */
iot = vfd_io_write | (append ? vfd_io_append : 0) ;
- fd = uty_vfd_file_open(pns, iot) ;
+ fd = uty_fd_file_open(pns, iot, PIPEFILE_MODE) ;
+ /* If failed, report.
+ * If OK, create and push the vout.
+ */
if (fd < 0)
{
- uty_out(vio, "%% Could not open output file %s\n", pns) ;
-
- ret = CMD_WARNING ; /* TODO add errno to message ? */
+ uty_out(vio, "%% Could not open output file %s: %s\n", pns,
+ errtoa(errno, 0).str) ;
+ ret = CMD_WARNING ;
}
else
{
- /* OK -- now push the new output onto the vout_stack. */
vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ;
- uty_vout_push(vio, vf, VOUT_FILE, uty_file_write_ready,
- uty_file_write_timeout, 16 * 1024) ;
+ uty_vout_push(vio, vf, VOUT_FILE, vty_file_write_ready,
+ vty_file_write_timeout, file_buffer_size, after) ;
+ vf->write_timeout = file_timeout ;
+
ret = CMD_SUCCESS ;
} ;
@@ -245,13 +343,12 @@ uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
} ;
/*------------------------------------------------------------------------------
- * Command line fetch from a file or pipe.
+ * Command line fetch from a file -- VIN_FILE or VIN_CONFIG, in vf_open state.
*
* Returns: CMD_SUCCESS -- have another command line ready to go
* CMD_WAITING -- would not wait for input <=> non-blocking
- * CMD_EOF -- ran into EOF
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ * CMD_EOF -- ran into EOF (and nothing else)
+ * CMD_IO_ERROR -- ran into an I/O error or time-out
*
* In "non-blocking" state, on CMD_WAITING sets read ready, which will
* vty_cmd_signal() the command loop to come round again.
@@ -261,16 +358,23 @@ uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
*
* This can be called in any thread.
*
- * NB: the vin_state will become vf_eof the first time that CMD_EOF is
- * returned.
+ * NB: the vin_state is set to vf_end when CMD_EOF is returned, and this
+ * code may not be called again.
+ *
+ * NB: the vin_state is set to vf_end when CMD_IO_ERROR is returned, and
+ * this code may not be called again.
+ *
+ * When an error occurs it is signalled to the command loop. This function
+ * is called from the command loop -- so, in fact, the CMD_IO_ERROR return
+ * code does the trick.
*/
extern cmd_return_code_t
uty_file_fetch_command_line(vio_vf vf, cmd_action action)
{
VTY_ASSERT_LOCKED() ; /* In any thread */
- assert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ;
- assert((vf->vin_state == vf_open) || (vf->vin_state == vf_eof)) ;
+ qassert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ;
+ qassert(vf->vin_state == vf_open) ;
while (1)
{
@@ -297,11 +401,11 @@ uty_file_fetch_command_line(vio_vf vf, cmd_action action)
break ; /* loop back to uty_fifo_command_line() */
if (get == -1)
- return CMD_IO_ERROR ; /* register error */
+ return uty_vf_error(vf, verr_io_vin, errno) ;
if (get == -2)
{
- /* Hit end of file set the vf into vf_eof.
+ /* Hit end of file, so set the vf into vf_end.
*
* NB: does not know has hit eof until tries to read and nothing
* is returned.
@@ -310,18 +414,18 @@ uty_file_fetch_command_line(vio_vf vf, cmd_action action)
* that we know that there is no more data to come -- it may
* signal CMD_SUCCESS (non-empty final line) or CMD_EOF.
*/
- assert(vio_fifo_empty(vf->ibuf)) ;
+ qassert(vio_fifo_empty(vf->ibuf)) ;
- vf->vin_state = vf_eof ;
+ vf->vin_state = vf_end ;
break ; /* loop back to uty_fifo_command_line() */
} ;
/* Would block -- for non-blocking return CMD_WAITING, for
* blocking we block here with a timeout, and when there is more
- * to read, loop back to
+ * to read, loop back to vio_fifo_read_nb().
*/
- assert(get == 0) ;
+ qassert(get == 0) ;
if (!vf->blocking)
{
@@ -332,23 +436,27 @@ uty_file_fetch_command_line(vio_vf vf, cmd_action action)
/* Implement blocking I/O, with timeout */
qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_read_mnum,
vf->read_timeout) ;
- if (qps_mini_wait(qm, NULL, false) == 0)
- return CMD_IO_TIMEOUT ; /* TODO vf_timeout ?? */
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back to vio_fifo_read_nb() */
- /* Loop back to vio_fifo_read_nb() */
- }
+ return uty_vf_error(vf, verr_to_vin, 0) ;
+ } ;
} ;
} ;
/*------------------------------------------------------------------------------
* Try to complete a command line for the given vf, from the current input
- * fifo -- performs NO I/O
+ * fifo -- performs NO I/O.
+ *
+ * Expects that most of the time will be able to set the vf->cl to point
+ * directly at a command line in the fifo -- but at the edge of fifo buffers
+ * (and if get continuation lines) will copy line fragments to the vf->cl.
*
* Returns: CMD_SUCCESS -- have a command line.
* CMD_EOF -- there is no more
* CMD_WAITING -- waiting for more
*
- * If vf->vin_state == vf_eof, will return CMD_SUCCESS if have last part line
+ * If vf->vin_state == vf_end, will return CMD_SUCCESS if have last part line
* in hand. Otherwise will return CMD_EOF, any number of times.
*/
static cmd_return_code_t
@@ -380,7 +488,7 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
/* If fifo is empty, may be last line before eof, eof or waiting */
if (have == 0)
{
- if (vf->vin_state == vf_eof)
+ if (vf->vin_state == vf_end)
{
if (qs_len_nn(vf->cl) > 0)
break ; /* have non-empty last line */
@@ -391,7 +499,7 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
return CMD_WAITING ;
} ;
- assert(vf->vin_state != vf_eof) ; /* not empty => not eof */
+ qassert(vf->vin_state != vf_end) ; /* not empty => not eof */
/* Try to find a '\n' -- converting all other control chars to ' '
*
@@ -472,7 +580,7 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
} ;
} ;
- /* Now worry about we have a trailing '\'. */
+ /* Now worry about possible trailing '\'. */
if ((p == s) || (*(p-1) != '\\'))
break ; /* no \ => no continuation => success */
@@ -509,16 +617,17 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
} ;
/*------------------------------------------------------------------------------
- * File or pipe is ready to read -- this is the call-back planted in the
- * vf->vfd.
+ * File is ready to read -- call-back for VIN_FILE.
+ *
+ * This is used if the VIN_FILE is non-blocking.
*
- * The command_queue processing can continue.
+ * Signals command loop, so may continue if was waiting.
*
* Note that the read_ready state and any time out are automatically
* disabled when they go off.
*/
static void
-uty_file_read_ready(vio_vfd vfd, void* action_info)
+vty_file_read_ready(vio_vfd vfd, void* action_info)
{
vio_vf vf ;
@@ -528,19 +637,23 @@ uty_file_read_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
+ /* If the vin is no longer open then read ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * File or pipe has timed out, waiting to read -- this is the call-back planted
- * in the vf->vfd.
+ * File has timed out, waiting to read -- call-back for VIN_FILE.
+ *
+ * This is used if the VIN_FILE is non-blocking.
*
- * ????
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_file_read_timeout(vio_timer timer, void* action_info)
+vty_file_read_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -548,9 +661,9 @@ uty_file_read_timeout(vio_timer timer, void* action_info)
VTY_ASSERT_CLI_THREAD() ;
vf = action_info ;
- assert(vf->vfd->write_timer == timer) ;
+ assert(vf->vfd->read_timer == timer) ;
-//cq_continue(vf->vio->vty) ; TODO
+ uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -558,82 +671,99 @@ uty_file_read_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Command output push to a file.
+ * Command output push to a file -- VOUT_FILE or VOUT_CONFIG -- vf_open.
*
- * Until the file becomes vf_closing, will not write the end_lump of the fifo,
- * so all output is in units of the fifo size -- which should be "chunky".
+ * Unless all || final this will not write the end_lump of the fifo, so output
+ * is in units of the fifo size -- which should be "chunky".
*
* Although it is unlikely to happen, if blocking, will block if cannot
* completely write away what is required, or enable write ready.
*
* If final, will write as much as possible, but not block and will ignore
- * any errors -- but the return code will be an error return code.
+ * any errors (but will return an error return code).
*
- * Returns: CMD_SUCCESS -- written everything
- * CMD_WAITING -- could not write everything (<=> non-blocking)
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- not going to wait any longer (<=> blocking)
+ * If an error occurred earlier, then returns immediately (CMD_SUCCESS).
*
- * In "non-blocking" state, on CMD_WAITING the caller will have to take steps
- * to come back later.
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
*
- * In "blocking" state, will not return until have written everything there is,
- * away, or cannot continue.
+ * If "non-blocking", on CMD_WAITING the pselect() background process
+ * will complete the output and signal the result via uty_cmd_signal().
+ *
+ * If "blocking", will not return until have written away everything there is,
+ * or cannot continue.
*
* This can be called in any thread.
*/
extern cmd_return_code_t
-uty_file_out_push(vio_vf vf, bool final)
+uty_file_out_push(vio_vf vf, bool final, bool all)
{
- bool all ;
-
VTY_ASSERT_LOCKED() ; /* In any thread */
- assert((vf->vout_state == vf_open) || (vf->vout_state == vf_closing)) ;
- assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
- all = (vf->vout_state == vf_closing) ;
- /* empty buffers if closing */
- while (1)
+ qassert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+ qassert(vf->vout_state == vf_open) ;
+
+ /* If squelching, dump anything we have in the obuf.
+ *
+ * Otherwise, write contents away.
+ */
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
{
- qps_mini_t qm ;
- int n ;
+ while (1)
+ {
+ qps_mini_t qm ;
+ int n ;
- n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ;
+ n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ;
- if (n < 0)
- return CMD_IO_ERROR ; /* TODO */
+ if (n < 0)
+ return uty_vf_error(vf, verr_io_vout, errno) ;
- if ((n == 0) || final) /* all gone (or as much as can go) */
- return CMD_SUCCESS ;
+ if (n == 0)
+ break ; /* all gone */
- /* Cannot write everything away without waiting */
- if (!vf->blocking)
- {
- uty_vf_set_write(vf, on) ;
- return CMD_WAITING ;
- } ;
+ /* Cannot write everything away without waiting */
+ if (!vf->blocking)
+ {
+ if (!final)
+ uty_vf_set_write(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ if (final)
+ return CMD_WAITING ;
- /* Implement blocking I/O, with timeout */
- qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum, vf->write_timeout) ;
- if (qps_mini_wait(qm, NULL, false) == 0)
- return CMD_IO_TIMEOUT ;
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum,
+ vf->write_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* Loop back to vio_fifo_write_nb() */
- /* Loop back to vio_fifo_write_nb() */
+ return uty_vf_error(vf, verr_to_vout, 0) ;
+ } ;
} ;
+
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * File or pipe is ready to write -- this is the call-back planted in the
- * vf->vfd.
+ * File is ready to write -- call-back for VOUT_FILE.
+ *
+ * This is used if the VOUT_FILE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
*
* Note that the write_ready state and any time out are automatically
* disabled when they go off.
- *
- * Since we have gone to all the trouble of going through pselect, might as
- * well write everything away. This works while the file is being closed, too.
*/
static void
-uty_file_write_ready(vio_vfd vfd, void* action_info)
+vty_file_write_ready(vio_vfd vfd, void* action_info)
{
cmd_return_code_t ret ;
vio_vf vf ;
@@ -644,23 +774,32 @@ uty_file_write_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
- ret = uty_file_out_push(vf, false) ; /* re-enable write ready if required */
+ /* If the vout is no longer open then write ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ if (vf->vout_state == vf_open)
+ {
+ /* Push, not final and not all -- re-enables write ready if required */
+ ret = uty_file_out_push(vf, false, false) ;
+ }
+ else
+ ret = CMD_SUCCESS ;
if (ret != CMD_WAITING)
- uty_cmd_signal(vf->vio, ret) ;
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * File or pipe is ready to write -- this is the call-back planted in the
- * vf->vfd.
+ * File has timed out, waiting to write -- call-back for VOUT_FILE.
*
- * Note that the read_ready state and any time out are automatically
- * disabled when they go off.
+ * This is used if the VOUT_FILE is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_file_write_timeout(vio_timer timer, void* action_info)
+vty_file_write_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -670,7 +809,7 @@ uty_file_write_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->vfd->write_timer == timer) ;
-//uty_file_out_push(vf) ; // TODO ???????
+ uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -678,76 +817,279 @@ uty_file_write_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Tidy up after input file has been closed.
+ * Tidy up after input file has been closed -- VIN_FILE.
*
* Nothing further required -- input comes to a halt.
+ *
+ * Returns: CMD_SUCCESS -- at all times
*/
extern cmd_return_code_t
uty_file_read_close(vio_vf vf, bool final)
{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_type == VIN_FILE) ;
+
return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Flush output buffer and close.
+ * Flush output buffer ready for close -- VOUT_FILE and VOUT_CONFIG.
*
* See uty_file_out_push()
*
- * Returns: CMD_SUCCESS -- buffers are empty, or final
- * CMD_WAITING -- waiting for I/O to complete
- * CMD_xxx TODO
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
*/
extern cmd_return_code_t
-uty_file_write_close(vio_vf vf, bool final, bool base)
+uty_file_write_close(vio_vf vf, bool final)
{
VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- assert(vf->vout_state == vf_closing) ;
- assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
- return uty_file_out_push(vf, final) ;
+ qassert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+
+ if (vf->vout_state == vf_open)
+ return uty_file_out_push(vf, final, true) ; /* write all */
+ else
+ return CMD_SUCCESS ;
} ;
/*==============================================================================
- * Shell pipe stuff
+ * VTY Pipe I/O -- VIN_PIPE, VOUT_PIPE & VOUT_SH_CMD
+ *
+ * This is for input/output from/to shell commands.
+ *
+ * This is complicated by (a) the existence of up to 3 streams of data, and
+ * (b) the extra step of collecting the child and its return code on close.
+ *
+ * The three streams of data, from the perspective of the shell command, are:
+ *
+ * stdin -- input from Quagga (if any)
+ *
+ * stdout -- output to Quagga -- may be treated as command lines
+ *
+ * stderr -- output to Quagga -- assumed to be diagnostic information
+ *
+ * There are three variants of pipe I/O:
+ *
+ * * VIN_PIPE -- command line: <| shell command
+ *
+ * stdin -- none
+ *
+ * stdout -- read by Quagga as command lines
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * * VOUT_PIPE -- command line: .... >| shell command
+ *
+ * stdin -- the Quagga command(s) output
+ *
+ * stdout -- collected by Quagga and output to the next vout.
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * * VOUT_SH_CMD -- command line: | shell_command
+ *
+ * stdin -- none
+ *
+ * stdout -- collected by Quagga and output to the next vout.
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * When Quagga is collecting stdout for output, that is known as the
+ * "pipe return", and is expected to be the result of the command.
+ *
+ * In all cases the stderr is assumed to be diagnostic information, which is
+ * known as the "pipe stderr return". This is collected in the vf->ps_buf.
+ * When the pipe is closed, any stderr return is then appended to the
+ * vio->ps_buf. When the output stack is closed down to the base vout, the
+ * contents of the vio->ps_buf are transferred to the vio->obuf. This means
+ * that:
+ *
+ * 1. all pipe stderr return for a given pipe is collected and output
+ * all together to the base vout (eg the VTY_TERMINAL). The output is
+ * bracketted so:
+ *
+ * %[--- 'name':
+ * ....
+ * ....
+ * %---]
+ *
+ * to give context.
+ *
+ * 2. the pipe stderr return is not mixed up with any other pipe or other
+ * command output, and in particular cannot be piped to another pipe.
+ *
+ * 3. but... if VOUT_PIPE are stacked on top of each other, it may be a
+ * while before the pipe stderr return stuff reaches the base vout.
+ *
+ * The problem here is that if one VOUT_PIPE is stacked above another,
+ * the lower pipe may or may not be in the middle of something when
+ * the upper pipe is closed -- so to avoid a mix-up, must wait until
+ * the all lower pipes close before the pipe stderr is finally output
+ * (along with all other pending stderr return).
+ *
+ * While the main vin/vout is open, the pipe I/O is handled as follows:
+ *
+ * * VIN_PIPE
+ *
+ * The shell command's stdout is connected to the vf's vin, and is read
+ * as command lines, much as VIN_FILE. A read timeout is set, to deal
+ * with shell commands which take an unreasonable time.
+ *
+ * The shell command's stderr is connected to the vf's pipe stderr
+ * return. For non-blocking, that is read into the vf->ps_buf, by the
+ * pselect() process. For blocking, that is read in parallel with the
+ * main vin. No timeout is set until the main vin is closed.
+ *
+ * * VOUT_PIPE
+ *
+ * The shell command's stdin is connected to the vf's vout, and is
+ * written to much like VOUT_FILE. A write timeout is set, to deal
+ * with shell commands which take an unreasonable time.
+ *
+ * The shell command's stdout is connected to the vf's pipe return.
+ * For non-blocking vf, the pipe return is read autonomously under
+ * pselect() and pushed to the next vout. For blocking vf, the pipe
+ * return is polled whenever the (main) vout is written to, and any
+ * available input is pushed to the slave vout. No timeout is set
+ * until the main vout is closed.
+ *
+ * The shell command's stderr is connected to the vf's pipe stderr
+ * return. For non-blocking, that is read into the vf->ps_buf, by the
+ * pselect() process. For blocking, that is read in parallel with the
+ * pipe return. No timeout is set until the main vout is closed.
+ *
+ * * VOUT_SH_CMD
*
+ * The shell command's stdin is set empty, and the vf's vout set to
+ * vf_closed.
+ *
+ * Otherwise, this is the same as VOUT_PIPE.
+ *
+ * The closing of a VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is a little involved:
+ *
+ * 0. close the main vin/vout.
+ *
+ * for a VIN_PIPE the vin_state will be:
+ *
+ * vf_end -- eof met or forced by an early close, or error or timeout
+ * occurred
+ *
+ * So phase 0 finishes immediately.
+ *
+ * for a VOUT_PIPE the vout_state will be:
+ *
+ * vf_open -- until all output completes or hits error or timeout
+ * vf_end -- nothing more to be output, or error or timeout occurred
+ *
+ * If vf_open, must wait until no longer vf_open, ie until all pending
+ * output completes, or hits and error or timeout.
+ *
+ * As soon as is no longer vf_open, must close the vfd to signal to the
+ * child that is now at eof.
+ *
+ * for a VOUT_SH_CMD the vout_state will be:
+ *
+ * vf_end -- nothing to be output.
+ *
+ * So phase 0 finishes immediately.
+ *
+ * 1. collect remaining input and close the pipe return.
+ *
+ * for a VIN_PIPE there is no pipe return.
+ *
+ * for a VOUT_PIPE or VOUT_SH_CMD, must empty the pipe return and then
+ * close it -- stopping immediately if gets error or time-out on either
+ * the pipe return or the slave vout.
+ *
+ * Note that up to this point no time-out has been set on the pipe return,
+ * since there is no need for there to be any input. But expect now to
+ * see at least eof in a reasonable time.
+ *
+ * For non-blocking vf, the pr_vfd is set read ready. Then for all
+ * non-blocking pipes, the remaining pipe return input proceeds in the
+ * pselect() process.
+ *
+ * For blocking pipes, the remaining pipe return must complete (or
+ * time-out) during the close operation.
+ *
+ * 2. collect remaining input and close the pipe stderr return.
+ *
+ * For non-blocking vf, the ps_vfd is set read ready. Then for all
+ * non-blocking pipes, the remaining pipe return input proceeds in the
+ * pselect() process -- complete with time-out.
+ *
+ * For blocking pipes, the remaining pipe return must complete (or
+ * time-out) during the close operation.
+ *
+ * 3. once all input has been collected and pr_vfd and ps_vfd are closed,
+ * need to collect the child, so that we can check the return code.
+ *
+ * This may time out.
+ *
+ * If the return code is not 0, or anything else happens, a diagnostic
+ * message is appended to the vf->rbuf.
+ *
+ * For non-blocking vf the close may return CMD_WAITING, and must be called
+ * again later to progress the close.
+ *
+ * For "final" close will not block or return CMD_WAITING, but complete the
+ * process as quickly as possible while outputting as much as possible.
*/
typedef int pipe_pair_t[2] ;
typedef int* pipe_pair ;
-enum pipe_half
+typedef enum pipe_half
{
in_fd = 0,
out_fd = 1,
-} ;
-typedef enum pipe_half pipe_half_t ;
+
+} pipe_half_t ;
CONFIRM(STDIN_FILENO == 0) ;
CONFIRM(STDOUT_FILENO == 1) ;
CONFIRM(STDERR_FILENO == 2) ;
-enum
+typedef enum std_id
{
stdin_fd = STDIN_FILENO,
stdout_fd = STDOUT_FILENO,
stderr_fd = STDERR_FILENO,
stds = 3
-} ;
-enum pipe_id
+} std_id_t ;
+
+typedef enum pipe_id
{
in_pipe,
out_pipe,
- ret_pipe,
+ err_pipe,
pipe_count
-} ;
-typedef enum pipe_id pipe_id_t ;
+
+} pipe_id_t ;
+
+typedef enum pipe_type
+{
+ vin_pipe,
+ vout_pipe,
+ vout_sh_cmd,
+
+} pipe_type_t ;
typedef pipe_pair_t pipe_set[pipe_count] ;
static pid_t uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes,
- pipe_id_t type) ;
+ pipe_type_t type) ;
static bool uty_pipe_pair(vty_io vio, const char* cmd_str,
const char* what, pipe_set pipes,
pipe_id_t id,
@@ -757,34 +1099,32 @@ static bool uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
pipe_pair pair,
const char* action) ;
static void uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half) ;
-static void uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd,
- vio_vf slave) ;
+static void uty_pipe_open_complete(vio_vf vf, pid_t pid, int pr_fd, int ps_fd) ;
static bool uty_pipe_exec_prepare(vty_io vio, pipe_set pipes) ;
static cmd_return_code_t uty_pipe_return_close(vio_vf vf, bool final) ;
-static cmd_return_code_t uty_pipe_return_empty(vio_vf vf, bool final) ;
-static cmd_return_code_t uty_pipe_collect_child(vio_vf vf, bool final) ;
-static cmd_return_code_t uty_pipe_release_slave(vio_vf vf, bool final) ;
-
-static cmd_return_code_t uty_pipe_shovel(vio_vf vf, bool final, bool closing) ;
+static cmd_return_code_t uty_pipe_shovel(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_stderr_suck(vio_vf vf, bool final) ;
+static vio_fifo uty_pipe_ps_buf(vio_vf vf) ;
-static void uty_pipe_read_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_pipe_read_timeout(vio_timer timer,
+static void vty_pipe_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_pipe_read_timeout(vio_timer timer,
void* action_info) ;
-static void uty_pipe_return_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_pipe_return_timeout(vio_timer timer,
+static void vty_pipe_return_ready(vio_vfd vfd, void* action_info) ;
+static void vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_pipe_return_timeout(vio_timer timer,
void* action_info) ;
-static void uty_pipe_write_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_pipe_write_timeout(vio_timer timer,
+static vty_timer_time vty_pipe_stderr_return_timeout(vio_timer timer,
+ void* action_info) ;
+static void vty_pipe_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_pipe_write_timeout(vio_timer timer,
void* action_info) ;
/*------------------------------------------------------------------------------
- * Open pipe whose child's stdout is going to be read and executed as commands.
- *
- * The child's stderr is read, separately, and that is sent to the current
- * vout.
+ * Open VIN_PIPE: pipe whose child's stdout is read and executed as commands.
*
- * If could not open, issues message to the vio.
+ * The child's stderr is read, separately, as the pipe return, and sent to the
+ * current vout.
*
* The new vin has two vio_fd's, the standard one to read commands, and the
* other (the pr_vfd) to collect any stderr output (the "return") from the
@@ -793,29 +1133,10 @@ static vty_timer_time uty_pipe_write_timeout(vio_timer timer,
* The current vout is made the "slave" of the new vin "master". For the
* return the pr_vd reads into the "slave" obuf.
*
- * There are three tricky bits:
- *
- * - while reading from the main vfd, also reads from the return vfd.
- *
- * Up to eof on the main vfd, reads from the return vfd whenever tries
- * to fetch a command. Once a command is fetched, will not shovel anything
- * further into the slave obuf until the next command is fetched. This
- * means that return stuff is output *between* commands.
- *
- * - in "blocking" state, will not block on the return unless blocks on the
- * main vfd, or the main vfd is at eof.
- *
- * in "non-blocking" state, will set read ready on the main vfd, until that
- * reaches eof, in which case sets read ready on the return vfd (if that
- * has not yet reached eof).
- *
- * - the close operation will not succeed until the return vfd reaches EOF,
- * or is a "final" close.
- *
* If could not open, issues message to the vio.
*
* Returns: CMD_SUCCESS -- all set to go
- * CMD_WARNING -- failed to open
+ * CMD_WARNING -- failed to open -- message sent to vio.
*/
extern cmd_return_code_t
uty_pipe_read_open(vty_io vio, qstring command, cmd_context context)
@@ -824,105 +1145,110 @@ uty_pipe_read_open(vty_io vio, qstring command, cmd_context context)
const char* cmd_str ;
vio_vf vf ;
pid_t child ;
+ qpath dir ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_LOCKED() ;
assert(vio->vin != NULL) ; /* Not expected to be vin_base */
cmd_str = qs_make_string(command) ;
/* Fork it */
- child = uty_pipe_fork(vio, cmd_str, pipes, in_pipe) ;
+ child = uty_pipe_fork(vio, cmd_str, pipes, vin_pipe) ;
if (child < 0)
return CMD_WARNING ;
/* We have a pipe, so now save context */
- uty_vin_new_context(vio, context, NULL) ;
+ dir = NULL ;
+
+ if (*cmd_str == '/')
+ {
+ const char* p ;
+ p = cmd_str ;
+ while (*p > ' ')
+ ++p ;
+ dir = qpath_set_n(NULL, cmd_str, p - cmd_str) ;
+ } ;
+
+ uty_vin_new_context(vio, context, dir) ;
+
+ if (dir != NULL)
+ qpath_free(dir) ;
/* OK -- now push the new input onto the vin_stack. */
vf = uty_vf_new(vio, cmd_str, pipes[in_pipe][in_fd], vfd_pipe, vfd_io_read) ;
- uty_vin_push(vio, vf, VIN_PIPE, uty_pipe_read_ready,
- uty_pipe_read_timeout, 4 * 1024) ;
+ uty_vin_push(vio, vf, VIN_PIPE, vty_pipe_read_ready,
+ vty_pipe_read_timeout, pipe_buffer_size) ;
+ vf->read_timeout = pipe_timeout ;
/* And the err_pair is set as the return from the child */
- uty_pipe_open_complete(vf, child, pipes[ret_pipe][in_fd], vio->vout) ;
+ uty_pipe_open_complete(vf, child, -1, pipes[err_pipe][in_fd]) ;
return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Open pipe which is going to be written to (or not, if shell_only), and
- * what the pipe returns will be output to the current vout.
+ * Open VOUT_PIPE or VOUT_SH_CMD: pipe which is going to be written to
+ * (or not, if shell_cmd), where what the pipe returns will be output to the
+ * current vout.
*
* The new vout becomes the "master", and has two vio_vfd's, one for output to
* the pipe (this is NULL if shell_only) and the other for the pipe return.
* The pipe return reads directly into the "slave" obuf.
*
- * There are three tricky bits:
- *
- * - while writing to the output vfd, also reads from the return vfd.
- *
- * This is to avoid any possibility that the child process blocks because
- * it cannot write to the return pipe.
- *
- * - when closing the master pipe, it must remain open up to the time it
- * gets eof on the return pipe.
- *
- * This means that the all return output is collected before any further
- * output to the slave can be done.
- *
- * - in "blocking" state, reads from the return only when writes to the
- * pipe, and when closing the pipe.
- *
- * in "non-blocking" state, reads from the return and pushes to the slave
- * under pselect... so sucks and blows at its own rate.
+ * If could not open, issues message to the vio.
*
* Returns: CMD_SUCCESS -- all set to go
* CMD_WARNING -- failed to open -- message sent to vio.
*/
extern cmd_return_code_t
-uty_pipe_write_open(vty_io vio, qstring command, bool shell_only)
+uty_pipe_write_open(vty_io vio, qstring command, bool shell_cmd, bool after)
{
pipe_set pipes ;
const char* cmd_str ;
pid_t child ;
vio_vf vf ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_LOCKED() ;
cmd_str = qs_make_string(command) ;
/* Do the basic file open. */
- child = uty_pipe_fork(vio, cmd_str, pipes, shell_only ? ret_pipe : out_pipe) ;
+ child = uty_pipe_fork(vio, cmd_str, pipes, shell_cmd ? vout_sh_cmd
+ : vout_pipe) ;
if (child < 0)
return CMD_WARNING ;
- /* OK -- now push the new output onto the vout_stack. */
-
- if (shell_only)
+ /* OK -- now push the new output onto the vout_stack.
+ *
+ * Note that for VOUT_SH_CMD no vfd is set up, and the vout_state is
+ * immediately set to vf_end.
+ */
+ if (shell_cmd)
{
vf = uty_vf_new(vio, cmd_str, -1, vfd_none, vfd_io_none) ;
- uty_vout_push(vio, vf, VOUT_SHELL_ONLY, NULL, NULL, 0) ;
+ uty_vout_push(vio, vf, VOUT_SH_CMD, NULL, NULL, 0, after) ;
+ vf->vout_state = vf_end ;
}
else
{
vf = uty_vf_new(vio, cmd_str, pipes[out_pipe][out_fd],
vfd_pipe, vfd_io_write) ;
- uty_vout_push(vio, vf, VOUT_PIPE, uty_pipe_write_ready,
- uty_pipe_write_timeout, 4 * 1024) ;
+ uty_vout_push(vio, vf, VOUT_PIPE, vty_pipe_write_ready,
+ vty_pipe_write_timeout, pipe_buffer_size, after) ;
+ vf->write_timeout = pipe_timeout ;
} ;
/* Record the child pid and set up the pr_vf and enslave vout_next */
- uty_pipe_open_complete(vf, child, pipes[ret_pipe][in_fd], vf->vout_next) ;
-
- vf->pr_only = shell_only ;
-
- /* If not blocking start up the return pipe reader. */
-
- if (!vf->blocking)
- vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */
+ uty_pipe_open_complete(vf, child, pipes[in_pipe][in_fd],
+ pipes[err_pipe][in_fd]) ;
+ /* Until eof (or error or timeout) on the vin, neither wait for or timeout
+ * the pipe return or the pipe stderr return.
+ */
+ qassert(vf->pr_timeout == 0) ;
+ qassert(vf->ps_timeout == 0) ;
return CMD_SUCCESS ;
} ;
@@ -931,38 +1257,60 @@ uty_pipe_write_open(vty_io vio, qstring command, bool shell_only)
* Complete the opening of a pipe.
*
* All pipes have a return. For in_pipes this is the child's stderr, for
- * out_pipes this is the child's combined stdout/stderr.
+ * out_pipes this is the child's stdout and its stderr.
*
- * VIN_PIPE sucks the return in between reading command lines.
- *
- * VOUT_PIPE/VOUT_SHELL_ONLY suck the return at the same time as writing to
- * the child.
- *
- * Sets pr_only false.
+ * For non-blocking, sets up the pipe return and the pipe stderr return ready
+ * and timeout actions, but leaves pr_timeout == 0 and ps_timeout == 0
*/
static void
-uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
+uty_pipe_open_complete(vio_vf vf, pid_t pid, int pr_fd, int ps_fd)
{
vfd_io_type_t iot ;
vf->child = uty_child_register(pid, vf) ;
- /* And configure the pipe return vfd. */
- iot = vfd_io_read | (vf->blocking ? vfd_io_blocking : 0) ;
+ iot = vfd_io_read | (vf->blocking ? vfd_io_ps_blocking : 0) ;
- vf->pr_vfd = vio_vfd_new(ret_fd, vfd_pipe, iot, vf) ;
- vf->pr_state = vf_open ;
+ /* If there is a pipe return, set up vfd and for non-blocking prepare the
+ * read ready and read timeout actions.
+ *
+ * Note that do not at this stage set a timeout value, but do set the return
+ * read ready, to proceed asynchronously.
+ */
+ if (pr_fd >= 0)
+ {
+ vf->pr_vfd = vio_vfd_new(pr_fd, vfd_pipe, iot, vf) ;
+ vf->pr_state = vf_open ;
- vf->pr_only = false ;
+ qassert(vf->pr_timeout == 0) ;
- if (!vf->blocking)
- vio_vfd_set_read_action(vf->pr_vfd, uty_pipe_return_ready) ;
+ if (!vf->blocking)
+ {
+ vio_vfd_set_read_action(vf->pr_vfd, vty_pipe_return_ready) ;
+ vio_vfd_set_read_timeout_action(vf->pr_vfd, vty_pipe_return_timeout) ;
+
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+ } ;
+ } ;
+
+ /* Set up vfd for pipe stderr return and for non-blocking prepare the
+ * read ready and read timeout actions.
+ *
+ * Note that do not at this stage set a timeout value, or set the return
+ * read ready.
+ */
+ vf->ps_vfd = vio_vfd_new(ps_fd, vfd_pipe, iot, vf) ;
+ vf->ps_state = vf_open ;
- vio_vfd_set_read_timeout_action(vf->pr_vfd, uty_pipe_return_timeout) ;
+ qassert(vf->ps_timeout == 0) ;
- /* Configure master/slave relationship. */
- slave->pr_master = vf ;
- vf->pr_slave = slave ;
+ if (!vf->blocking)
+ {
+ vio_vfd_set_read_action(vf->ps_vfd, vty_pipe_stderr_return_ready) ;
+ vio_vfd_set_read_timeout_action(vf->ps_vfd, vty_pipe_stderr_return_timeout) ;
+
+ vio_vfd_set_read(vf->ps_vfd, on, vf->ps_timeout) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
@@ -971,13 +1319,15 @@ uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
* Set up pipes according to type of fork:
*
* in_pipe -- input from child's stdout as main fd
- * input from child's stderr as return fd
+ * input from child's stderr as stderr return fd
*
* out_pipe -- output to child's stdin as main fd
- * input from child's stderr & stdout as return fd
+ * input from child's stdout as return fd
+ * input from child's stderr as stderr return fd
*
- * ret_pipe -- nothing for main fd
- * input from child's stderr & stdout as return fd
+ * err_pipe -- nothing for main fd
+ * input from child's stdout as return fd
+ * input from child's stderr as stderr return fd
*
* vfork to create child process and then:
*
@@ -985,9 +1335,14 @@ uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
*
* -- in child, close all unused fds, and move relevant part(s) of pair(s)
* to stdin, stdout and stderr, sort out signal, set pwd then exec sh -c.
+ *
+ * Returns: > 0 -- OK, this is child pid
+ * < 0 -- Failed
+ *
+ * NB: only returns in the parent process -- exec's in the child.
*/
static pid_t
-uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
+uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_type_t type)
{
pid_t child ;
int id ;
@@ -1000,15 +1355,19 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
} ;
/* Open as many pipes as are required. */
- if (type == in_pipe)
+ if (type == vin_pipe)
if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd))
return -1 ;
- if (type == out_pipe)
+ if ((type == vout_pipe) || (type == vout_sh_cmd))
+ if (!uty_pipe_pair(vio, cmd_str, "return pipe", pipes, in_pipe, in_fd))
+ return -1 ;
+
+ if (type == vout_pipe)
if (!uty_pipe_pair(vio, cmd_str, "output pipe", pipes, out_pipe, out_fd))
return -1 ;
- if (!uty_pipe_pair(vio, cmd_str, "return pipe", pipes, ret_pipe, in_fd))
+ if (!uty_pipe_pair(vio, cmd_str, "stderr pipe", pipes, err_pipe, in_fd))
return -1 ;
/* Off to the races */
@@ -1017,7 +1376,6 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
if (child == 0) /* In child */
{
-
/* Prepare all file descriptors and then execute */
if (uty_pipe_exec_prepare(vio, pipes))
execl("/bin/bash", "bash", "-c", cmd_str, NULL) ; /* does not return */
@@ -1029,7 +1387,7 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
/* close the pipe fds we do not need */
uty_pipe_close_half_pipe(pipes[in_pipe], out_fd) ;
uty_pipe_close_half_pipe(pipes[out_pipe], in_fd) ;
- uty_pipe_close_half_pipe(pipes[ret_pipe], out_fd) ;
+ uty_pipe_close_half_pipe(pipes[err_pipe], out_fd) ;
}
else if (child < 0) /* In parent -- failed */
uty_pipe_fork_fail(vio, cmd_str, "vfork", pipes, NULL, "child") ;
@@ -1039,7 +1397,9 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
/*------------------------------------------------------------------------------
* Open a pipe pair -- generate suitable error message if failed and close
- * any early pipes that have been opened.
+ * any earlier pipes that have been opened.
+ *
+ * Returns: true <=> success
*/
static bool
uty_pipe_pair(vty_io vio, const char* cmd_str,
@@ -1055,15 +1415,17 @@ uty_pipe_pair(vty_io vio, const char* cmd_str,
return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair, "open") ;
if (set_nonblocking(pair[half]) < 0)
- return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair,
- "set non-blocking") ;
+ return uty_pipe_fork_fail(vio, cmd_str, what, pipes, NULL,
+ "set non-blocking for") ;
return true ;
} ;
/*------------------------------------------------------------------------------
- * Open a pipe pair -- generate suitable error message if failed and close
- * any early pipes that have been opened.
+ * Failed to open pipe: generate suitable error message and close any earlier
+ * pipes that have been opened.
+ *
+ * Returns: false
*/
static bool
uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
@@ -1114,27 +1476,30 @@ uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half)
*
* in_pipe -- if present, is: stdout for the child
* out_pipe -- if present, is: stdin for the child
- * ret_pipe -- if present, is: stderr for the child
+ * err_pipe -- if present, is: stderr for the child
* and: stdout for the child, if no in_pipe
*
* Set current directory.
*
* Reset all signals to default state and unmask everything.
+ *
+ * Returns: true <=> good to go
+ * false => some sort of error -- see errno
*/
static bool
uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
{
- int std[stds] ;
- int fd ;
+ int std[stds] ;
+ int fd ;
/* Assign fds to child's std[] */
std[stdin_fd] = pipes[out_pipe][in_fd] ; /* stdin for child */
std[stdout_fd] = pipes[in_pipe][out_fd] ; /* stdout for child */
- std[stderr_fd] = pipes[ret_pipe][out_fd] ; /* stderr for child */
+ std[stderr_fd] = pipes[err_pipe][out_fd] ; /* stderr for child */
/* Mark everything to be closed on exec */
- for (fd = 0 ; fd <= 1024 ; ++fd) /* TODO -- max_fd */
+ for (fd = 0 ; fd < qlib_open_max ; ++fd)
{
int fd_flags ;
fd_flags = fcntl(fd, F_GETFD, 0) ;
@@ -1152,7 +1517,7 @@ uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
for (fd = 0 ; fd < stds ; ++fd)
{
if ((std[fd] >= 0) && (std[fd] < stds))
- if ((std[fd] = fcntl(std[fd], F_DUPFD, stds)) < 0)
+ if ((std[fd] = fcntl(std[fd], F_DUPFD_CLOEXEC, stds)) < 0)
return false ;
} ;
@@ -1184,38 +1549,45 @@ uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
} ;
/*------------------------------------------------------------------------------
- * Command line fetch from a pipe and (for blocking) shovel return into
- * slave and push.
+ * Command line fetch from a pipe -- VIN_PIPE in vf_open state.
+ *
+ * Before attempting to fetch command line, will shovel any return into the
+ * slave and push. This means that any return is output between command lines.
*
* In "non-blocking" state, on CMD_WAITING may be waiting for read ready on
* this vf, or (indirectly) write ready on the slave.
*
* In "blocking" state, will not return until have something, or there is
- * nothing to be had.
- *
- * Returns: CMD_SUCCESS -- have another command line ready to go
- * CMD_WAITING -- would not wait for input <=> non-blocking
- * CMD_EOF -- ran into EOF -- on input AND return
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ * nothing to be had, or times out.
*
- * If returns CMD_EOF will have vf->vin_state == vf_eof. Further, will not
- * have vf_eof until returns CMD_EOF (the first time).
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- waiting for input not output <=> non-blocking
+ * CMD_EOF -- ran into EOF -- on input
+ * CMD_IO_ERROR -- ran into an I/O error or time-out
*
* This can be called in any thread.
*
- * NB: once this has signalled CMD_EOF (on the main input) the close must deal
- * with sucking up any remaining return.
+ * NB: the vin_state is set to vf_end when CMD_EOF is returned, and this
+ * code may not be called again.
+ *
+ * Signals CMD_EOF on the main input. If this occurs before EOF on the
+ * return input, any remaining return input must be dealt with before
+ * the vf is finally closed -- see uty_pipe_read_close().
+ *
+ * NB: the vout_state is set to vf_end when CMD_IO_ERROR is returned, and
+ * this code may not be called again.
+ *
+ * When an error occurs it is signalled to the command loop. This function
+ * is called from the command loop -- so, in fact, the CMD_IO_ERROR
+ * return code does the trick.
*/
extern cmd_return_code_t
uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
{
VTY_ASSERT_LOCKED() ; /* In any thread */
- assert(vf->vin_type == VIN_PIPE) ;
- assert((vf->vin_state == vf_open) || (vf->vin_state == vf_eof)) ;
-
- /* TODO police the state of the two vfs ? */
+ qassert(vf->vin_type == VIN_PIPE) ;
+ qassert(vf->vin_state == vf_open) ;
while (1) /* so blocking stuff can loop round */
{
@@ -1229,47 +1601,46 @@ uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
*/
ret = uty_fifo_command_line(vf, action) ;
- if ((ret == CMD_SUCCESS) || (ret == CMD_EOF))
+ if (ret != CMD_WAITING)
return ret ;
- /* Worry about the return.
+ /* If blocking, worry about the stderr return -- just to keep I/O moving.
*
- * If the child is outputting to both stderr and stdout, we have no
- * way of telling the order the data was sent in -- but we treat
- * stderr as a higher priority !
- *
- * Expect only CMD_SUCCESS or CMD_WAITING (non-blocking), CMD_IO_ERROR
- * or CMD_IO_TIMEOUT (blocking).
+ * Expect only CMD_SUCCESS or CMD_IO_ERROR.
*/
- if (vf->blocking && (vf->pr_state == vf_open))
+ if (vf->blocking && (vf->ps_state == vf_open))
{
- ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
- if ((ret != CMD_SUCCESS) && (ret != CMD_EOF))
- return ret ; /* cannot continue */
- } ;
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
- /* Need more from the main input. */
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
+ /* Need more from the main input.
+ */
get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ;
if (get > 0)
continue ; /* loop back */
if (get == -1)
- return CMD_IO_ERROR ; /* register error */
+ return uty_vf_error(vf, verr_io_vin, errno) ;
if (get == -2) /* EOF met immediately */
{
- vf->vin_state = vf_eof ;
+ vf->vin_state = vf_end ;
continue ; /* loop back -- deals with possible
final line and the return. */
} ;
- assert (get == 0) ;
+ qassert(get == 0) ;
- /* We get here if main input is not yet at eof. */
- assert ((vf->vin_state == vf_open) || (vf->pr_state == vf_open)) ;
+ /* We get here if main input is not yet at eof, but has nothing
+ * to read at the moment.
+ */
+ qassert(vf->vin_state == vf_open) ;
if (!vf->blocking)
{
@@ -1277,17 +1648,20 @@ uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
return CMD_WAITING ;
} ;
- /* Implement blocking I/O, with timeout */
+ /* Implement blocking I/O, with timeout
+ *
+ * Note that waits for both the main vin and the pipe return.
+ */
qps_mini_set(qm, (vf->vin_state == vf_open) ? vio_vfd_fd(vf->vfd)
: -1,
qps_read_mnum, vf->read_timeout) ;
if (vf->pr_state == vf_open)
qps_mini_add(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum) ;
- if (qps_mini_wait(qm, NULL, false) == 0)
- return CMD_IO_TIMEOUT ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
- continue ; /* loop back */
+ return uty_vf_error(vf, verr_to_vin, 0) ;
} ;
} ;
@@ -1295,27 +1669,25 @@ uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
* Command output push to a pipe and (for blocking) shovel return into
* slave and push.
*
- * Does not push to the pipe while there is return input to be read. For
- * blocking I/O may block in the slave output. For non-blocking I/O, will
- * return if would block in the slave output.
- *
- * Until the file becomes vf_closing, will not write the end_lump of the fifo,
- * so all output is in units of the fifo size -- which should be "chunky".
- *
- * Although it is unlikely to happen, if blocking, will block if cannot
- * completely write away what is required, or enable write ready.
+ * For blocking, does not push to the pipe while there is return input to be
+ * read and pushed to the slave. For non-blocking the return input/output is
+ * handled autonomously.
*
* If final, will do final shovel from return to slave and also attempt to
* empty any output buffer -- will not wait or block, and ignores errors.
*
- * Returns: CMD_SUCCESS -- written everything
- * CMD_WAITING -- could not write everything (<=> non-blocking)
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- not going to wait any longer (<=> blocking)
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
*
* In "non-blocking" state, on CMD_WAITING the slave output will put the
* master return pr_vfd into read ready state when it sees write ready, or will
- * here set the pr_vfd into read ready state.
+ * here set the pr_vfd into read ready state. This requires no further action
+ * from the caller, the background pselect process will complete the output and
+ * may signal the result via uty_cmd_signal().
*
* In "blocking" state, will not return until have written everything there is,
* away, or cannot continue.
@@ -1326,58 +1698,91 @@ extern cmd_return_code_t
uty_pipe_out_push(vio_vf vf, bool final)
{
VTY_ASSERT_LOCKED() ; /* In any thread */
- assert((vf->vout_state == vf_open) || (vf->vout_state == vf_closing)) ;
- assert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SHELL_ONLY)) ;
- /* If blocking, see if there is anything in the return, and shovel.
- *
- * For non-blocking, the pselect process looks after this.
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ if (vf->vout_state != vf_open)
+ return CMD_SUCCESS ; /* Get out if going nowhere */
+
+ /* If blocking, keep the stderr return moving.
*/
- if (vf->blocking && (vf->pr_state == vf_open))
+ if (vf->blocking && (vf->ps_state == vf_open))
{
cmd_return_code_t ret ;
- ret = uty_pipe_shovel(vf, final, false) ; /* not closing */
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
- if ((ret != CMD_SUCCESS) && (ret != CMD_EOF) && !final)
- return ret ;
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
} ;
- /* Now write away everything we can -- nothing if pr_only.
+ /* If squelching, dump anything we have in the obuf.
+ *
+ * Otherwise, write contents away.
*/
- if (!vf->pr_only)
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
{
- bool all ;
-
- all = (vf->vout_state == vf_closing) ;
-
while (1)
{
qps_mini_t qm ;
int put ;
- put = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all) ;
+ /* If blocking, see if there is anything in the return, and shovel.
+ *
+ * For non-blocking, the pselect process looks after this.
+ */
+ if (vf->blocking && (vf->pr_state == vf_open))
+ {
+ cmd_return_code_t ret ;
- if (put < 0)
- return CMD_IO_ERROR ; /* TODO */
+ ret = uty_pipe_shovel(vf, final) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
- if ((put == 0) || final) /* all gone or final */
+ if (vf->vout_state != vf_open)
+ break ; /* Quit if error has stopped the main vout */
+
+ /* Now write to the main vout, blocking if required.
+ */
+ put = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) ;
+
+ if (put == 0) /* all gone */
break ;
- /* Cannot write everything away without waiting */
+ if (put < 0)
+ return uty_vf_error(vf, verr_io_vout, errno) ;
+
+ /* Cannot write everything away without waiting
+ */
if (!vf->blocking)
{
- uty_vf_set_write(vf, on) ;
+ if (!final)
+ uty_vf_set_write(vf, on) ;
return CMD_WAITING ;
} ;
- /* Implement blocking I/O, with timeout */
+ /* Implement blocking I/O, with timeout
+ *
+ * Note that waits for both the main vout and the pipe return.
+ */
+ if (final)
+ return CMD_WAITING ;
+
qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum,
- vf->write_timeout) ;
- if (qps_mini_wait(qm, NULL, false) == 0)
- return CMD_IO_TIMEOUT ;
+ vf->write_timeout) ;
+ if (vf->pr_state == vf_open)
+ qps_mini_add(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum) ;
+
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* Loop back */
- /* Loop back to vio_fifo_write_nb() */
+ return uty_vf_error(vf, verr_to_vout, 0) ;
} ;
} ;
@@ -1385,112 +1790,282 @@ uty_pipe_out_push(vio_vf vf, bool final)
} ;
/*------------------------------------------------------------------------------
- * Shovel from return to slave, pushing the slave as we go.
+ * Shovel from pipe return to slave, pushing the slave as we go.
+ *
+ * While the main vout is open, this function is used:
+ *
+ * * VOUT_PIPE: for blocking vf, this is called each time the output is
+ * pushed -- to keep any returned stuff moving. Does not
+ * block reading the return, but may block writing to the
+ * slave.
+ *
+ * for non-blocking vf, this is called by the pselect
+ * process, which keeps the return moving autonomously,
+ * or may be called "final".
+ *
+ * Note that pr_timeout == 0 while the main vout is open.
*
- * For VIN_PIPE: this is called between command line reads, when a command
- * line read needs more input. Wants to suck the return dry before proceeding
- * to read any more command lines. Is also called when the vin is closed.
+ * * VOUT_SH_CMD: the main vin/vout is closed from the get go.
*
- * For VOUT_PIPE: for blocking vfs this is called each time the output is
- * pushed -- to keep the process moving. And when the vout is closed, to
- * complete the process. For non-blocking vfs, TODO
+ * All pipe return is handled by the close process.
*
- * For VOUT_SHELL_ONLY: same as VOUT_PIPE.
+ * When the main vin/vout is closed:
*
- * For blocking, keeps going until can read no more from the return. The slave
- * output may block, and could time out.
+ * * VOUT_PIPE & VOUT_SH_CMD:
*
- * With non-blocking, reads from the return and pushes at the slave. If the
- * slave obuf is not emptied by the push, the slave will set the master
- * read ready, when the slave goes write-ready.
+ * for blocking vf, this is called by the close function,
+ * to suck up any remaining return, and push it to the slave.
+ * May block reading the return and/or the slave.
*
- * For "final" does not attempt to read anything from the return, but sets it
- * vf_eof.
+ * for non-blocking vf, this is called by the pselect
+ * process, which keeps the return moving autonomously, or
+ * may be called "final".
*
- * For "closing" (if not "final"), if blocking will block on the return, until
- * get eof (or time out).
+ * Note that pr_timeout != 0 once the main vout is closed.
*
- * Returns: CMD_SUCCESS -- shovelled everything, but not hit EOF
- * if "closing" <=> non-blocking
- * CMD_EOF -- shovelled everything and is now at EOF
- * CMD_WAITING -- waiting for slave write-ready <=> non-blocking
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if runs into I/O error or time-out on the slave output, then sets
+ * vf_end on the TODO
*
* This can be called in any thread.
*/
static cmd_return_code_t
-uty_pipe_shovel(vio_vf vf, bool final, bool closing)
+uty_pipe_shovel(vio_vf vf, bool final)
{
- vio_vf slave ;
-
VTY_ASSERT_LOCKED() ; /* In any thread */
- // TODO other pr_state ??? vf_closed/vf_closing/vf_error ??
+ /* The pipe return MUST still be open, but may be cancelling all I/O
+ * or the slave may already be in error or otherwise not open.
+ */
+ qassert(vf->pr_state == vf_open) ;
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ if ((vf->vio->cancel) || (vf->vout_next->vout_state != vf_open))
+ return CMD_SUCCESS ;
/* Suck and blow -- may block in uty_cmd_out_push()
*
* Note that we do not push the slave if there is nothing new from the
* return -- there shouldn't be anything pending in the slave obuf.
*/
- slave = vf->pr_slave ;
- assert(vf == slave->pr_master) ;
-
- while (!final && (vf->pr_state == vf_open))
+ while (1)
{
cmd_return_code_t ret ;
int get ;
- get = vio_fifo_read_nb(slave->obuf, vio_vfd_fd(vf->pr_vfd), 10) ;
+ get = vio_fifo_read_nb(vf->vout_next->obuf, vio_vfd_fd(vf->pr_vfd), 10) ;
- if (get == 0)
+ if (get == 0) /* Nothing there, but not EOF */
{
qps_mini_t qm ;
- if (!closing || !vf->blocking)
- return CMD_SUCCESS ; /* quit if now dry */
+ /* Nothing there to read.
+ *
+ * Returns if not blocking, if no timeout is set or if is "final".
+ *
+ * NB: before blocking on the pipe return, poll the pipe stderr
+ * return to keep that moving -- so child cannot stall trying
+ * to output to its stderr !
+ */
+ if (!vf->blocking || (vf->pr_timeout == 0) || final)
+ break ; /* do not block reading */
+
+ if (vf->ps_state == vf_open)
+ {
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
- /* Implement blocking I/O, with timeout */
qps_mini_set(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum,
- vf->read_timeout) ;
- if (qps_mini_wait(qm, NULL, false) == 0)
- return CMD_IO_TIMEOUT ;
+ vf->pr_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_pr, 0) ; /* CMD_IO_ERROR */
}
- else if (get >= 0)
+ else if (get > 0) /* Read something */
{
- ret = uty_cmd_out_push(slave, final) ; /* may block etc. */
+ ret = uty_cmd_out_push(vf->vout_next, final) ; /* may block */
- if (ret != CMD_SUCCESS)
- return ret ;
+ if (ret == CMD_SUCCESS)
+ continue ; /* Loop back if emptied buffer */
+
+ if (ret == CMD_IO_ERROR)
+ uty_pipe_return_stop(vf) ;
+ /* No point continuing */
+ else
+ {
+ /* Is CMD_WAITING on the slave output.
+ *
+ * If we are "final":
+ *
+ * for blocking vf that means would have blocked, but didn't.
+ *
+ * for non-blocking vf, that means could not empty the buffer.
+ *
+ * If not "final":
+ *
+ * cannot be a blocking vf !
+ *
+ * for non-blocking vf, the output buffer will be serviced,
+ * in due course and can leave it up to the pselect() process
+ * to read anything more -- no need to do so now.
+ *
+ * ...in all cases don't want to try to input or output any more,
+ * so give up and return CMD_WAITING.
+ */
+ qassert(ret == CMD_WAITING) ;
+ } ;
+
+ return ret ; /* CMD_WAITING or CMD_IO_ERROR */
}
- else if (get == -1)
- return CMD_IO_ERROR ; /* register error TODO */
+ else if (get == -1) /* Hit error */
+ {
+ return uty_vf_error(vf, verr_io_pr, errno) ; /* CMD_IO_ERROR */
+ }
else
{
- assert (get == -2) ; /* eof on the return */
- break ;
+ assert (get == -2) ; /* eof on the return */
+
+ vf->pr_state = vf_end ;
+ break ; /* quit: OK */
} ;
} ;
- vf->pr_state = vf_eof ; /* Set EOF TODO: willy nilly ? */
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Suck the pipe stderr return to vf->ps_buf.
+ *
+ * While the main vin/vout is open or the pipe return is open, this function
+ * is used:
+ *
+ * for blocking vf, this is called each time the output is pushed, or the
+ * input is read, or the pipe return is read -- to keep any returned stuff
+ * moving. Does not block reading the stderr return.
+ *
+ * for non-blocking vf, this is called by the pselect which keeps the stderr
+ * return moving autonomously.
+ *
+ * Note that ps_timeout == 0 under these conditions.
+ *
+ * When the main vin/vout and the pipe return are closed:
+ *
+ * for blocking vf, this is called by the close function, to suck up any
+ * remaining stderr return, which may block.
+ *
+ * for non-blocking vf, this is called by the pselect process, which keeps
+ * the stderr return moving autonomously, or may be called "final".
+ *
+ * Note that ps_timeout != 0 under these conditions.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
+ */
+static cmd_return_code_t
+uty_pipe_stderr_suck(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ /* The pipe return MUST still be open, but may be cancelling all I/O
+ * or the slave may already be in error or otherwise not open.
+ */
+ qassert(vf->ps_state == vf_open) ;
+
+ /* Suck
+ */
+ while (1)
+ {
+ int get ;
+
+ if (vf->ps_buf == NULL)
+ {
+ char buf[100] ;
+ get = read_nb(vio_vfd_fd(vf->ps_vfd), buf, sizeof(buf)) ;
+
+ if (get > 0)
+ vio_fifo_put_bytes(uty_pipe_ps_buf(vf), buf, get) ;
+ }
+ else
+ get = vio_fifo_read_nb(vf->ps_buf, vio_vfd_fd(vf->ps_vfd), 10) ;
+
+ if (get == 0) /* Nothing there, but not EOF */
+ {
+ qps_mini_t qm ;
+
+ /* Nothing there to read.
+ *
+ * Returns if not blocking, if no timeout is set or if is "final".
+ *
+ * NB: before blocking on the pipe return, poll the pipe stderr
+ * return to keep that moving -- so child cannot stall trying
+ * to output to its stderr !
+ */
+ if (!vf->blocking || (vf->ps_timeout == 0) || final)
+ break ; /* do not block reading */
+
+ qps_mini_set(qm, vio_vfd_fd(vf->ps_vfd), qps_read_mnum,
+ vf->ps_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_ps, 0) ; /* CMD_IO_ERROR */
+ }
+
+ else if (get > 0) /* Read something */
+ {
+ continue ;
+ }
- return CMD_EOF ;
+ else if (get == -1) /* Hit error on stderr return */
+ {
+ return uty_vf_error(vf, verr_io_ps, errno) ; /* CMD_IO_ERROR */
+ }
+
+ else
+ {
+ assert (get == -2) ; /* eof on the stderr return */
+
+ vf->ps_state = vf_end ;
+ break ; /* quit: OK */
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Pipe is ready to read -- this is the call-back planted in the vf->vfd, of
- * type VIN_PIPE.
+ * Pipe is ready to read -- call-back for VIN_PIPE.
*
- * Can restart the command_queue.
+ * This is used if the VIN_PIPE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
*
* Note that the read_ready state and any time out are automatically
* disabled when they go off.
*/
static void
-uty_pipe_read_ready(vio_vfd vfd, void* action_info)
+vty_pipe_read_ready(vio_vfd vfd, void* action_info)
{
vio_vf vf ;
@@ -1500,19 +2075,23 @@ uty_pipe_read_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
+ /* If the vin is no longer vf_open then read ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Pipe has timed out, waiting to read -- this is the call-back planted in the
- * vf->vfd, of type VIN_PIPE.
+ * Pipe has timed out, waiting to read -- call-back for VIN_PIPE.
+ *
+ * This is used if the VIN_PIPE is non-blocking.
*
- * ????
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_pipe_read_timeout(vio_timer timer, void* action_info)
+vty_pipe_read_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -1522,7 +2101,7 @@ uty_pipe_read_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->vfd->read_timer == timer) ;
-// TODO .... signal time out
+ uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -1530,27 +2109,22 @@ uty_pipe_read_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Pipe return is ready to read -- this is the call-back planted in the
- * vf->pr_vfd:
- *
- * VIN_PIPE: used only when closing the vin, and are waiting for
- * the return to be drained.
- *
- * VOUT_PIPE: used to continually shovel from the return to the
- * slave -- including while closing.
+ * Pipe return is ready to read -- call-back for VOUT_PIPE and VOUT_SH_CMD.
*
- * VOUT_SHELL_ONLY: used to continually shovel from the return to the
- * slave -- which happens while closing.
+ * This is used if the VOUT_PIPE/VOUT_SH_CMD is non-blocking.
*
- * If all is well, and more return input is expected, re-enables read ready.
+ * Shovels any available return input to the output. If required, the shoveller
+ * will set read ready when runs out of input.
*
- * If uty_pipe_shovel() returns CMD_WAITING, then is waiting for the slave
- * output buffer to clear. The slave will kick uty_pipe_return_slave_ready().
+ * Signals to the command loop iff hits eof or an error or time-out. (Also
+ * signals to the command loop if is not open... just in case command loop is
+ * waiting.)
*
- * For all other returns, kick the command loop, which if it is waiting is TODO
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
*/
static void
-uty_pipe_return_ready(vio_vfd vfd, void* action_info)
+vty_pipe_return_ready(vio_vfd vfd, void* action_info)
{
cmd_return_code_t ret ;
vio_vf vf ;
@@ -1561,45 +2135,82 @@ uty_pipe_return_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->pr_vfd == vfd) ;
- ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */
- /* TODO -- errors !! */
- if (ret == CMD_SUCCESS)
- vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* expect more */
- else if (ret != CMD_WAITING)
- uty_cmd_signal(vf->vio, (ret == CMD_EOF) ? CMD_SUCCESS
- : ret) ; /* in case TODO */
+ /* If the pipe return is no longer open then read ready should have been
+ * turned off -- but kicking the command loop will not hurt.
+ *
+ * Note that uty_pipe_shovel() returns CMD_WAITING, unless hits eof
+ * (CMD_SUCCESS) or gets an I/O error or timeout (CMD_IO_ERROR).
+ */
+ if (vf->pr_state == vf_open)
+ ret = uty_pipe_shovel(vf, false) ; /* not final */
+ else
+ ret = CMD_SUCCESS ;
+
+ if (vf->pr_state == vf_open)
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+ else if (vf->ps_state != vf_open)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Pipe return slave is ready to write.
+ * Pipe stderr return is ready to read -- call-back for VIN_PIPE, VOUT_PIPE and
+ * VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
*
- * This is called by the pipe return slave, to prompt the master when the slave
- * has been waiting to be able to write away its output buffer.
+ * Shovels any available return input to the output. If required, the shoveller
+ * will set read ready when runs out of input.
*
- * This will set read ready on the return, to keep the process of shovelling
- * from return to slave going.
+ * Signals to the command loop iff hits eof or an error or time-out. (Also
+ * signals to the command loop if is not open... just in case command loop is
+ * waiting.)
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
*/
-extern void
-uty_pipe_return_slave_ready(vio_vf slave)
+static void
+vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info)
{
- vio_vf vf ;
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->ps_vfd == vfd) ;
+
+ /* If the pipe stderr return is no longer open then read ready should have
+ * been turned off -- but kicking the command loop will not hurt.
+ *
+ * Note that uty_pipe_shovel() returns CMD_WAITING, unless hits eof
+ * (CMD_SUCCESS) or gets an I/O error or timeout (CMD_IO_ERROR).
+ */
+ if (vf->ps_state == vf_open)
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+ else
+ ret = CMD_SUCCESS ;
- vf = slave->pr_master ;
- assert(vf->pr_slave == slave) ;
+ if (vf->ps_state == vf_open)
+ vio_vfd_set_read(vf->ps_vfd, on, vf->ps_timeout) ;
+ else if (vf->pr_state != vf_open)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
- vio_vfd_set_read(vf->pr_vfd, on, 0) ;
+ VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Pipe return has timed out -- this is the call-back planted in the
- * vf->pr_vfd of either a VIN_PIPE or a VOUT_PIPE ????
+ * Pipe return has timed out, waiting to read -- call-back for VIN_PIPE,
+ * VOUT_PIPE and VOUT_SH_CMD.
*
- * ????
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_pipe_return_timeout(vio_timer timer, void* action_info)
+vty_pipe_return_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -1609,7 +2220,10 @@ uty_pipe_return_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->pr_vfd->read_timer == timer) ;
-//cq_continue(vf->vio->vty) ; TODO
+ if (vf->ps_state == vf_open)
+ vio_vfd_set_read(vf->ps_vfd, off, 0) ;
+
+ uty_vf_error(vf, verr_to_ps, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -1617,14 +2231,46 @@ uty_pipe_return_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Pipe output is ready to write -- this is the call-back planted in the
- * vf->vfd of a VOUT_PIPE
+ * Pipe return has timed out, waiting to read -- call-back for VIN_PIPE,
+ * VOUT_PIPE and VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_pipe_stderr_return_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->ps_vfd->read_timer == timer) ;
+
+ if (vf->pr_state == vf_open)
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+
+ uty_vf_error(vf, verr_to_pr, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe is ready to write -- call-back for VOUT_PIPE.
+ *
+ * This is used if the VOUT_PIPE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
*
* Note that the write_ready state and any time out are automatically
* disabled when they go off.
*/
static void
-uty_pipe_write_ready(vio_vfd vfd, void* action_info)
+vty_pipe_write_ready(vio_vfd vfd, void* action_info)
{
cmd_return_code_t ret ;
vio_vf vf ;
@@ -1635,25 +2281,31 @@ uty_pipe_write_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
- ret = uty_pipe_out_push(vf, false) ; /* not final */
+ /* If the vout is no longer vf_open then write ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ if (vf->vout_state == vf_open)
+ {
+ ret = uty_pipe_out_push(vf, false) ; /* not final */
+ }
+ else
+ ret = CMD_SUCCESS ;
if (ret != CMD_WAITING)
- uty_cmd_signal(vf->vio, ret) ;
-
- if ((ret == CMD_SUCCESS) && (vf->pr_master != NULL))
- uty_pipe_return_slave_ready(vf) ;
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Pipe output has timed out -- this is the call-back planted in the
- * vf->vfd of a VOUT_PIPE
+ * Pipe has timed out, waiting to write -- call-back for VOUT_PIPE.
+ *
+ * This is used if the VOUT_PIPE is non-blocking.
*
- * ????
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_pipe_write_timeout(vio_timer timer, void* action_info)
+vty_pipe_write_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -1663,7 +2315,7 @@ uty_pipe_write_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->vfd->write_timer == timer) ;
-// TODO
+ uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -1671,257 +2323,434 @@ uty_pipe_write_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Tidy up after input pipe has been read closed.
- *
- * Nothing needs to be done with the main input, but do need to close the
- * return, collect the child and release the slave.
+ * Complete the close of a VIN_PIPE after the vfd has been read closed.
+ *
+ * Nothing needs to be done with the main input, but must close the return,
+ * collect the child and release the slave.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible, the return is done with
+ * (and the vfd closed) and the child has been
+ * collected (or is overdue).
+ * CMD_WAITING -- not "final" => waiting for return I/O to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ *
+ * If returns CMD_WAITING (and not "final") then the command loop will be
+ * signalled when it is time to call this function again to progress the
+ * close operation.
*/
extern cmd_return_code_t
uty_pipe_read_close(vio_vf vf, bool final)
{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_type == VIN_PIPE) ;
+ qassert(vf->vin_state == vf_end) ;
+
return uty_pipe_return_close(vf, final) ;
} ;
/*------------------------------------------------------------------------------
- * Close VOUT_PIPE or VOUT_SHELL_ONLY.
+ * Close VOUT_PIPE or VOUT_SH_CMD.
*
* For not-final, wish to flush output buffer. If final will attempt to empty
* the output buffer -- but will not wait or block, and ignores errors.
*
* Must then close the return, collect the child and release the slave.
*
- * Returns: CMD_SUCCESS -- pushed what there was, or final
- * CMD_WAITING -- would have blocked <=> non-blocking
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ * Returns: CMD_SUCCESS -- done everything possible, the vout and the
+ * return are done with (and the vfds closed) and
+ * the child has been collected (or is overdue).
+ * CMD_WAITING -- not "final" => waiting for output or return I/O
+ * to complete <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ *
+ * If returns CMD_WAITING (and not "final") then the command loop will be
+ * signalled when it is time to call this function again to progress the
+ * close operation.
*/
extern cmd_return_code_t
-uty_pipe_write_close(vio_vf vf, bool final, bool base, bool shell_only)
+uty_pipe_write_close(vio_vf vf, bool final)
{
- cmd_return_code_t ret ;
-
VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- /* If the main vfd is still there, keep pushing.
- *
- * Close it if manage to empty out buffers (or final) -- signals end to the
- * child process.
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ /* If the main vfd is still there, keep pushing (which, for blocking vf,
+ * will keep shovelling from pipe return to slave).
*/
- if (!vf->pr_only)
+ if (vf->vout_state == vf_open)
{
+ cmd_return_code_t ret ;
+
ret = uty_pipe_out_push(vf, final) ;
- if ((ret != CMD_SUCCESS) && !final)
+ if ((ret == CMD_WAITING) && !final)
return ret ;
- /* Now need to close the output vfd to signal to the child that we
- * are done.
- *
- * Sets pr_only so that if does go CMD_WAITING, then future call of
- * uty_pipe_out_push() will not attempt any I/O ! vf->vfd is set NULL,
- * which will be ignored by any future vio_vfd_close().
- */
- vf->vfd = vio_vfd_close(vf->vfd) ;
- vf->pr_only = true ; /* only the return is left */
+ vf->vout_state = vf_end ; /* no further output */
} ;
+ /* Now need to close the output vfd to signal to the child that we
+ * are done -- note that closing an already closed vfd does nothing.
+ */
+ vf->vfd = vio_vfd_close(vf->vfd) ;
+
/* If the return is still open, try to empty and close that. */
return uty_pipe_return_close(vf, final) ;
} ;
/*------------------------------------------------------------------------------
- * Close the return on a pipe.
+ * Close the return on a VIN_PIPE, VOUT_PIPE or a VOUT_SH_CMD.
+ *
+ * This is the second stage of closing a pipe, after the vin or the main vout
+ * has reached vf_end, so all main I/O is complete (or failed).
+ *
+ * If returns CMD_WAITING from here, may be called again, any number of times,
+ * to attempt to complete the process.
+ *
+ * If "final" then will close after making efforts to complete I/O and collect
+ * child, short of blocking or waiting.
+ *
+ * Phase 1: if the return is vf_open:
+ *
+ * Set the read time out for the return (want any remaining return stuff, and
+ * the eof) which (for non-blocking) indicates is now prepared to block.
+ *
+ * If non-blocking:
+ *
+ * if not "final", set the read ready timeout (if not already set), and
+ * leave CMD_WAITING.
+ *
+ * if "final", kill the read ready stuff, and shovel once "final", just
+ * in case there is stuff there.
+ *
+ * If blocking:
+ *
+ * Shovel stuff from return to slave output. If not "final", may block and
+ * may time-out. If "final" will not block, but may return CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * Phase 2: if the stderr return is vf_open:
+ *
+ * Set the read time out for the stderr return (want any remaining stderr
+ * return stuff, and the eof) which (for non-blocking) indicates is now
+ * prepared to block.
+ *
+ * Suck stderr return. If "final", do not block or wait for either input or
+ * output. If not final, may block and may time-out or may return
+ * CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * If non-blocking:
+ *
+ * if not "final", set the read ready timeout (if not already set), and
+ * leave CMD_WAITING.
+ *
+ * if "final", kill the read ready stuff, and shovel once "final", just
+ * in case there is stuff there.
+ *
+ * If blocking:
+ *
+ * Shovel stuff from return to slave output. If not "final", may block and
+ * may time-out. If "final" will not block, but may return CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * Phase 3: collect the child return code and deal with it:
+ *
+ * If the child has yet to be collected:
+ *
+ * if "final", collect child immediately or set overdue.
+ *
+ * if not "final":
+ *
+ * if non-blocking, return CMD_WAITING and wait for the SIGCHLD to do
+ * the business or to time out.
+ *
+ * if blocking, collect child or time out and set overdue.
*
- * If not final, may need to drain the return.
+ * If the child is not collected, or has not terminated cleanly, output
+ * diagnostic to the pipe stderr return.
*
- * Need to collect the child to complete the process. Once the child is
- * collected (or have given up waiting, will send a message to the slave if
- * not completed with return code == 0.
+ * If the child is not collected, the SIGCHLD handling will tidy up in the
+ * background.
*
- * Once any message has been pushed to the slave, can release the slave
- * and the close process will be complete.
+ * In any case, dismiss child.
*
- * If final, then will not block and will ignore errors. Return code reflects
- * the last operation.
+ * Phase 4: transfer the pipe stderr return to the main vio pipe stderr return.
*
- * Returns: CMD_SUCCESS -- closed and child collected
- * CMD_WAITING
- * CMD_IO_ERROR
- * CMD_IO_TIMEOUT
- * CMD_xxxx -- child error(s)
+ * This leaves it up to the vout_base to deal with the pipe stderr return,
+ * in its own time -- e.g when the output stack closes.
+ *
+ * When this (eventually) returns CMD_SUCCESS, all pipe return has been
+ * sucked up and has cleared the slave vout buffer, and the child has been
+ * collected -- unless something has gone wrong, and part of that has been
+ * short-circuited.
+ *
+ * Returns: CMD_SUCCESS -- closed and child collected
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
*/
static cmd_return_code_t
uty_pipe_return_close(vio_vf vf, bool final)
{
- cmd_return_code_t ret ;
+ vty_io vio ;
VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- /* If the return is still open, try to empty and close that. */
- if (vf->pr_state != vf_closed)
+ vio = vf->vio ;
+
+ /* Phase 1: if return is still open, try to empty it -- subject to "final".
+ * Sets timeout and will now block.
+ *
+ * If not blocking but "final", turn off read ready and any timeout,
+ * and shovel one last time.
+ *
+ * When return is all done, close it (which the child may see) and
+ * mark it vf_closed.
+ */
+ if (vf->pr_state == vf_open)
{
- ret = uty_pipe_return_empty(vf, final) ;
+ cmd_return_code_t ret ;
+ bool set_timeout ;
+
+ set_timeout = (vf->pr_timeout == 0) ;
+ vf->pr_timeout = pipe_timeout ;
- if (!final)
+ if (!vf->blocking)
{
- if (ret == CMD_SUCCESS)
+ if (!final)
{
- vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */
- return CMD_WAITING ;
+ if (set_timeout)
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+
+ return CMD_WAITING ; /* in hands of pselect() */
} ;
- if (ret != CMD_EOF)
- return ret ;
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
} ;
- } ;
- /* If not already collected, collect the child. */
- if (vf->child != NULL)
- {
- ret = uty_pipe_collect_child(vf, final) ;
+ ret = uty_pipe_shovel(vf, final) ;
- if ((ret != CMD_SUCCESS) && !final)
+ if ((ret == CMD_WAITING) && !final)
return ret ;
} ;
- /* Finally, release the slave */
- return uty_pipe_release_slave(vf, final) ;
-}
-
-/*------------------------------------------------------------------------------
- * If the return is still open, shovel from return to slave.
- *
- * If final will not block or wait, and ignores errors -- return code reflects
- * the last operation.
- *
- * If blocking, will block until return hits EOF or timeout.
- *
- * Returns: CMD_SUCCESS -- shovelled what there was, may be more to come
- * <=> non-blocking
- * CMD_EOF -- shovelled what there was, is now *closed*
- * CMD_WAITING -- waiting for slave write-ready <=> non-blocking
- * CMD_IO_ERROR -- ran into an I/O error
- * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
- */
-static cmd_return_code_t
-uty_pipe_return_empty(vio_vf vf, bool final)
-{
- cmd_return_code_t ret ;
-
- // worry about pr_state ??
-
- ret = uty_pipe_shovel(vf, final, true) ; /* closing ! */
-
- if ((ret == CMD_EOF) || final)
+ if (vf->pr_state != vf_closed)
{
vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ;
vf->pr_state = vf_closed ;
} ;
- return ret ;
-} ;
+ /* Phase 2: if stderr return is still open, try to empty it -- subject to
+ * "final". Sets timeout and will now block.
+ *
+ * If not blocking but "final", turn off read ready and any timeout,
+ * and suck one last time.
+ *
+ * When return is all done, close it (which the child may see) and
+ * mark it vf_closed.
+ */
+ if (vf->ps_state == vf_open)
+ {
+ cmd_return_code_t ret ;
+ bool set_timeout ;
-/*------------------------------------------------------------------------------
- * Collect child.
- *
- * When does collect, or times out, will dismiss the child and set vf->child
- * to NULL -- which indicates that the child is done with.
- *
- * Returns: CMD_SUCCESS -- child dismissed, vf->child == NULL
- * CMD_WAITING -- waiting to collect child <=> non-blocking
- *
- */
-static cmd_return_code_t
-uty_pipe_collect_child(vio_vf vf, bool final)
-{
- vio_vf slave ;
+ set_timeout = (vf->ps_timeout == 0) ;
+ vf->ps_timeout = pipe_timeout ;
- assert(vf->child != NULL) ;
+ if (!vf->blocking)
+ {
+ if (!final)
+ {
+ if (set_timeout)
+ vio_vfd_set_read(vf->ps_vfd, on, vf->pr_timeout) ;
- /* Collect -- blocking or not blocking */
- if (!vf->child->collected && !vf->child->overdue)
+ return CMD_WAITING ; /* in hands of pselect() */
+ } ;
+
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+ } ;
+
+ ret = uty_pipe_stderr_suck(vf, final) ;
+
+ if ((ret == CMD_WAITING) && !final)
+ return ret ;
+ } ;
+
+ if (vf->ps_state != vf_closed)
{
- /* If we are !blocking and !final, leave the child collection up to
- * the SIGCHLD system.
+ vf->ps_vfd = vio_vfd_close(vf->ps_vfd) ;
+ vf->ps_state = vf_closed ;
+ } ;
+
+ /* Phase 3: if not already collected, collect the child
+ *
+ * When does collect, or times out, will dismiss the child and set vf->child
+ * to NULL -- which indicates that the child is done with.
+ *
+ * Note that may write diagnostic message to slave, so in last phase we
+ * push the slave output to clear that out before releasing the slave.
+ */
+ if (vf->child != NULL)
+ {
+ /* Collect -- blocking or not blocking */
+ if (!vf->child->collected && !vf->child->overdue)
+ {
+ /* If we are blocking or "final" or vio->cancel, try to collect the
+ * child here and now -- if not final or vio->cancel may block here.
+ *
+ * For non-blocking and not "final", leave the child collection up to
+ * the SIGCHLD system.
+ */
+ if (vf->blocking || final || vf->vio->cancel)
+ {
+ uty_child_collect(vf->child, child_timeout,
+ final || vf->vio->cancel) ;
+ }
+ else
+ {
+ uty_child_awaited(vf->child, child_timeout) ;
+ return CMD_WAITING ;
+ } ;
+ } ;
+
+ /* If child is overdue, or did not terminate cleanly, write message to
+ * the pipe stderr return.
*/
- if (!vf->blocking && !final)
+ if (!vf->child->collected)
{
- uty_child_awaited(vf->child, 6) ;
- return CMD_WAITING ;
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process still running\n") ;
+ }
+ else if (WIFEXITED(vf->child->report))
+ {
+ int status = WEXITSTATUS(vf->child->report) ;
+ if (status != 0)
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process ended normally, but with status = %d\n",
+ status) ;
+ }
+ else if (WIFSIGNALED(vf->child->report))
+ {
+ int signal = WTERMSIG(vf->child->report) ;
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process terminated by signal = %d\n", signal) ;
+ }
+ else
+ {
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process ended in unknown state = %d\n",
+ vf->child->report) ;
} ;
- /* If we are blocking or final, try to collect the child here and
- * now -- if not final may block here.
+ /* Can now dismiss the child -- if not collected, is left on the register.
*/
- uty_child_collect(vf->child, 6, final) ;
+ uty_child_dismiss(vf->child, false) ; /* not curtains */
} ;
- /* If child is overdue, or did not terminate cleanly, write message to
- * slave... which we then have to push...
+ /* Phase 4: if there is anything in the pipe stderr return buffer, finish
+ * it, and transfer to the vio->ps_buf (unless vio->cancel).
*/
- slave = vf->pr_slave ;
- assert((slave != NULL) && (vf == slave->pr_master)) ;
-
- if (!vf->child->collected)
+ if (vf->ps_buf != NULL)
{
- vio_fifo_printf(slave->obuf, "%% child process still running\n") ;
- }
- else if (WIFEXITED(vf->child->report))
- {
- int status = WEXITSTATUS(vf->child->report) ;
- if (status != 0)
- vio_fifo_printf(slave->obuf,
- "%% child process ended normally, but with status = %d\n",
- status) ;
- }
- else if (WIFSIGNALED(vf->child->report))
- {
- int signal = WTERMSIG(vf->child->report) ;
- vio_fifo_printf(slave->obuf,
- "%% child process terminated by signal = %d\n", signal) ;
- }
- else
- {
- vio_fifo_printf(slave->obuf,
- "%% child process ended in unknown state = %d\n",
- vf->child->report) ;
- } ;
+ vio_fifo_trim(vf->ps_buf, true) ; /* trim trailing whitespace
+ * and '\n' terminate */
+ if (!vio_fifo_empty(vf->ps_buf) && !vio->cancel)
+ {
+ const char* what ;
+
+ if (vf->vin_type == VIN_PIPE)
+ what = "<|" ;
+ else if (vf->vout_type == VOUT_PIPE)
+ what = ">|" ;
+ else
+ what = "|" ;
- /* Can now dismiss the child. */
- vf->child = uty_child_dismiss(vf->child, false) ; /* not final */
+ if (vio->ps_buf == NULL)
+ vio->ps_buf = vio_fifo_new(1024) ;
+
+ vio_fifo_printf(vio->ps_buf, "%%--- in '%s %s':\n", what, vf->name) ;
+ vio_fifo_copy(vio->ps_buf, vf->ps_buf) ;
+ } ;
+
+ vf->ps_buf = vio_fifo_free(vf->ps_buf) ;
+ } ;
return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Push output to slave a final time -- to shift any final message about state
- * of child on closing.
+ * Stop pipe return and/or pipe stderr return.
*
- * Then release slave.
+ * Then if either vin and/or vout is open, set to vf_end to terminate I/O.
+ *
+ * There is no point continuing with anything once either return is broken.
*/
-static cmd_return_code_t
-uty_pipe_release_slave(vio_vf vf, bool final)
+extern void
+uty_pipe_return_stop(vio_vf vf)
{
- cmd_return_code_t ret ;
- vio_vf slave ;
+ if (vf->pr_state == vf_open)
+ vf->pr_state = vf_end ;
- ret = CMD_SUCCESS ;
+ if (vf->ps_state == vf_open)
+ vf->ps_state = vf_end ;
- slave = vf->pr_slave ;
- if (slave != NULL)
- {
- assert(vf == slave->pr_master) ;
+ if (vf->vin_state == vf_open)
+ vf->vin_state = vf_end ;
- ret = uty_cmd_out_push(slave, final) ; /* may block etc. */
+ if (vf->vout_state == vf_open)
+ vf->vout_state = vf_end ;
+} ;
- if ((ret != CMD_SUCCESS) && !final)
- return ret ;
+/*------------------------------------------------------------------------------
+ * Cancel any further input from the pipe return.
+ *
+ * Note that does not touch anything buffered ready to be output in the
+ * slave -- that must be dealt with separately.
+ */
+extern void
+uty_pipe_return_cancel(vio_vf vf)
+{
+ qassert( (vf->vin_type == VIN_PIPE) || (vf->vout_type == VOUT_PIPE)
+ || (vf->vout_type == VOUT_SH_CMD) ) ;
+ qassert(vf->pr_state == vf_open) ;
- slave->pr_master = NULL ; /* release slave */
- vf->pr_slave = NULL ;
- } ;
+ vf->pr_state = vf_end ;
+} ;
- return ret ;
+/*------------------------------------------------------------------------------
+ * Get pipe stderr return buffer for the vf -- make one if none yet exists.
+ */
+static vio_fifo
+uty_pipe_ps_buf(vio_vf vf)
+{
+ if (vf->ps_buf == NULL)
+ vf->ps_buf = vio_fifo_new(1024) ;
+
+ return vf->ps_buf ;
} ;
/*==============================================================================