diff options
Diffstat (limited to 'lib/vty_io_file.c')
-rw-r--r-- | lib/vty_io_file.c | 2761 |
1 files changed, 2761 insertions, 0 deletions
diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c new file mode 100644 index 00000000..d1d1a50a --- /dev/null +++ b/lib/vty_io_file.c @@ -0,0 +1,2761 @@ +/* VTY I/O for Files + * + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#include "misc.h" +#include <fcntl.h> +#include <sys/wait.h> +#include <errno.h> + +#include "command_local.h" + +#include "vty_io.h" +#include "vty_io_file.h" +#include "vty_io_basic.h" +#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 the vout_base to be VOUT_STDERR. + * + * 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) +{ + vty vty ; + vty_io vio ; + vio_vf vf ; + + VTY_LOCK() ; + + /* Create the vty and its vio. + * + * NB: VTY_CONFIG_READ type vty is set vio->blocking. + */ + vty = uty_new(VTY_CONFIG_READ, CONFIG_NODE) ; + + vio = vty->vio ; + + /* Create the vin_base/vout_base vf + * + * NB: don't need to specify vfd_io_ps_blocking, because the vio is set + * blocking. + */ + qassert(vio->blocking) ; + + vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ; + + 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. + * + * Before there is any chance of a SIGCHLD being raised for a child of the + * configuration file, invoke the magic required for SIGCHLD to wake up + * a pselect() while waiting to collect child. + */ + uty_child_signal_nexus_set(vio) ; + + VTY_UNLOCK() ; + + return vty ; +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after config input file has been closed. + * + * There is now no further possibility that will block waiting for child to be + * collected. + * + * Nothing further required -- input comes to a halt. + */ +extern cmd_return_code_t +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 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, + * all I/O is non-blocking. The actual I/O is non-blocking, the "blocking" + * I/O is manufactured using a mini-pselect, so can time-out file writing + * before too long. + */ +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 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 -- VIN_FILE. + * + * If could not open, issues message to the vio. + * + * If opens OK, save the current context in the current vin (before pushing + * the new vin). + * + * Returns: CMD_SUCCESS -- all set to go + * CMD_WARNING -- failed to open + * + * If "blocking" vf, this can be called from any thread, otherwise must be the + * cli thread -- see uty_vfd_new(). + */ +extern cmd_return_code_t +uty_file_read_open(vty_io vio, qstring name, cmd_context context) +{ + cmd_return_code_t ret ; + qpath path ; + const char* pns ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; + + VTY_ASSERT_LOCKED() ; + + assert(vio->vin != NULL) ; /* Not expected to be vin_base */ + + /* 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. May be vfd_io_ps_blocking, but we don't care + * about that here. + */ + iot = vfd_io_read ; + + 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: %s\n", pns, + errtoa(errno, 0).str) ; + ret = CMD_WARNING ; + } + else + { + uty_vin_new_context(vio, context, path) ; + + vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ; + 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 ; + } ; + + qpath_free(path) ; + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * 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 + * + * If "blocking" vf, this can be called from any thread, otherwise must be the + * 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, + bool after) +{ + cmd_return_code_t ret ; + qpath path ; + const char* pns ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; + + 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. 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_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: %s\n", pns, + errtoa(errno, 0).str) ; + ret = CMD_WARNING ; + } + else + { + vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ; + 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 ; + } ; + + qpath_free(path) ; + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * 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 (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. + * + * In "blocking" state, will not return until have something, or there is + * nothing to be had, or times out. + * + * This can be called in any thread. + * + * 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 */ + + qassert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ; + qassert(vf->vin_state == vf_open) ; + + while (1) + { + cmd_return_code_t ret ; + + /* Try to complete line straight from the buffer. + * + * If buffer is empty and have seen eof on the input, signal CMD_EOF. + */ + ret = uty_fifo_command_line(vf, action) ; + + if (ret != CMD_WAITING) + return ret ; + + /* Could not complete line and exhausted contents of fifo */ + while (1) + { + qps_mini_t qm ; + int get ; + + get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 1) ; + + if (get > 0) + break ; /* loop back to uty_fifo_command_line() */ + + if (get == -1) + return uty_vf_error(vf, verr_io_vin, errno) ; + + if (get == -2) + { + /* 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. + * + * Loop back so that uty_fifo_command_line() can reconsider, now + * that we know that there is no more data to come -- it may + * signal CMD_SUCCESS (non-empty final line) or CMD_EOF. + */ + qassert(vio_fifo_empty(vf->ibuf)) ; + + 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 vio_fifo_read_nb(). + */ + qassert(get == 0) ; + + if (!vf->blocking) + { + uty_vf_set_read(vf, on) ; + return CMD_WAITING ; + } ; + + /* 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) + continue ; /* 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. + * + * 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_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 +uty_fifo_command_line(vio_vf vf, cmd_action action) +{ + VTY_ASSERT_LOCKED() ; /* In any thread */ + + if (vf->line_complete) + { + vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */ + + vf->line_complete = false ; + vf->line_number += vf->line_step ; + + qs_set_len_nn(vf->cl, 0) ; + vf->line_step = 0 ; + } ; + + while (1) + { + char* s, * p, * e ; + ulen have ; + ulen len ; + bool eol ; + + /* Get what we can from the fifo */ + have = vio_fifo_get(vf->ibuf) ; + + /* If fifo is empty, may be last line before eof, eof or waiting */ + if (have == 0) + { + if (vf->vin_state == vf_end) + { + if (qs_len_nn(vf->cl) > 0) + break ; /* have non-empty last line */ + else + return CMD_EOF ; + } ; + + return CMD_WAITING ; + } ; + + qassert(vf->vin_state != vf_end) ; /* not empty => not eof */ + + /* Try to find a '\n' -- converting all other control chars to ' ' + * + * When we find '\n' step back across any trailing ' ' (which includes + * any control chars before the '\n'). + * + * This means that we cope with "\r\n" line terminators. But not + * anything more exotic. + */ + p = s = vio_fifo_get_ptr(vf->ibuf) ; + e = s + have ; /* have != 0 */ + + eol = false ; + while (p < e) + { + if (*p++ < 0x20) + { + if (*(p-1) != '\n') + { + *(p-1) = ' ' ; /* everything other than '\n' */ + continue ; + } ; + + ++vf->line_step ; /* got a '\n' */ + + eol = true ; + break ; + } ; + } ; + + /* Step past what have just consumed -- we have a hold_mark, so + * stuff is still in the fifo. + */ + vio_fifo_step(vf->ibuf, p - s) ; + + /* If not found '\n', then we have a line fragment that needs to be + * appended to any previous line fragments. + * + * Loops back to try to get some more form the fifo. + */ + if (!eol) + { + qs_append_str_n(vf->cl, s, p - s) ; + continue ; + } ; + + /* Have an eol. Step back across the '\n' and any trailing spaces + * we have in hand. + */ + do --p ; while ((p > s) && (*(p-1) == ' ')) ; + + /* If we have nothing so far, set alias to point at what we have in + * the fifo. Otherwise, append to what we have. + * + * End up with: s = start of entire line, so far. + * p = end of entire line so far. + */ + len = p - s ; /* length to add */ + if (qs_len_nn(vf->cl) == 0) + qs_set_alias_n(vf->cl, s, len) ; + else + { + if (len != 0) + qs_append_str_n(vf->cl, s, len) ; + + s = qs_char_nn(vf->cl) ; + p = s + qs_len_nn(vf->cl) ; + + if ((len == 0) && (p > s) && (*(p-1) == ' ')) + { + /* Have an empty end of line section, and the last character + * of what we have so far is ' ', so need now to trim trailing + * spaces off the stored stuff. + */ + do --p ; while ((p > s) && (*(p-1) == ' ')) ; + + qs_set_len_nn(vf->cl, p - s) ; + } ; + } ; + + /* Now worry about possible trailing '\'. */ + + if ((p == s) || (*(p-1) != '\\')) + break ; /* no \ => no continuation => success */ + + /* Have a trailing '\'. + * + * If there are an odd number of '\', strip the last one and loop + * round to collect the continuation. + * + * If there are an even number of '\', then this is not a continuation. + * + * Note that this rule deals with the case of the continuation line + * being empty... e.g. ....\\\ n n -- where n is '\n' + */ + e = p ; + do --p ; while ((p > s) && (*(p-1) == '\\')) ; + + if (((e - p) & 1) == 0) + break ; /* even => no continuation => success */ + + qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */ + + continue ; /* loop back to fetch more */ + } ; + + /* Success: have a line in hand */ + + vf->line_complete = true ; + + action->to_do = cmd_do_command ; + action->line = vf->cl ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * File is ready to read -- call-back for VIN_FILE. + * + * This is used if the VIN_FILE 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 +vty_file_read_ready(vio_vfd vfd, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + 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 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 +vty_file_read_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->read_timer == timer) ; + + uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */ + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Command output push to a file -- VOUT_FILE or VOUT_CONFIG -- vf_open. + * + * 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 will return an error return code). + * + * If an error occurred earlier, then returns immediately (CMD_SUCCESS). + * + * 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") + * + * 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, bool all) +{ + VTY_ASSERT_LOCKED() ; /* In any thread */ + + 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 + { + while (1) + { + qps_mini_t qm ; + int n ; + + n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ; + + if (n < 0) + return uty_vf_error(vf, verr_io_vout, errno) ; + + if (n == 0) + break ; /* all gone */ + + /* 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 ; + + 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() */ + + return uty_vf_error(vf, verr_to_vout, 0) ; + } ; + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * 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. + */ +static void +vty_file_write_ready(vio_vfd vfd, void* action_info) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + /* 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) ; /* CMD_SUCCESS or CMD_IO_ERROR */ + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * File has timed out, waiting to write -- call-back for VOUT_FILE. + * + * This is used if the VOUT_FILE is non-blocking. + * + * Signals a timeout error to the command loop. + */ +static vty_timer_time +vty_file_write_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->write_timer == timer) ; + + uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */ + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * 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 ready for close -- VOUT_FILE and VOUT_CONFIG. + * + * See uty_file_out_push() + * + * 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) +{ + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + 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 ; +} ; + +/*============================================================================== + * 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 ; + +typedef enum pipe_half +{ + in_fd = 0, + out_fd = 1, + +} pipe_half_t ; + +CONFIRM(STDIN_FILENO == 0) ; +CONFIRM(STDOUT_FILENO == 1) ; +CONFIRM(STDERR_FILENO == 2) ; + +typedef enum std_id +{ + stdin_fd = STDIN_FILENO, + stdout_fd = STDOUT_FILENO, + stderr_fd = STDERR_FILENO, + + stds = 3 + +} std_id_t ; + +typedef enum pipe_id +{ + in_pipe, + out_pipe, + err_pipe, + + pipe_count + +} 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_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, + pipe_half_t half) ; +static bool uty_pipe_fork_fail(vty_io vio, const char* cmd_str, + const char* what, pipe_set pipes, + 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 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_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 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 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 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 VIN_PIPE: pipe whose child's stdout is read and executed as commands. + * + * 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 + * child, which is sent to the current vout. + * + * The current vout is made the "slave" of the new vin "master". For the + * return the pr_vd reads into the "slave" obuf. + * + * 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_read_open(vty_io vio, qstring command, cmd_context context) +{ + pipe_set pipes ; + const char* cmd_str ; + vio_vf vf ; + pid_t child ; + qpath dir ; + + 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, vin_pipe) ; + + if (child < 0) + return CMD_WARNING ; + + /* We have a pipe, so now save context */ + 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, 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, -1, pipes[err_pipe][in_fd]) ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * 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. + * + * 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_cmd, bool after) +{ + pipe_set pipes ; + const char* cmd_str ; + pid_t child ; + vio_vf vf ; + + VTY_ASSERT_LOCKED() ; + + cmd_str = qs_make_string(command) ; + + /* Do the basic file open. */ + 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. + * + * 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_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, 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[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 ; +} ; + +/*------------------------------------------------------------------------------ + * 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 stdout and its stderr. + * + * 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 pr_fd, int ps_fd) +{ + vfd_io_type_t iot ; + + vf->child = uty_child_register(pid, vf) ; + + iot = vfd_io_read | (vf->blocking ? vfd_io_ps_blocking : 0) ; + + /* 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 ; + + qassert(vf->pr_timeout == 0) ; + + 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 ; + + qassert(vf->ps_timeout == 0) ; + + 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) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Fork fork fork + * + * Set up pipes according to type of fork: + * + * in_pipe -- input from child's stdout as main fd + * input from child's stderr as stderr return fd + * + * out_pipe -- output to child's stdin as main fd + * input from child's stdout as return fd + * input from child's stderr as stderr 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: + * + * -- in parent close the unused part(s) of the pair(s). + * + * -- 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_type_t type) +{ + pid_t child ; + int id ; + + /* Set pipes empty */ + for (id = 0 ; id < pipe_count ; ++id) + { + pipes[id][in_fd] = -1 ; + pipes[id][out_fd] = -1 ; + } ; + + /* Open as many pipes as are required. */ + if (type == vin_pipe) + if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd)) + return -1 ; + + 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, "stderr pipe", pipes, err_pipe, in_fd)) + return -1 ; + + /* Off to the races */ + + child = vfork() ; + + 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 */ + else + exit(0x80 | errno) ; + } + else if (child > 0) /* In parent -- success */ + { + /* 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[err_pipe], out_fd) ; + } + else if (child < 0) /* In parent -- failed */ + uty_pipe_fork_fail(vio, cmd_str, "vfork", pipes, NULL, "child") ; + + return child ; +} ; + +/*------------------------------------------------------------------------------ + * Open a pipe pair -- generate suitable error message if failed and close + * any earlier pipes that have been opened. + * + * Returns: true <=> success + */ +static bool +uty_pipe_pair(vty_io vio, const char* cmd_str, + const char* what, pipe_set pipes, + pipe_id_t id, + pipe_half_t half) +{ + pipe_pair pair ; + + pair = pipes[id] ; + + if (pipe(pair) < 0) + 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, NULL, + "set non-blocking for") ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * 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, + const char* what, pipe_set pipes, + pipe_pair pair, + const char* action) +{ + int err = errno ; + int id ; + + /* Close anything that has been opened */ + for (id = 0 ; id < pipe_count ; ++id) + { + if (pipes[id] == pair) + continue ; /* ignore if just failed to open */ + + uty_pipe_close_half_pipe(pipes[id], in_fd) ; + uty_pipe_close_half_pipe(pipes[id], out_fd) ; + } ; + + uty_out(vio, "%% Failed to %s %s for %s\n", action, what, cmd_str) ; + + errno = err ; + + return false ; +} ; + +/*------------------------------------------------------------------------------ + * Close half of a pipe pair, if it is open. + */ +static void +uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half) +{ + if (pair[half] >= 0) + { + close(pair[half]) ; + pair[half] = -1 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * In the child process... prepare to exec the shell. + * + * Discard all fd's except for the those required for the shell command. + * + * Arrange for the pipe fd's to be stdin/stdout/stderr as required. The pairs + * are named wrt to the parent process: + * + * in_pipe -- if present, is: stdout for the child + * out_pipe -- if present, is: stdin 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 ; + + /* 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[err_pipe][out_fd] ; /* stderr for child */ + + /* Mark everything to be closed on exec */ + for (fd = 0 ; fd < qlib_open_max ; ++fd) + { + int fd_flags ; + fd_flags = fcntl(fd, F_GETFD, 0) ; + if (fd_flags >= 0) + fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) ; + } ; + + /* Now dup anything of what we keep to ensure is above 2, making sure that + * the dup remains FD_CLOEXEC. + * + * This is highly unlikely, so no real extra work. It simplifies the next + * step which moves fds to the required position, and clears the FD_CLOEXEC + * flag on the duplicate. + */ + for (fd = 0 ; fd < stds ; ++fd) + { + if ((std[fd] >= 0) && (std[fd] < stds)) + if ((std[fd] = fcntl(std[fd], F_DUPFD_CLOEXEC, stds)) < 0) + return false ; + } ; + + /* Now dup2 to the required location -- destination is NOT FD_CLOEXEC. + */ + for (fd = 0 ; fd < stds ; ++fd) + { + if (std[fd] >= 0) + if (dup2(std[fd], fd) != fd) + return false ; + } ; + + /* Finally (for fds) if we don't have a stdout for the child, + * dup stderr to stdout. + */ + if ((std[stdout_fd] < 0) && (std[stderr_fd] >= 0)) + if (dup2(stderr_fd, stdout_fd) != stdout_fd) + return false ; + + /* Clear down all the signals magic. */ + quagga_signal_reset() ; + + /* Set the working directory */ + + if (qpath_setcwd(vio->vty->exec->context->dir_cd) != 0) + return false ; + + return true ; /* coming, ready or not */ +} ; + +/*------------------------------------------------------------------------------ + * 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, or times out. + * + * 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: 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 */ + + qassert(vf->vin_type == VIN_PIPE) ; + qassert(vf->vin_state == vf_open) ; + + while (1) /* so blocking stuff can loop round */ + { + cmd_return_code_t ret ; + int get ; + qps_mini_t qm ; + + /* Try to complete line straight from the buffer. + * + * If buffer is empty and have seen eof on the input, signal CMD_EOF. + */ + ret = uty_fifo_command_line(vf, action) ; + + if (ret != CMD_WAITING) + return ret ; + + /* If blocking, worry about the stderr return -- just to keep I/O moving. + * + * Expect only CMD_SUCCESS or CMD_IO_ERROR. + */ + if (vf->blocking && (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 */ + } ; + + /* 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 uty_vf_error(vf, verr_io_vin, errno) ; + + if (get == -2) /* EOF met immediately */ + { + vf->vin_state = vf_end ; + continue ; /* loop back -- deals with possible + final line and the return. */ + } ; + + qassert(get == 0) ; + + /* 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) + { + uty_vf_set_read(vf, on) ; + return CMD_WAITING ; + } ; + + /* 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) + continue ; /* loop back */ + + return uty_vf_error(vf, verr_to_vin, 0) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Command output push to a pipe and (for blocking) shovel return into + * slave and push. + * + * 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 -- 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. 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. + * + * This can be called in any thread. + */ +extern cmd_return_code_t +uty_pipe_out_push(vio_vf vf, bool final) +{ + VTY_ASSERT_LOCKED() ; /* In any thread */ + + 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->ps_state == vf_open)) + { + cmd_return_code_t ret ; + + 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 */ + } ; + + /* 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 + { + while (1) + { + qps_mini_t qm ; + int put ; + + /* 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 ; + + ret = uty_pipe_shovel(vf, final) ; + + if (ret != CMD_SUCCESS) + return ret ; /* cannot continue */ + } ; + + 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 ; + + if (put < 0) + return uty_vf_error(vf, verr_io_vout, errno) ; + + /* 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 + * + * 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 (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 */ + + return uty_vf_error(vf, verr_to_vout, 0) ; + } ; + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * 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. + * + * * VOUT_SH_CMD: the main vin/vout is closed from the get go. + * + * All pipe return is handled by the close process. + * + * When the main vin/vout is closed: + * + * * VOUT_PIPE & VOUT_SH_CMD: + * + * 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 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 once the main vout is closed. + * + * 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) +{ + 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->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. + */ + while (1) + { + cmd_return_code_t ret ; + int get ; + + get = vio_fifo_read_nb(vf->vout_next->obuf, vio_vfd_fd(vf->pr_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->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 */ + } ; + + qps_mini_set(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum, + 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) /* Read something */ + { + ret = uty_cmd_out_push(vf->vout_next, final) ; /* may block */ + + 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) /* Hit error */ + { + return uty_vf_error(vf, verr_io_pr, errno) ; /* CMD_IO_ERROR */ + } + + else + { + assert (get == -2) ; /* eof on the return */ + + vf->pr_state = vf_end ; + break ; /* quit: OK */ + } ; + } ; + + 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 ; + } + + 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 -- call-back for VIN_PIPE. + * + * 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 +vty_pipe_read_ready(vio_vfd vfd, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + 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 -- 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 +vty_pipe_read_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->read_timer == timer) ; + + uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */ + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Pipe return is ready to read -- call-back for VOUT_PIPE and VOUT_SH_CMD. + * + * This is used if the VOUT_PIPE/VOUT_SH_CMD is non-blocking. + * + * Shovels any available return input to the output. If required, the shoveller + * will set read ready when runs out of input. + * + * 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. + */ +static void +vty_pipe_return_ready(vio_vfd vfd, void* action_info) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->pr_vfd == vfd) ; + + /* 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 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. + * + * Shovels any available return input to the output. If required, the shoveller + * will set read ready when runs out of input. + * + * 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. + */ +static void +vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info) +{ + 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 ; + + 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 */ + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * 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_return_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->pr_vfd->read_timer == timer) ; + + 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() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * 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 +vty_pipe_write_ready(vio_vfd vfd, void* action_info) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + /* 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) ; /* CMD_SUCCESS or CMD_IO_ERROR */ + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * 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 +vty_pipe_write_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->write_timer == timer) ; + + uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */ + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * 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_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 -- 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) +{ + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + 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->vout_state == vf_open) + { + cmd_return_code_t ret ; + + ret = uty_pipe_out_push(vf, final) ; + + if ((ret == CMD_WAITING) && !final) + return ret ; + + 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 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 the child is not collected, or has not terminated cleanly, output + * diagnostic to the pipe stderr return. + * + * If the child is not collected, the SIGCHLD handling will tidy up in the + * background. + * + * In any case, dismiss child. + * + * Phase 4: transfer the pipe stderr return to the main vio pipe stderr return. + * + * 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) +{ + vty_io vio ; + + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + 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) + { + cmd_return_code_t ret ; + bool set_timeout ; + + set_timeout = (vf->pr_timeout == 0) ; + vf->pr_timeout = pipe_timeout ; + + if (!vf->blocking) + { + if (!final) + { + if (set_timeout) + vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ; + + return CMD_WAITING ; /* in hands of pselect() */ + } ; + + vio_vfd_set_read(vf->pr_vfd, off, 0) ; + } ; + + ret = uty_pipe_shovel(vf, final) ; + + if ((ret == CMD_WAITING) && !final) + return ret ; + } ; + + if (vf->pr_state != vf_closed) + { + vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; + vf->pr_state = vf_closed ; + } ; + + /* 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 ; + + set_timeout = (vf->ps_timeout == 0) ; + vf->ps_timeout = pipe_timeout ; + + if (!vf->blocking) + { + if (!final) + { + if (set_timeout) + vio_vfd_set_read(vf->ps_vfd, on, vf->pr_timeout) ; + + 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) + { + 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->child->collected) + { + 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) ; + } ; + + /* Can now dismiss the child -- if not collected, is left on the register. + */ + uty_child_dismiss(vf->child, false) ; /* not curtains */ + } ; + + /* Phase 4: if there is anything in the pipe stderr return buffer, finish + * it, and transfer to the vio->ps_buf (unless vio->cancel). + */ + if (vf->ps_buf != NULL) + { + 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 = "|" ; + + 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 ; +} ; + +/*------------------------------------------------------------------------------ + * Stop pipe return and/or pipe stderr return. + * + * 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. + */ +extern void +uty_pipe_return_stop(vio_vf vf) +{ + if (vf->pr_state == vf_open) + vf->pr_state = vf_end ; + + if (vf->ps_state == vf_open) + vf->ps_state = vf_end ; + + if (vf->vin_state == vf_open) + vf->vin_state = vf_end ; + + if (vf->vout_state == vf_open) + vf->vout_state = vf_end ; +} ; + +/*------------------------------------------------------------------------------ + * 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) ; + + vf->pr_state = vf_end ; +} ; + +/*------------------------------------------------------------------------------ + * 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 ; +} ; + +/*============================================================================== + * stdout and stderr + * + * + */ + |