diff options
Diffstat (limited to 'lib/vty_io_file.c')
-rw-r--r-- | lib/vty_io_file.c | 1802 |
1 files changed, 1704 insertions, 98 deletions
diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c index 2d17276a..2322114a 100644 --- a/lib/vty_io_file.c +++ b/lib/vty_io_file.c @@ -20,114 +20,339 @@ * 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" /*============================================================================== - * VTY File Output + * VTY Configuration file I/O -- VIN_CONFIG and VOUT_CONFIG. + * * - * This is for input and output of configuration files and piped stuff. * - * 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. */ +/*------------------------------------------------------------------------------ + * Set up VTY on which to read configuration file -- using already open fd. + * + * Sets TODO + * + * 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. + */ +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_blocking, because the vio is set blocking. + */ + vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ; + + uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ; + uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ; + + /* 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() ; + + uty_child_signal_nexus_clear(vf->vio) ; + + return CMD_SUCCESS ; +} ; /*============================================================================== - * Prototypes. + * VTY File I/O -- VIN_FILE and VOUT_FILE + * + * This is for input and output of configuration files and piped stuff. + * + * 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 uty_file_read_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_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, + 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. + * + * 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, bool reflect) +uty_file_read_open(vty_io vio, qstring name, cmd_context context) { - const char* name_str ; - int fd ; - vio_vf vf ; + cmd_return_code_t ret ; + qpath path ; + const char* pns ; + int fd ; + vio_vf vf ; vfd_io_type_t iot ; - name_str = qs_make_string(name) ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + assert(vio->vin != NULL) ; /* Not expected to be vin_base */ + + /* Now is the time to complete the name, if required. */ - iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */ + path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ; + pns = qpath_string(path) ; /* Do the basic file open. */ - fd = uty_vfd_file_open(name_str, iot) ; + iot = vfd_io_read ; + + fd = uty_vfd_file_open(pns, iot) ; if (fd < 0) { - uty_out(vio, "%% Could not open input file %s\n", name_str) ; + uty_out(vio, "%% Could not open input file %s\n", pns) ; - return CMD_WARNING ; + ret = CMD_WARNING ; /* TODO add errno to message ? */ } + 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, name_str, fd, vfd_file, iot) ; - uty_vin_open(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ; + /* 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) ; - vf->parse_type = cmd_parse_strict ; - vf->reflect_enabled = reflect ; + ret = CMD_SUCCESS ; + } ; - return CMD_SUCCESS ; + qpath_free(path) ; + return ret ; } ; /*------------------------------------------------------------------------------ + * Open file for output. + * + * If could not open, issues message to the vio. * + * 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) +uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context) { - const char* name_str ; + cmd_return_code_t ret ; + qpath path ; + const char* pns ; int fd ; vio_vf vf ; vfd_io_type_t iot ; - iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */ + VTY_ASSERT_CLI_THREAD_LOCKED() ; - if (append) - iot |= vfd_io_append ; + /* Now is the time to complete the name, if required. */ - name_str = qs_make_string(name) ; + path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ; + pns = qpath_string(path) ; /* Do the basic file open. */ - fd = uty_vfd_file_open(name_str, iot) ; + iot = vfd_io_write | (append ? vfd_io_append : 0) ; + + fd = uty_vfd_file_open(pns, iot) ; if (fd < 0) { - uty_out(vio, "%% Could not open output file %s\n", name_str) ; + uty_out(vio, "%% Could not open output file %s\n", pns) ; - return CMD_WARNING ; + ret = CMD_WARNING ; /* TODO add errno to message ? */ } + 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) ; + ret = CMD_SUCCESS ; + } ; - /* OK -- now push the new input onto the vin_stack. */ - vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; - uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; - - vf->out_enabled = true ; - - return CMD_SUCCESS ; + qpath_free(path) ; + return ret ; } ; -/*============================================================================== +/*------------------------------------------------------------------------------ * Command line fetch from a file or pipe. * - * Returns: CMD_SUCCESS -- have another command line ready to go - * CMD_WAITING -- do not have a command line at the moment - * CMD_EOF -- ran into EOF - * CMD_IO_ERROR -- ran into an I/O error + * 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 + * + * 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 will become vf_eof the first time that CMD_EOF is + * returned. */ extern cmd_return_code_t -uty_file_fetch_command_line(vio_vf vf, qstring* line) +uty_file_fetch_command_line(vio_vf vf, cmd_action action) { - assert(vf->vin_state == vf_open) ; + 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)) ; + + 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 CMD_IO_ERROR ; /* register error */ + + if (get == -2) + { + /* Hit end of file set the vf into vf_eof. + * + * 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. + */ + assert(vio_fifo_empty(vf->ibuf)) ; + + vf->vin_state = vf_eof ; + + 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 + */ + assert(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) + return CMD_IO_TIMEOUT ; /* TODO vf_timeout ?? */ + + /* Loop back to vio_fifo_read_nb() */ + } + } ; +} ; + +/*------------------------------------------------------------------------------ + * Try to complete a command line for the given vf, from the current input + * fifo -- performs NO I/O + * + * 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 + * 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) { @@ -142,47 +367,42 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) while (1) { - char* s, * p, * q, * e ; + char* s, * p, * e ; size_t have ; ulen len ; + bool eol ; + /* Get what we can from the fifo */ s = vio_fifo_get(vf->ibuf, &have) ; - /* If nothing in hand, try and get some more. - * - * Either exits or loops back to set s & have. - */ + /* If fifo is empty, may be last line before eof, eof or waiting */ if (have == 0) { - int get ; - - get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ; - - if (get > 0) - continue ; /* loop back */ - - if (get == 0) - return CMD_WAITING ; /* need to set read ready ! */ - - if (get == -1) - ; /* register error */ + if (vf->vin_state == vf_eof) + { + if (qs_len_nn(vf->cl) > 0) + break ; /* have non-empty last line */ + else + return CMD_EOF ; + } ; - if (get == -2) - return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ; + return CMD_WAITING ; } ; + assert(vf->vin_state != vf_eof) ; /* 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. + * This means that we cope with "\r\n" line terminators. But not + * anything more exotic. */ p = s ; e = s + have ; /* have != 0 */ - q = NULL ; + eol = false ; while (p < e) { if (*p++ < 0x20) @@ -195,9 +415,7 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) ++vf->line_step ; /* got a '\n' */ - q = p ; /* point just past '\n' */ - do --q ; while ((q > s) && (*(q-1) == ' ')) ; - /* discard trailing "spaces" */ + eol = true ; break ; } ; } ; @@ -212,28 +430,30 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) * * Loops back to try to get some more form the fifo. */ - if (q == NULL) + if (!eol) { - qs_append_n(vf->cl, s, p - s) ; + 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 = q - s ; /* length to add */ + len = p - s ; /* length to add */ if (qs_len_nn(vf->cl) == 0) - { - qs_set_alias_n(vf->cl, s, len) ; - p = q ; - } + qs_set_alias_n(vf->cl, s, len) ; else { if (len != 0) - qs_append_n(vf->cl, s, len) ; + qs_append_str_n(vf->cl, s, len) ; s = qs_char_nn(vf->cl) ; p = s + qs_len_nn(vf->cl) ; @@ -265,10 +485,10 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) * Note that this rule deals with the case of the continuation line * being empty... e.g. ....\\\ n n -- where n is '\n' */ - q = p ; - do --q ; while ((q > s) && (*(q-1) == '\\')) ; + e = p ; + do --p ; while ((p > s) && (*(p-1) == '\\')) ; - if (((p - q) & 1) == 0) + if (((e - p) & 1) == 0) break ; /* even => no continuation => success */ qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */ @@ -276,47 +496,1433 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) continue ; /* loop back to fetch more */ } ; - /* Success have a line in hand */ + /* Success: have a line in hand */ vf->line_complete = true ; - *line = vf->cl ; + action->to_do = cmd_do_command ; + action->line = vf->cl ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe is ready to read -- this is the call-back planted in the + * vf->vfd. + * + * The command_queue processing can continue. + * + * 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) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + 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. + * + * ???? + */ +static vty_timer_time +uty_file_read_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) ; + +//cq_continue(vf->vio->vty) ; TODO + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Command output push to a file. + * + * 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. + * + * 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. + * + * 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) + * + * In "non-blocking" state, on CMD_WAITING the caller will have to take steps + * to come back later. + * + * 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_file_out_push(vio_vf vf, bool final) +{ + 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) + { + qps_mini_t qm ; + int n ; + + 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) || final) /* all gone (or as much as can go) */ + return CMD_SUCCESS ; + + /* Cannot write everything away without waiting */ + if (!vf->blocking) + { + uty_vf_set_write(vf, on) ; + 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 ; + + /* Loop back to vio_fifo_write_nb() */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe is ready to write -- this is the call-back planted in the + * vf->vfd. + * + * 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) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + ret = uty_file_out_push(vf, false) ; /* re-enable write ready if required */ + + if (ret != CMD_WAITING) + uty_cmd_signal(vf->vio, ret) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe is ready to write -- this is the call-back planted in the + * vf->vfd. + * + * Note that the read_ready state and any time out are automatically + * disabled when they go off. + */ +static vty_timer_time +uty_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_file_out_push(vf) ; // TODO ??????? + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after input file has been closed. + * + * Nothing further required -- input comes to a halt. + */ +extern cmd_return_code_t +uty_file_read_close(vio_vf vf, bool final) +{ return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Command output push to a file or pipe. + * Flush output buffer and close. + * + * See uty_file_out_push() * - * Returns: CMD_SUCCESS -- done - * CMD_IO_ERROR -- ran into an I/O error + * Returns: CMD_SUCCESS -- buffers are empty, or final + * CMD_WAITING -- waiting for I/O to complete + * CMD_xxx TODO */ extern cmd_return_code_t -uty_file_out_push(vio_vf vf) +uty_file_write_close(vio_vf vf, bool final, bool base) { - assert(vf->vout_state == vf_open) ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; + assert(vf->vout_state == vf_closing) ; + assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ; - if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0) - return CMD_SUCCESS ; + return uty_file_out_push(vf, final) ; +} ; + +/*============================================================================== + * Shell pipe stuff + * + */ +typedef int pipe_pair_t[2] ; +typedef int* pipe_pair ; + +enum pipe_half +{ + in_fd = 0, + out_fd = 1, +} ; +typedef enum pipe_half pipe_half_t ; + +CONFIRM(STDIN_FILENO == 0) ; +CONFIRM(STDOUT_FILENO == 1) ; +CONFIRM(STDERR_FILENO == 2) ; + +enum +{ + stdin_fd = STDIN_FILENO, + stdout_fd = STDOUT_FILENO, + stderr_fd = STDERR_FILENO, + + stds = 3 +} ; + +enum pipe_id +{ + in_pipe, + out_pipe, + ret_pipe, + + pipe_count +} ; +typedef enum pipe_id pipe_id_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) ; +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 ret_fd, + vio_vf slave) ; +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 void uty_pipe_read_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_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, + 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, + 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. + * + * If could not open, issues message to the vio. + * + * 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. + * + * 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 + */ +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 ; + + VTY_ASSERT_CLI_THREAD_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) ; + + if (child < 0) + return CMD_WARNING ; + + /* We have a pipe, so now save context */ + uty_vin_new_context(vio, context, NULL) ; + + /* 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) ; + + /* 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) ; + + 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. + * + * 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. + * + * 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) +{ + pipe_set pipes ; + const char* cmd_str ; + pid_t child ; + vio_vf vf ; + + VTY_ASSERT_CLI_THREAD_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) ; + if (child < 0) + return CMD_WARNING ; + + /* OK -- now push the new output onto the vout_stack. */ + + if (shell_only) + { + vf = uty_vf_new(vio, cmd_str, -1, vfd_none, vfd_io_none) ; + uty_vout_push(vio, vf, VOUT_SHELL_ONLY, NULL, NULL, 0) ; + } else - return CMD_IO_ERROR ; + { + 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) ; + } ; + + /* 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 */ + + 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 combined stdout/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. + */ +static void +uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave) +{ + 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) ; + + vf->pr_vfd = vio_vfd_new(ret_fd, vfd_pipe, iot, vf) ; + vf->pr_state = vf_open ; + + vf->pr_only = false ; + + if (!vf->blocking) + vio_vfd_set_read_action(vf->pr_vfd, uty_pipe_return_ready) ; + + /* Configure master/slave relationship. */ + slave->pr_master = vf ; + vf->pr_slave = slave ; +} ; + +/*------------------------------------------------------------------------------ + * 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 return fd + * + * out_pipe -- output to child's stdin as main fd + * input from child's stderr & stdout as return fd + * + * ret_pipe -- nothing for main fd + * input from child's stderr & stdout as 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. + */ +static pid_t +uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_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 == in_pipe) + if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd)) + return -1 ; + + if (type == out_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)) + 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[ret_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 early pipes that have been opened. + */ +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, pair, + "set non-blocking") ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Open a pipe pair -- generate suitable error message if failed and close + * any early pipes that have been opened. + */ +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 + * ret_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. + */ +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[ret_pipe][out_fd] ; /* stderr for child */ + + /* Mark everything to be closed on exec */ + for (fd = 0 ; fd <= 1024 ; ++fd) /* TODO -- 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, 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 and (for blocking) shovel return into + * slave and push. + * + * 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 + * + * If returns CMD_EOF will have vf->vin_state == vf_eof. Further, will not + * have vf_eof until returns CMD_EOF (the first time). + * + * 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. + */ +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 ? */ + + 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_SUCCESS) || (ret == CMD_EOF)) + return ret ; + + /* Worry about the return. + * + * 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). + */ + if (vf->blocking && (vf->pr_state == vf_open)) + { + ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */ + + if ((ret != CMD_SUCCESS) && (ret != CMD_EOF)) + 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 */ + + if (get == -2) /* EOF met immediately */ + { + vf->vin_state = vf_eof ; + continue ; /* loop back -- deals with possible + final line and the return. */ + } ; + + assert (get == 0) ; + + /* We get here if main input is not yet at eof. */ + assert ((vf->vin_state == vf_open) || (vf->pr_state == vf_open)) ; + + if (!vf->blocking) + { + uty_vf_set_read(vf, on) ; + return CMD_WAITING ; + } ; + + /* Implement blocking I/O, with timeout */ + 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 ; + + continue ; /* loop back */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * 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. + * + * 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) + * + * 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. + * + * 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 */ + 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. + */ + if (vf->blocking && (vf->pr_state == vf_open)) + { + cmd_return_code_t ret ; + + ret = uty_pipe_shovel(vf, final, false) ; /* not closing */ + + if ((ret != CMD_SUCCESS) && (ret != CMD_EOF) && !final) + return ret ; + } ; + + /* Now write away everything we can -- nothing if pr_only. + */ + if (!vf->pr_only) + { + 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 (put < 0) + return CMD_IO_ERROR ; /* TODO */ + + if ((put == 0) || final) /* all gone or final */ + break ; + + /* Cannot write everything away without waiting */ + if (!vf->blocking) + { + uty_vf_set_write(vf, on) ; + 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 ; + + /* Loop back to vio_fifo_write_nb() */ + } ; + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Shovel from return to slave, pushing the slave as we go. + * + * 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. + * + * 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 + * + * For VOUT_SHELL_ONLY: same as VOUT_PIPE. + * + * For blocking, keeps going until can read no more from the return. The slave + * output may block, and could time out. + * + * 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 "final" does not attempt to read anything from the return, but sets it + * vf_eof. + * + * For "closing" (if not "final"), if blocking will block on the return, until + * get eof (or time out). + * + * 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 + * + * This can be called in any thread. + */ +static cmd_return_code_t +uty_pipe_shovel(vio_vf vf, bool final, bool closing) +{ + vio_vf slave ; + + VTY_ASSERT_LOCKED() ; /* In any thread */ + + // TODO other pr_state ??? vf_closed/vf_closing/vf_error ?? + + /* 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)) + { + cmd_return_code_t ret ; + int get ; + + get = vio_fifo_read_nb(slave->obuf, vio_vfd_fd(vf->pr_vfd), 10) ; + + if (get == 0) + { + qps_mini_t qm ; + + if (!closing || !vf->blocking) + return CMD_SUCCESS ; /* quit if now dry */ + + /* 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 ; + } + + else if (get >= 0) + { + ret = uty_cmd_out_push(slave, final) ; /* may block etc. */ + + if (ret != CMD_SUCCESS) + return ret ; + } + + else if (get == -1) + return CMD_IO_ERROR ; /* register error TODO */ + + else + { + assert (get == -2) ; /* eof on the return */ + break ; + } ; + } ; + + vf->pr_state = vf_eof ; /* Set EOF TODO: willy nilly ? */ + + return CMD_EOF ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe is ready to read -- this is the call-back planted in the vf->vfd, of + * type VIN_PIPE. + * + * Can restart the command_queue. + * + * 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) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + 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. + * + * ???? + */ +static vty_timer_time +uty_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) ; + +// TODO .... signal time out + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ } ; /*------------------------------------------------------------------------------ - * Tidy up after input file has been closed + * 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. + * + * VOUT_SHELL_ONLY: used to continually shovel from the return to the + * slave -- which happens while closing. + * + * If all is well, and more return input is expected, re-enables read ready. + * + * 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(). + * + * For all other returns, kick the command loop, which if it is waiting is TODO + */ +static void +uty_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) ; + + 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 */ + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe return slave is ready to write. + * + * 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. + * + * This will set read ready on the return, to keep the process of shovelling + * from return to slave going. */ extern void -uty_file_read_close(vio_vf vf) +uty_pipe_return_slave_ready(vio_vf slave) { - return ; + vio_vf vf ; + + vf = slave->pr_master ; + assert(vf->pr_slave == slave) ; + + vio_vfd_set_read(vf->pr_vfd, on, 0) ; } ; /*------------------------------------------------------------------------------ - * Flush output buffer and close. + * 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 ???? * - * Returns: true <=> buffer (now) empty + * ???? */ -extern bool -uty_file_write_close(vio_vf vf, bool final) +static vty_timer_time +uty_pipe_return_timeout(vio_timer timer, void* action_info) { - return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->pr_vfd->read_timer == timer) ; + +//cq_continue(vf->vio->vty) ; TODO + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ } ; + +/*------------------------------------------------------------------------------ + * Pipe output is ready to write -- this is the call-back planted in the + * vf->vfd of a VOUT_PIPE + * + * 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) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + ret = uty_pipe_out_push(vf, false) ; /* not final */ + + if (ret != CMD_WAITING) + uty_cmd_signal(vf->vio, ret) ; + + if ((ret == CMD_SUCCESS) && (vf->pr_master != NULL)) + uty_pipe_return_slave_ready(vf) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe output has timed out -- this is the call-back planted in the + * vf->vfd of a VOUT_PIPE + * + * ???? + */ +static vty_timer_time +uty_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) ; + +// TODO + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * 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. + */ +extern cmd_return_code_t +uty_pipe_read_close(vio_vf vf, bool final) +{ + return uty_pipe_return_close(vf, final) ; +} ; + +/*------------------------------------------------------------------------------ + * Close VOUT_PIPE or VOUT_SHELL_ONLY. + * + * 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 + */ +extern cmd_return_code_t +uty_pipe_write_close(vio_vf vf, bool final, bool base, bool shell_only) +{ + 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. + */ + if (!vf->pr_only) + { + ret = uty_pipe_out_push(vf, final) ; + + if ((ret != CMD_SUCCESS) && !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 */ + } ; + + /* 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. + * + * If not final, may need to drain the 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. + * + * Once any message has been pushed to the slave, can release the slave + * and the close process will be complete. + * + * If final, then will not block and will ignore errors. Return code reflects + * the last operation. + * + * Returns: CMD_SUCCESS -- closed and child collected + * CMD_WAITING + * CMD_IO_ERROR + * CMD_IO_TIMEOUT + * CMD_xxxx -- child error(s) + */ +static cmd_return_code_t +uty_pipe_return_close(vio_vf vf, bool final) +{ + cmd_return_code_t ret ; + + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + /* If the return is still open, try to empty and close that. */ + if (vf->pr_state != vf_closed) + { + ret = uty_pipe_return_empty(vf, final) ; + + if (!final) + { + if (ret == CMD_SUCCESS) + { + vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */ + return CMD_WAITING ; + } ; + + if (ret != CMD_EOF) + return ret ; + } ; + } ; + + /* If not already collected, collect the child. */ + if (vf->child != NULL) + { + ret = uty_pipe_collect_child(vf, final) ; + + if ((ret != CMD_SUCCESS) && !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) + { + vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; + vf->pr_state = vf_closed ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * 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 ; + + assert(vf->child != NULL) ; + + /* Collect -- blocking or not blocking */ + if (!vf->child->collected && !vf->child->overdue) + { + /* If we are !blocking and !final, leave the child collection up to + * the SIGCHLD system. + */ + if (!vf->blocking && !final) + { + uty_child_awaited(vf->child, 6) ; + return CMD_WAITING ; + } ; + + /* If we are blocking or final, try to collect the child here and + * now -- if not final may block here. + */ + uty_child_collect(vf->child, 6, final) ; + } ; + + /* If child is overdue, or did not terminate cleanly, write message to + * slave... which we then have to push... + */ + slave = vf->pr_slave ; + assert((slave != NULL) && (vf == slave->pr_master)) ; + + if (!vf->child->collected) + { + 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) ; + } ; + + /* Can now dismiss the child. */ + vf->child = uty_child_dismiss(vf->child, false) ; /* not final */ + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Push output to slave a final time -- to shift any final message about state + * of child on closing. + * + * Then release slave. + */ +static cmd_return_code_t +uty_pipe_release_slave(vio_vf vf, bool final) +{ + cmd_return_code_t ret ; + vio_vf slave ; + + ret = CMD_SUCCESS ; + + slave = vf->pr_slave ; + if (slave != NULL) + { + assert(vf == slave->pr_master) ; + + ret = uty_cmd_out_push(slave, final) ; /* may block etc. */ + + if ((ret != CMD_SUCCESS) && !final) + return ret ; + + slave->pr_master = NULL ; /* release slave */ + vf->pr_slave = NULL ; + } ; + + return ret ; +} ; + +/*============================================================================== + * stdout and stderr + * + * + */ + |