summaryrefslogtreecommitdiffstats
path: root/lib/vty_io_file.c
diff options
context:
space:
mode:
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 ;
} ;
/*==============================================================================