diff options
Diffstat (limited to 'lib/vty_io.c')
-rw-r--r-- | lib/vty_io.c | 3637 |
1 files changed, 1539 insertions, 2098 deletions
diff --git a/lib/vty_io.c b/lib/vty_io.c index 74e32c75..81af4e5e 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -1,4 +1,4 @@ -/* VTY IO Functions +/* VTY IO Functions -- top level of VTY IO hierarchy * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -20,14 +20,19 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ - -#include "zebra.h" +#include "misc.h" #include "vty.h" #include "vty_io.h" +#include "vty_io_term.h" +#include "vty_io_file.h" #include "vty_cli.h" +#include "vty_command.h" #include "qstring.h" #include "keystroke.h" +#include "list_util.h" +#include "command_parse.h" +#include "command_execute.h" #include "memory.h" @@ -41,148 +46,37 @@ #include <arpa/telnet.h> #include <sys/un.h> /* for VTYSH */ #include <sys/socket.h> +#include <wait.h> #define VTYSH_DEBUG 0 /*============================================================================== - * VTY Command Output -- base functions - * - * During command processing the output sent here is held until the command - * completes. + * Basic output to VTY. */ -static int uty_config_write(vty_io vio, bool all) ; - /*------------------------------------------------------------------------------ - * VTY output function -- cf fprintf + * VTY output -- cf fprintf ! Same as vty_out, less the VTY_LOCK(). * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - */ -extern int -uty_out (struct vty *vty, const char *format, ...) -{ - int result; - VTY_ASSERT_LOCKED() ; - va_list args; - va_start (args, format); - result = uty_vout(vty, format, args); - va_end (args); - return result; -} - -/*------------------------------------------------------------------------------ - * VTY output function -- cf vfprintf + * This is for command output, which may later be suppressed * * Returns: >= 0 => OK * < 0 => failed (see errno) - * - * NB: for VTY_TERM and for VTY_SHELL_SERV -- this is command output: - * - * * MAY NOT do any command output if !cmd_enabled - * - * * first, the life of a vty is not guaranteed unless cmd_in_progress, - * so should not attempt to use a vty anywhere other than command - * execution. - * - * * second, cmd_out_enabled is false most of the time, and is only - * set true when a command completes, and it is time to write away - * the results. - * - * * all output is placed in the vio->cmd_obuf. When the command completes, - * the contents of the cmd_obuf will be written away -- subject to line - * control. - * - * * output is discarded if the vty is no longer write_open */ extern int -uty_vout(struct vty *vty, const char *format, va_list args) +uty_out(vty_io vio, const char *format, ...) { - vty_io vio ; - int ret ; + int ret ; + va_list args ; VTY_ASSERT_LOCKED() ; - vio = vty->vio ; - - switch (vio->type) - { - case VTY_STDOUT: - case VTY_SHELL: - ret = vprintf (format, args) ; - break ; - - case VTY_STDERR: - ret = vfprintf (stderr, format, args) ; - break ; - - case VTY_CONFIG_WRITE: - ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; - if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf)) - ret = uty_config_write(vio, false) ; - break ; - - case VTY_TERM: - case VTY_SHELL_SERV: - assert(vio->cmd_in_progress) ; - - if (!vio->sock.write_open) - return 0 ; /* discard output if not open ! */ - - /* fall through.... */ - - case VTY_CONFIG_READ: - ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; - break ; - - default: - zabort("impossible VTY type") ; - } ; + va_start (args, format) ; + ret = uty_vprintf(vio, format, args) ; + va_end (args) ; return ret ; } ; -/*------------------------------------------------------------------------------ - * Clear the contents of the command output FIFO etc. - * - * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc - * flags -- competent parties must deal with those - */ -extern void -uty_out_clear(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - vio_fifo_clear(&vio->cmd_obuf) ; - - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; -} ; - -/*------------------------------------------------------------------------------ - * Flush the contents of the command output FIFO to the given file. - * - * Takes no notice of any errors ! - */ -extern void -uty_out_fflush(vty_io vio, FILE* file) -{ - char* src ; - size_t have ; - - VTY_ASSERT_LOCKED() ; - - fflush(file) ; - - while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) - { - fwrite(src, 1, have, file) ; - vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; - } ; - - fflush(file) ; -} ; - /*============================================================================== * The watch dog. * @@ -190,2553 +84,2100 @@ uty_out_fflush(vty_io vio, FILE* file) * * * for changes to the host name, which should be reflected in the * prompt for any terminals. - * - * * the death watch list */ -enum { vty_watch_dog_interval = 5 } ; - -static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) ; -static int vty_watch_dog_thread(struct thread *thread) ; +/* Watch-dog timer */ +static vio_timer_t vty_watch_dog ; -static void uty_watch_dog_bark(void) ; -static bool uty_death_watch_scan(void) ; +static vty_timer_time uty_watch_dog_bark(vio_timer timer, void* info) ; /*------------------------------------------------------------------------------ - * Start watch dog -- the first time a VTY is created. + * Start watch dog -- before a VTY is created. + * + * The host name is set during initialisation, so no need to check it here. */ extern void -uty_watch_dog_start() +uty_watch_dog_start(void) { - if (vty_cli_nexus) - vty_watch_dog.qnexus = qtimer_init_new(NULL, vty_cli_nexus->pile, - NULL, NULL) ; - - uty_watch_dog_bark() ; /* start up by barking the first time */ -} + vio_timer_init_new(vty_watch_dog, uty_watch_dog_bark, NULL) ; + vio_timer_set(vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ; +} ; /*------------------------------------------------------------------------------ * Stop watch dog timer -- at close down. - * - * Final run along the death-watch - * */ extern void uty_watch_dog_stop(void) { - if (vty_watch_dog.anon != NULL) - { - if (vty_cli_nexus) - qtimer_free(vty_watch_dog.qnexus) ; - else - thread_cancel(vty_watch_dog.thread) ; - } ; - - uty_death_watch_scan() ; /* scan the death-watch list */ -} - -/*------------------------------------------------------------------------------ - * qnexus watch dog action - */ -static void -vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) -{ - VTY_LOCK() ; - - uty_watch_dog_bark() ; - - VTY_UNLOCK() ; -} ; - -/*------------------------------------------------------------------------------ - * thread watch dog action - */ -static int -vty_watch_dog_thread(struct thread *thread) -{ - VTY_LOCK() ; - - vty_watch_dog.thread = NULL ; - uty_watch_dog_bark() ; - - VTY_UNLOCK() ; - return 0 ; + vio_timer_reset(vty_watch_dog, keep_it) ; } ; /*------------------------------------------------------------------------------ - * Watch dog action + * Watch dog vio_timer action */ -static void -uty_watch_dog_bark(void) +static vty_timer_time +uty_watch_dog_bark(vio_timer timer, void* info) { - uty_check_host_name() ; /* check for host name change */ - - uty_death_watch_scan() ; /* scan the death-watch list */ - - /* Set timer to go off again later */ - if (vty_cli_nexus) - qtimer_set(vty_watch_dog.qnexus, - qt_add_monotonic(QTIME(vty_watch_dog_interval)), - vty_watch_dog_qnexus) ; - else - { - if (vty_watch_dog.thread != NULL) - thread_cancel (vty_watch_dog.thread); - vty_watch_dog.thread = thread_add_timer (vty_master, - vty_watch_dog_thread, NULL, vty_watch_dog_interval) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Scan the death watch list. - * - * A vty may finally be freed if it is closed and there is no command in - * progress. - */ -static bool -uty_death_watch_scan(void) -{ - vty_io vio ; - vty_io next ; - - next = vio_death_watch ; - while (next != NULL) - { - vio = next ; - next = sdl_next(vio, vio_list) ; - - if (vio->closed && !vio->cmd_in_progress) - { - uty_close(vio) ; /* closes again to ensure that all buffers - are released. */ + cmd_host_name(true) ; /* check for host name change */ - sdl_del(vio_death_watch, vio, vio_list) ; - - XFREE(MTYPE_VTY, vio->vty) ; - XFREE(MTYPE_VTY, vio) ; - } ; - } ; - - return (vio_death_watch == NULL) ; + return VTY_WATCH_DOG_INTERVAL ; } ; /*============================================================================== * Prototypes. */ -static void uty_sock_init_new(vio_sock sock, int fd, void* info) ; -static void uty_sock_half_close(vio_sock sock) ; -static void uty_sock_close(vio_sock sock) ; - -static void vty_read_qnexus (qps_file qf, void* file_info) ; -static void vty_write_qnexus (qps_file qf, void* file_info) ; -static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ; - -static int vty_read_thread (struct thread *thread) ; -static int vty_write_thread (struct thread *thread) ; -static int vty_timer_thread (struct thread *thread) ; - -static void vtysh_read_qnexus (qps_file qf, void* file_info) ; -static int vtysh_read_thread (struct thread *thread) ; - -static enum vty_readiness uty_write(vty_io vio) ; +static void uty_vout_close_reason(vio_vf vf, const char* reason) ; +static cmd_return_code_t uty_vf_read_close(vio_vf vf, bool final) ; +static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) ; +static vio_vf uty_vf_free(vio_vf vf) ; /*============================================================================== * Creation and destruction of VTY objects */ /*------------------------------------------------------------------------------ - * Allocate new vty struct + * Allocate new vty structure, including empty vty_io but no exec structure. + * + * Sets: + * + * * vio->err_depth = 1 if VTY_TERMINAL + * = 0 otherwise * - * Allocates and initialises basic vty and vty_io structures, setting the - * given type. + * So on everthing except VTY_TERMINAL, any error + * will close the entire VTY. On VTY_TERMINAL, an + * error will close down to the vin_base/vout_base + * (unless error is in one of those). * - * Note that where is not setting up a vty_sock, this *may* be called from - * any thread. + * * vio->blocking = true if VTY_CONFIG_READ + * = false otherwise * - * NB: may not create a VTY_CONFIG_WRITE type vty directly + * Caller must complete the initialisation of the vty_io, which means: * - * see: vty_open_config_write() and vty_close_config_write() + * * constructing a suitable vio_vf and doing uty_vin_push() to set the + * vin_base. * - * NB: the sock_fd *must* be valid for VTY_TERM and VTY_SHELL_SERV. - * (So MUST be in the CLI thread to set those up !) + * All vty_io MUST have a vin_base, even if it is /dev/null. * - * the sock_fd is ignored for everything else. + * * constructing a suitable vio_vf and doing uty_vout_push() to set the + * vout_base and the vio->obuf. * - * Returns: new vty + * All vty_io MUST have a vout_base, even if it is /dev/null. + * + * * setting vio->cli, if required + * + * * etc. + * + * "exec" is allocated only when the command loop is entered. */ -extern struct vty * -uty_new(enum vty_type type, int sock_fd) +extern vty +uty_new(vty_type_t type, node_type_t node) { - struct vty *vty ; - struct vty_io* vio ; + vty vty ; + vty_io vio ; VTY_ASSERT_LOCKED() ; - /* If this is a VTY_TERM or a VTY_SHELL, place */ - switch (type) - { - case VTY_TERM: /* Require fd -- Telnet session */ - case VTY_SHELL_SERV: /* Require fd -- Unix socket */ - assert(sock_fd >= 0) ; - break ; - - case VTY_CONFIG_WRITE: - zabort("may not make a new VTY_CONFIG_WRITE VTY") ; - break ; - - case VTY_CONFIG_READ: - case VTY_STDOUT: - case VTY_STDERR: - case VTY_SHELL: - sock_fd = -1 ; /* No fd -- output to stdout/stderr */ - break ; - - default: - zabort("unknown VTY type") ; - } ; - /* Basic allocation */ - vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); - vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; - vty->vio = vio ; - vio->vty = vty ; - - /* Zeroising the vty_io structure has set: - * - * name = NULL -- no name, yet - * - * vio_list both pointers NULL - * mon_list both pointers NULL - * - * half_closed = 0 -- NOT half closed (important !) - * closed = 0 -- NOT closed (important !) - * close_reason = NULL -- no reason, yet - * - * real_type = 0 -- not material - * file_fd = 0 -- not material - * file_error = 0 -- not material - * - * key_stream = NULL -- no key stream (always empty, at EOF) + /* Zeroising the vty structure will set: * - * cli_drawn = 0 -- not drawn - * cli_dirty = 0 -- not dirty - * cli_prompt_len = 0 ) - * cli_extra_len = 0 ) not material - * cli_echo_suppress = 0 ) + * type = X -- set to actual type, below * - * cli_prompt_node = 0 -- not material - * cli_prompt_set = 0 -- so prompt needs to be constructed + * node = X -- set to actual node, below * - * cli_blocked = 0 -- not blocked - * cmd_in_progress = 0 -- no command in progress - * cmd_out_enabled = 0 -- command output is disabled - * cli_wait_more = 0 -- not waiting for response to "--more--" + * index = NULL -- nothing, yet + * index_sub = NULL -- nothing, yet * - * cli_more_enabled = 0 -- not enabled for "--more--" + * config = false -- not owner of configuration symbol of power + * config_brand = 0 -- none, yet * - * cmd_out_done = 0 -- not material + * exec = NULL -- execution state set up when required + * vio = X -- set below + */ + vty = XCALLOC(MTYPE_VTY, sizeof(struct vty)) ; + + vty->type = type ; + vty->node = node ; + + /* Zeroising the vty_io structure will set: * - * cli_do = 0 == cli_do_nothing + * vty = X -- set to point to parent vty, below + * vio_list = NULLs -- not on the vio_list, yet * - * cmd_lc = NULL -- no line control + * vin = NULL -- empty input stack + * vin_base = NULL -- empty input stack + * vin_depth = 0 -- no stacked vin's, yet + * vin_true_depth = 0 -- ditto * - * fail = 0 -- no login failures yet + * vout = NULL -- empty output stack + * vout_base = NULL -- empty output stack + * vout_depth = 0 -- no stacked vout's, yet * - * hist = empty vector - * hp = 0 -- at the beginning - * hindex = 0 -- the beginning + * ebuf = NULL -- no error at all, yet + * err_depth = 0 -- see below: zero unless VTY_TERMINAL * - * width = 0 -- unknown console width - * height = 0 -- unknown console height + * blocking = false -- set below: false unless VTY_CONFIG_READ + * state = vc_stopped -- not started vty command loop + * signal = CMD_SUCCESS -- OK (null signal) + * close_reason = NULL -- none set * - * lines = 0 -- no limit - * lines_set = 0 -- no explicit setting + * ps_buf = NULL -- no pipe stderr return buffer, yet * - * monitor = 0 -- not a monitor - * monitor_busy = 0 -- not a busy monitor + * obuf = NULL -- no output buffer, yet * - * config = 0 -- not holder of "config" mode + * mon_list = NULLs -- not on the monitors list + * monitor = false -- not a monitor + * mon_kick = false + * maxlvl = 0 + * mbuf = NULL */ - confirm(cli_do_nothing == 0) ; - confirm(AUTH_NODE == 0) ; /* default node type */ + vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ; - vio->type = type ; + confirm(vc_stopped == 0) ; + confirm(CMD_SUCCESS == 0) ; - /* Zeroising the vty structure has set: - * - * node = 0 TODO: something better for node value ???? - * buf = NULL -- no command line, yet - * parsed = NULL -- no parsed command, yet - * lineno = 0 -- nothing read, yet - * index = NULL -- nothing, yet - * index_sub = NULL -- nothing, yet - */ - if (type == VTY_TERM) - vty->newline = "\n" ; /* line control looks after "\r\n" */ - else - vty->newline = "\n" ; - - /* Initialise the vio_sock, */ - uty_sock_init_new(&vio->sock, sock_fd, vio) ; - - /* Make sure all buffers etc. are initialised clean and empty. - * - * Note that no buffers are actually allocated at this stage. - */ - qs_init_new(&vio->cli_prompt_for_node, 0) ; - - qs_init_new(&vio->cl, 0) ; - qs_init_new(&vio->clx, 0) ; + vty->vio = vio ; + vio->vty = vty ; - vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ + vio->err_depth = (type == VTY_TERMINAL) ? 1 : 0 ; - vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; + vio->blocking = (type == VTY_CONFIG_READ) ; /* Place on list of known vio/vty */ - sdl_push(vio_list_base, vio, vio_list) ; - - return vty; -} ; - -/*------------------------------------------------------------------------------ - * Create new vty of type VTY_TERM -- ie attached to a telnet session. - * - * Returns: new vty - */ -static struct vty * -uty_new_term(int sock_fd, union sockunion *su) -{ - struct vty *vty ; - vty_io vio ; - enum vty_readiness ready ; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_TERM, sock_fd) ; - vio = vty->vio ; - - /* Allocate and initialise a keystroke stream TODO: CSI ?? */ - vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; - - /* Set the socket action functions */ - if (vty_cli_nexus) - { - vio->sock.action.read.qnexus = vty_read_qnexus ; - vio->sock.action.write.qnexus = vty_write_qnexus ; - vio->sock.action.timer.qnexus = vty_timer_qnexus ; - } - else - { - vio->sock.action.read.thread = vty_read_thread ; - vio->sock.action.write.thread = vty_write_thread ; - vio->sock.action.timer.thread = vty_timer_thread ; - } ; - - /* The text form of the address identifies the VTY */ - vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); - - /* Set the initial node */ - if (no_password_check) - { - if (restricted_mode) - vty->node = RESTRICTED_NODE; - else if (host.advanced) - vty->node = ENABLE_NODE; - else - vty->node = VIEW_NODE; - } - else - vty->node = AUTH_NODE; - - /* Pick up current timeout setting */ - vio->sock.v_timeout = vty_timeout_val; - - /* Use global 'lines' setting, as default. May be -1 => unset */ - vio->lines = host.lines ; - - /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ - vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; - uty_set_height(vio) ; /* set initial state */ - - /* Initialise the CLI, ready for start-up messages etc. */ - uty_cli_init(vio) ; - - /* Reject connection if password isn't set, and not "no password" */ - if ((host.password == NULL) && (host.password_encrypt == NULL) - && ! no_password_check) - { - uty_half_close (vio, "Vty password is not set."); - vty = NULL; - } - else - { - /* Say hello to the world. */ - vty_hello (vty); - - if (! no_password_check) - uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE); - } ; - - /* Now start the CLI and set a suitable state of readiness */ - ready = uty_cli_start(vio) ; - uty_sock_set_readiness(&vio->sock, ready) ; + sdl_push(vio_live_list, vio, vio_list) ; return vty; } ; /*------------------------------------------------------------------------------ - * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. + * Set timeout value. * - * Returns: new vty + * This is only ever called when a command (eg exec-timeout) sets a new + * time out value -- which applies only to VIN_TERM and VTY_VTYSH. */ -static struct vty * -uty_new_shell_serv(int sock_fd) +extern void +uty_set_timeout(vty_io vio, vty_timer_time timeout) { - struct vty *vty ; - vty_io vio ; + vio_in_type_t vt ; VTY_ASSERT_LOCKED() ; - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_SHELL_SERV, sock_fd) ; - vio = vty->vio ; - - /* Set the action functions */ - if (vty_cli_nexus) - { - vio->sock.action.read.qnexus = vtysh_read_qnexus ; - vio->sock.action.write.qnexus = vty_write_qnexus ; - vio->sock.action.timer.qnexus = NULL ; - } - else - { - vio->sock.action.read.thread = vtysh_read_thread ; - vio->sock.action.write.thread = vty_write_thread ; - vio->sock.action.timer.thread = NULL ; - } ; - - vty->node = VIEW_NODE; - - /* Kick start the CLI etc. */ - uty_sock_set_readiness(&vio->sock, write_ready) ; - - return vty; -} ; - -/*------------------------------------------------------------------------------ - * Set/Clear "monitor" state: - * - * set: if VTY_TERM and not already "monitor" (and write_open !) - * clear: if is "monitor" - */ -extern void -uty_set_monitor(vty_io vio, bool on) -{ - VTY_ASSERT_LOCKED() ; + vt = vio->vin_base->vin_type ; - if (on && !vio->monitor) - { - if ((vio->type == VTY_TERM) && vio->sock.write_open) - { - vio->monitor = 1 ; - sdl_push(vio_monitors_base, vio, mon_list) ; - } ; - } - else if (!on && vio->monitor) - { - vio->monitor = 0 ; - sdl_del(vio_monitors_base, vio, mon_list) ; - } + if ((vt == VIN_TERM) || (vt == VIN_VTYSH)) + uty_vf_set_read_timeout(vio->vin_base, timeout) ; } ; /*------------------------------------------------------------------------------ - * Return "name" of VTY + * Return "name" of VTY. * - * For VTY_TERM this is the IP address of the far end of the telnet connection. + * The name of the base vin, or (failing that) the base vout. */ extern const char* uty_get_name(vty_io vio) { - return (vio->name != NULL) ? vio->name : "?" ; + const char* name ; + + name = vio->vin_base->name ; + if (name == NULL) + name = vio->vout_base->name ; + + return (name != NULL) ? name : "?" ; } ; /*------------------------------------------------------------------------------ - * Closing down VTY for reading. + * Close VTY -- final. * - * For VTY_TERM (must be in CLI thread): + * Turns off any log monitoring immediately. * - * * shut the socket for reading - * * discard all buffered input, setting it to "EOF" - * * turns off any monitor status ! - * * drop down to RESTRICTED_NODE + * Close "final" means: terminate any input immediately, but attempt to flush + * any pending output (and any pipe return) but give up if would block or gets + * any error. * - * For VTY_SHELL_SERV (must be in CLI thread): + * To call this the command loop must be vc_stopped. When a command loop exits + * normally, it completes all pending I/O and closes the stacks down to, but + * not including vout_base. So there is not much to do here. When a vty is + * reset, once the command loop has been stopped, this function will close + * everything. * - * * shut the socket for reading - * * discard all buffered input - * * drop down to RESTRICTED_NODE + * Once the vio stacks are all closed, except for the vout_base, makes some + * effort to issue any "close reason" message, then closes the vout_base. * - * In all cases: + * The vty can then be closed. * - * * place on death watch - * * set the vty half_closed - * * sets the reason for closing (if any given) + * NB: releases the vty, the vio, the exec and all related objects. * - * For VTY_TERM and VTY_SHELL_SERV, when the output side has emptied out all - * the buffers, the VTY is closed. - * - * May already have set the vio->close_reason, or can set it now. (Passing a - * NULL reason has no effect on any existing posted reason.) + * On return DO NOT attempt to do anything with the one-time VTY. */ extern void -uty_half_close (vty_io vio, const char* reason) +uty_close(vty_io vio) { - VTY_ASSERT_LOCKED() ; + vty vty = vio->vty ; - if (vio->half_closed) - return ; + VTY_ASSERT_CAN_CLOSE(vio->vty) ; - if (reason != NULL) - vio->close_reason = reason ; + qassert(vio->state == vc_stopped) ; - /* Do the file side of things - * - * Note that half closing the file sets a new timeout, sets read off - * and write on. + /* The vio is about to be closed, so take off the live list and make + * sure that is not a log monitor. */ - uty_sock_half_close(&vio->sock) ; - uty_set_monitor(vio, 0) ; + uty_set_monitor(vio, off) ; + sdl_del(vio_live_list, vio, vio_list) ; - /* Discard everything in the keystroke stream and force it to EOF */ - if (vio->key_stream != NULL) - keystroke_stream_set_eof(vio->key_stream) ; - - /* Turn off "--more--" so that all output clears without interruption. + /* Close all vin including the vin_base. * - * If is sitting on a "--more--" prompt, then exit the wait_more CLI. + * Note that the vin_base is closed, but is still on the vin stack. */ - vio->cli_more_enabled = 0 ; - - if (vio->cli_more_wait) - uty_cli_exit_more_wait(vio) ; + do + uty_vin_pop(vio, NULL, true) ; /* final close, discard context */ + while (vio->vin != vio->vin_base) ; - /* If a command is not in progress, enable output, which will clear - * the output buffer if there is anything there, plus any close reason, - * and then close. - * - * If command is in progress, then this process will start when it - * completes. + /* Close all the vout excluding the vout_base. */ - if (!vio->cmd_in_progress) - vio->cmd_out_enabled = 1 ; + while (vio->vout != vio->vout_base) + uty_vout_pop(vio, true) ; /* final close */ - /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, RESTRICTED_NODE) ; + /* If the vout_base is not closed, try to output the close reason, + * if any -- note that this will attempt to output, even if some + * earlier output has failed. + */ + uty_vout_close_reason(vio->vout_base, vio->close_reason) ; - /* Log closing of VTY_TERM */ - if (vio->type == VTY_TERM) - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; + /* Now final close the vout_base. + */ + uty_vout_pop(vio, true) ; /* final close */ - /* Move to the death watch list */ - sdl_del(vio_list_base, vio, vio_list) ; - sdl_push(vio_death_watch, vio, vio_list) ; + /* All should now be very quiet indeed. */ + if (vty_debug) + { + assert(vio->vin == vio->vin_base) ; + assert(vio->vin_depth == 0) ; + assert(vio->vin_true_depth == 0) ; - vio->half_closed = 1 ; -} ; + assert(vio->vout == vio->vout_base) ; + assert(vio->vout_depth == 0) ; -/*------------------------------------------------------------------------------ - * Closing down VTY. - * - * Shuts down everything and discards all buffers etc. etc. - * - * If cmd_in_progress, cannot complete the process -- but sets the closed - * flag. - * - * Can call vty_close() any number of times. - * - * The vty structure is placed on death watch, which will finally free the - * structure once no longer cmd_in_progress. - */ -extern void -uty_close (vty_io vio) -{ - VTY_ASSERT_LOCKED() ; + assert(vio->vin->vin_state == vf_closed) ; + assert(vio->vout->vout_state == vf_closed) ; - /* Empty all the output buffers */ - vio_fifo_reset_keep(&vio->cli_obuf) ; - vio_fifo_reset_keep(&vio->cmd_obuf) ; - vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ; - - /* If not already closed, close. */ - if (!vio->closed) - { - uty_half_close(vio, NULL) ; /* place on death watch -- if not - already done */ - if (vio->type == VTY_TERM) - uty_cli_close(vio) ; /* tell the CLI to stop */ + assert(vty->vio == vio) ; + } ; - vio->closed = 1 ; /* now closed (stop uty_write() - from recursing) */ + /* Release what remains of the vio. */ + vio->vty = NULL ; /* no longer required. */ - if (vio->sock.write_open) - uty_write(vio) ; /* last gasp attempt */ + vio->ebuf = vio_fifo_free(vio->ebuf) ; + vio->obuf = NULL ; /* about to discard the vout_base */ + vio->ps_buf = vio_fifo_free(vio->ps_buf) ; - uty_sock_close(&vio->sock) ; + XFREE(MTYPE_TMP, vio->close_reason) ; - } ; + if (vio->vin != vio->vout) + vio->vin = uty_vf_free(vio->vin) ; + else + vio->vin = NULL ; - /* Nothing more should happen, so can now release almost everything, - * the exceptions being the things that are related to a cmd_in_progress. - * - * All writing to buffers is suppressed, and as the sock has been closed, - * there will be no more read_ready or write_ready events. - */ - if (vio->name != NULL) - XFREE(MTYPE_VTY_NAME, vio->name) ; + vio->vout = uty_vf_free(vio->vout) ; - vio->key_stream = keystroke_stream_free(vio->key_stream) ; + vio->vin_base = NULL ; + vio->vout_base = NULL ; - qs_free_body(&vio->cli_prompt_for_node) ; - qs_free_body(&vio->cl) ; + assert(!vio->monitor) ; + vio->mbuf = vio_fifo_free(vio->mbuf) ; - { - qstring line ; - while ((line = vector_ream_keep(&vio->hist)) != NULL) - qs_reset_free(line) ; - } ; + /* Release the vio and the exec (if any) */ + XFREE(MTYPE_VTY, vty->vio) ; + vty->exec = cmd_exec_free(vty->exec) ; - /* The final stage cannot be completed if cmd_in_progress. - * - * The clx is pointed at by vty->buf -- containing the current command. - * - * Once everything is released, can take the vty off death watch, and - * release the vio and the vty. - */ - if (!vio->cmd_in_progress) - { - qs_free_body(&vio->clx) ; - vio->vty->buf = NULL ; - } ; + /* Finally, release the VTY itself. */ + XFREE(MTYPE_VTY, vty) ; } ; /*============================================================================== - * For writing configuration file by command, temporarily redirect output to - * an actual file. - */ - -/*------------------------------------------------------------------------------ - * Set the given fd as the VTY_FILE output. + * Pushing and popping vf objects on the vio->vin_stack and vio->vout_stack. */ -extern void -vty_open_config_write(struct vty* vty, int fd) -{ - vty_io vio ; - - VTY_LOCK() ; - - vio = vty->vio ; - - assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ; - - vio->real_type = vio->type ; - - vio->type = VTY_CONFIG_WRITE ; - vio->file_fd = fd ; - vio->file_error = 0 ; - - VTY_UNLOCK() ; -} ; /*------------------------------------------------------------------------------ - * Write away configuration file stuff -- all or just the full lump(s). + * Add a new vf to the vio->vin stack, and set read stuff. + * + * Sets the given vf->vin_type and set vf->vin_state = vf_open. + * + * Initialises an input buffer if required, and sets line_complete and + * line_step so that first attempt to fetch a line will give line 1. * - * Returns: > 0 => blocked - * 0 => all gone (up to last lump if !all) - * < 0 => failed -- see vio->file_error + * Sets the read ready action and the read timer timeout action, if required. + * If the vf is "blocking", then these actions are not required. Also, + * if the vin_type is one of the "specials", then these actions are not + * required -- noting that the vin_type may be "special" while the vout_type + * is a non-blocking ordinary output. + * + * NB: is usually called from the cli thread, but may be called from the cmd + * thread for vf which is blocking, or for a "special" vin_type ! + * + * NB: a uty_cmd_prepare() is required before command processing can continue. + * + * NB: see uty_vout_push() for notes on pushing a vin and a vout together. */ -static int -uty_config_write(vty_io vio, bool all) +extern void +uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type, + vio_vfd_action* read_action, + vio_timer_action* read_timer_action, + usize ibuf_size) { - int ret ; + vf->vin_type = type ; + vf->vin_state = vf_open ; - VTY_ASSERT_LOCKED() ; + qassert(type != VIN_NONE) ; - if (vio->file_error == 0) + if ((!vf->blocking) && (vf->vin_type < VIN_SPECIALS)) { - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; - - if (ret < 0) - vio->file_error = errno ; - } - else - ret = -1 ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Write away any pending stuff, and return the VTY to normal. - */ -extern int -vty_close_config_write(struct vty* vty) -{ - vty_io vio ; - int err ; - - VTY_LOCK() ; - - vio = vty->vio ; - - assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ; - - uty_config_write(vio, true) ; /* write all that is left */ - - err = vio->file_error ; + vio_vfd_set_read_action(vf->vfd, read_action) ; + vio_vfd_set_read_timeout_action(vf->vfd, read_timer_action) ; + } ; - vio->type = vio->real_type ; - vio->file_fd = -1 ; - vio->file_error = 0 ; + ssl_push(vio->vin, vf, vin_next) ; + vio->vin_true_depth = ++vio->vin_depth ; - VTY_UNLOCK() ; + if (vio->vin_base == NULL) + { + qassert(vio->vin_depth == 1) ; + vio->vin_base = vf ; + } ; - return err ; + if (ibuf_size != 0) + { + vf->ibuf = vio_fifo_new(ibuf_size) ; + vf->cl = qs_new(150) ; + vf->line_complete = true ; + vf->line_step = 1 ; + } ; } ; -/*============================================================================== - * vio_sock level operations - */ - /*------------------------------------------------------------------------------ - * Initialise a new vio_sock structure. + * Save the given context in the current top of the vin stack. + * + * This is done when a new pipe is opened, so that: * - * Requires that: the vio_sock structure is not currently in use. + * a) saves the current context in the current vin (the new pipe has + * not yet been pushed) so that uty_vin_pop() can restore this context + * after closing the then top of the stack. * - * if fd >= 0 then: sock is open and ready read and write - * otherwise: sock is not open + * b) can update context for about to be run vin, eg: * - * there are no errors, yet. + * - dir_here -- if required * - * Sets timeout to no timeout at all -- timeout is optional. + * - can_enable * - * NB: MUST be in the CLI thread if the fd is >= 0 ! + * So the top of the vin stack does not contain the current context, that is + * in the vty->exec ! */ -static void -uty_sock_init_new(vio_sock sock, int fd, void* info) +extern void +uty_vin_new_context(vty_io vio, cmd_context context, qpath file_here) { - VTY_ASSERT_LOCKED() ; - - if (fd >= 0) - VTY_ASSERT_CLI_THREAD() ; - - memset(sock, 0, sizeof(struct vio_sock)) ; - - /* Zeroising the structure has set: - * - * action = all the actions set NULL - * - * error_seen = 0 -- no error, yet - * - * qf = NULL -- no qfile, yet - * t_read = NULL ) no threads, yet - * t_write = NULL ) - * - * v_timeout = 0 -- no timeout set - * timer_runing = 0 -- not running, yet - * t_timer = NULL -- no timer thread, yet - * qtr = NULL -- no qtimer, yet - */ - sock->fd = fd ; - sock->info = info ; - - sock->read_open = (fd >= 0) ; - sock->write_open = (fd >= 0) ; - - if ((fd >= 0) && vty_cli_nexus) - { - sock->qf = qps_file_init_new(NULL, NULL); - qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info); - } ; + qassert(vio->vin->context == NULL) ; + vio->vin->context = cmd_context_new_save(context, file_here) ; } ; /*------------------------------------------------------------------------------ - * Restart the timer. + * Push a new vf to the vio->vout stack, and set write stuff. + * + * Sets the vf->vout_type and set vf->vout_state = vf_open. + * + * Sets the write ready action and the write timer timeout action, if required. + * If the vf is "blocking", then these actions are not required. Also, + * if the vout_type is one of the "specials", then these actions are not + * required -- noting that the vout_type may be "special" while the vin_type + * is a non-blocking ordinary input. + * + * Initialises an output buffer and sets an end_mark. + * + * The new vout_depth depends on whether a vin and vout are being opened + * together, and if so, in what order they are pushed: + * + * * for a vout being opened on its own (e.g. VOUT_CONFIG_WRITE) the + * vout_depth is set to the current vin_depth + 1. This means that + * when the current command completes, vout_depth > vin_depth, so the + * vout will automatically be closed. + * + * * for a vout being opened at the same time as a vin, the vout_depth + * is set to the same as the *new* vin_depth. This means that the + * vout will not be closed until the vin is. + * + * If the vout is opened before the vin, the new vout_depth will be + * vin_depth + 1. + * + * If the vout is opened after the vin, the new vout_depth will be + * vin_depth. + * + * ...hence the "after" argument. + * + * NB: is usually called from the cli thread, but may be called from the cmd + * thread for vf which is blocking, or for a "special" vout_type ! * - * If a timeout time is set, then start or restart the timer with that value. + * NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special. * - * If no timeout time is set, and the timer is running, unset it. + * The write_action and the write_timer_action are ignored. + * + * All actual I/O to these outputs is direct, blocking and via standard + * I/O -- except VOUT_DEV_NULL where all I/O is discarded. + * + * NB: all outputs are set up with an obuf, so all output is collected, even + * if it is later to be discarded. + * + * NB: a uty_cmd_prepare() is required before command processing can continue. */ -static void -uty_sock_restart_timer(vio_sock sock) +extern void +uty_vout_push(vty_io vio, vio_vf vf, vio_out_type_t type, + vio_vfd_action* write_action, + vio_timer_action* write_timer_action, + usize obuf_size, + bool after) { VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - if (sock->v_timeout != 0) - { - assert(sock->action.timer.anon != NULL) ; + vf->vout_type = type ; + vf->vout_state = vf_open ; - if (vty_cli_nexus) - { - if (sock->qtr == NULL) /* allocate qtr if required */ - sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, - NULL, sock->info) ; - qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)), - sock->action.timer.qnexus) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer); - sock->t_timer = thread_add_timer (vty_master, - sock->action.timer.thread, sock->info, sock->v_timeout) ; - } ; + qassert(type != VOUT_NONE) ; - sock->timer_running = 1 ; - } - else if (sock->timer_running) + if ((!vf->blocking) && (vf->vout_type < VOUT_SPECIALS)) { - if (vty_cli_nexus) - { - if (sock->qtr != NULL) - qtimer_unset(sock->qtr) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer) ; - } ; + vio_vfd_set_write_action(vf->vfd, write_action) ; + vio_vfd_set_write_timeout_action(vf->vfd, write_timer_action) ; + } ; - sock->timer_running = 0 ; + ssl_push(vio->vout, vf, vout_next) ; + if (vio->vout_base == NULL) + { + qassert(vio->vout_depth == 0) ; + vio->vout_base = vf ; } ; -} ; -/*------------------------------------------------------------------------------ - * Set read on/off - * - * Returns: the on/off state set - */ -static bool -uty_sock_set_read(vio_sock sock, bool on) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + vf->obuf = vio_fifo_new(obuf_size) ; + vio_fifo_set_end_mark(vf->obuf) ; - if (sock->fd < 0) - return 0 ; + vf->depth_mark = vio->vout_depth ; /* remember *old* depth */ - if (on) + if (after) { - assert(sock->action.read.anon != NULL) ; - - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ; - else - { - if (sock->t_read != NULL) - thread_cancel(sock->t_read) ; - - sock->t_read = thread_add_read(vty_master, - sock->action.read.thread, sock->info, sock->fd) ; - } ; + qassert(vio->vout_depth < vio->vin_depth) ; + vio->vout_depth = vio->vin_depth ; /* set new depth */ } else { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_read_mbit) ; - else - { - if (sock->t_read != NULL) - thread_cancel (sock->t_read) ; - } ; + qassert(vio->vout_depth <= vio->vin_depth) ; + vio->vout_depth = vio->vin_depth + 1 ; /* set new depth */ } ; - return on ; + vio->obuf = vf->obuf ; } ; /*------------------------------------------------------------------------------ - * Set write on/off + * Close top of the vin stack and pop when done -- see uty_vf_read_close(). + * + * If succeeds in closing, unless is vin_base, pops the vin stack and if this + * is read-only will free the vio_vf and all its contents. (So if this is + * vin_base, it is left on the stack, but vf_closed/vf_closing.) + * + * If the given context is not NULL, having popped the vin stack, the context + * in the new top of stack is restored to the given context. + * + * On final close, will not wait for I/O, will completely close the input, + * even if errors occur (and no errors are posted) and will return CMD_SUCCESS. + * + * Returns: CMD_SUCCESS -- input completely closed and popped + * CMD_WAITING -- waiting for input to close <=> non-blocking + * <=> not "final" + * CMD_IO_ERROR -- error or timeout <=> try again + * <=> not "final" * - * Returns: the on/off state set + * NB: a uty_cmd_prepare() is required before command processing can continue. */ -static bool -uty_sock_set_write(vio_sock sock, bool on) +extern cmd_return_code_t +uty_vin_pop(vty_io vio, cmd_context context, bool final) { + cmd_return_code_t ret ; + VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - if (sock->fd < 0) - return 0 ; + ret = uty_vf_read_close(vio->vin, final) ; - if (on) + if ((ret == CMD_SUCCESS) || final) { - assert(sock->action.write.anon != NULL) ; + assert(vio->vin->vin_state == vf_closed) ; - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; - else + if (vio->vin_depth > 1) { - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; + vio_vf vf ; - sock->t_write = thread_add_write(vty_master, - sock->action.write.thread, sock->info, sock->fd) ; - } ; - } - else - { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_write_mbit) ; + vf = ssl_pop(&vf, vio->vin, vin_next) ; + --vio->vin_depth ; + + if (vf->vout_state == vf_closed) + uty_vf_free(vf) ; + } else { - if (sock->t_write != NULL) - thread_cancel (sock->t_write) ; + assert(vio->vin == vio->vin_base) ; + vio->vin_depth = 0 ; /* may already have been closed */ + } ; + + if (vio->vin_true_depth > vio->vin_depth) + vio->vin_true_depth = vio->vin_depth ; + + if (vio->vin->context != NULL) + { + if (context != NULL) + vio->vin->context = cmd_context_restore(context, vio->vin->context); + else + vio->vin->context = cmd_context_free(vio->vin->context, false) ; + /* Not a copy */ } ; } ; - return on ; + return ret ; } ; /*------------------------------------------------------------------------------ - * Set read/write readiness -- for VTY_TERM + * Close top of the vout stack and pop when done -- see uty_vf_write_close(). + * + * If is vout_base, does not completely close, unless is "final" -- if not + * final, CMD_SUCCESS means that the output buffers are empty and the + * vout_depth has been reduced, but the vio_vf is still writeable, held in + * vf_closing state. + * + * Unless is vout_base, pops the vout stack. If this is write-only will free + * the vio_vf and all its contents. + * + * If this is vout_base, does not actually close the vfd and does not close + * the vout side of the vf. This leaves an active vio->obuf (inter alia.) + * + * Before closing, discard anything after end_mark, then push any outstanding + * output. + * + * Unless "final", the close is soft, that is, if there is any output still + * outstanding does not close the vout. + * + * If there is no outstanding output (or if final) will completely close the + * vf and free it (except for vout_base). * - * Note that for VTY_TERM, set only one of read or write, and sets write for - * preference. + * Returns: CMD_SUCCESS -- output completely closed and popped + * CMD_WAITING -- waiting for input to close <=> non-blocking + * <=> not "final" + * CMD_IO_ERROR -- error or timeout <=> try again + * <=> not "final" + * + * NB: a uty_cmd_prepare() is required before command processing can continue. + * + * That is not done here because this may be called from outside the + * command loop -- in particular by uty_close(). + * + * However, ensures that vio->obuf is up to date ! */ -extern void -uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) +extern cmd_return_code_t +uty_vout_pop(vty_io vio, bool final) { + cmd_return_code_t ret ; + VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - uty_sock_set_read(sock, (ready == read_ready)) ; - uty_sock_set_write(sock, (ready >= write_ready)) ; -} ; + ret = uty_vf_write_close(vio->vout, final) ; -/*------------------------------------------------------------------------------ - * Set a new timer value. - */ -extern void -uty_sock_set_timer(vio_sock sock, unsigned long timeout) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + if (ret != CMD_SUCCESS) + return ret ; - sock->v_timeout = timeout ; - if (sock->timer_running) - uty_sock_restart_timer(sock) ; -} ; + if (vio->vout_depth > 1) + { + vio_vf vf ; -/*------------------------------------------------------------------------------ - * Close given vty sock for reading. - * - * Sets timer to timeout for clearing any pending output. - * - * NB: if there is a socket, MUST be in the CLI thread - */ -static void -uty_sock_half_close(vio_sock sock) -{ - VTY_ASSERT_LOCKED() ; + vf = ssl_pop(&vf, vio->vout, vout_next) ; - sock->read_open = 0 ; /* make sure */ + qassert(vf->depth_mark < vio->vout_depth) ; + vio->vout_depth = vf->depth_mark ; - if (sock->fd < 0) - return ; /* nothing more if no socket */ + uty_vf_free(vf) ; + } + else + { + assert(vio->vout == vio->vout_base) ; + if (final) + assert(vio->vout->vout_state == vf_closed) ; - VTY_ASSERT_CLI_THREAD() ; + qassert(vio->vout->depth_mark == 0) ; + vio->vout_depth = 0 ; /* may already be */ + } ; - shutdown(sock->fd, SHUT_RD) ; /* actual half close */ + vio->obuf = vio->vout->obuf ; - uty_sock_set_read(sock, off) ; - uty_sock_set_write(sock, on) ; - sock->v_timeout = 30 ; /* for output to clear */ - uty_sock_restart_timer(sock) ; + return ret ; } ; /*------------------------------------------------------------------------------ - * Close given vio_sock, completely -- shut down any timer. + * Try to output the close reason to the vout_base. * - * Structure is cleared of everything except the last error ! + * This is the final act for the vout_base. Will attempt to output unless + * the thing is completely closed. * - * NB: if there is a socket, MUST be in the CLI thread + * Should by now not be any buffered output -- but if there is, that is + * discarded before the close reason is output. + * + * Any actual output may be done when the vout_base is finally closed. */ static void -uty_sock_close(vio_sock sock) +uty_vout_close_reason(vio_vf vf, const char* reason) { - VTY_ASSERT_LOCKED() ; - - sock->read_open = 0 ; /* make sure */ - sock->write_open = 0 ; - - if (sock->fd < 0) - { - assert( (sock->qf == NULL) - && (sock->qtr == NULL) - && (sock->t_read == NULL) - && (sock->t_write == NULL) - && (sock->t_timer == NULL) ) ; - return ; /* no more to be done here */ - } ; - - VTY_ASSERT_CLI_THREAD() ; - close(sock->fd) ; + if ((vf->vout_state == vf_closed) || (reason == NULL) || (*reason == '\0')) + return ; - if (vty_cli_nexus) - { - assert((sock->qf != NULL) && (sock->fd == qps_file_fd(sock->qf))) ; - qps_remove_file(sock->qf) ; - qps_file_free(sock->qf) ; - sock->qf = NULL ; - } ; + vio_fifo_clear(vf->obuf, true) ; /* clear any markers, too */ - sock->fd = -1 ; + if (vf->vout_type < VOUT_SPECIALS) + assert(vf->vfd != NULL) ; /* make sure */ - if (sock->t_read != NULL) - thread_cancel(sock->t_read) ; - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; + switch(vf->vout_type) + { + case VOUT_NONE: + zabort("invalid VOUT_NONE") ; + break ; - sock->t_read = NULL ; - sock->t_write = NULL ; + case VOUT_TERM: + uty_term_close_reason(vf, reason) ; + break ; - sock->info = NULL ; - sock->action.read.anon = NULL ; - sock->action.write.anon = NULL ; - sock->action.timer.anon = NULL ; + case VOUT_VTYSH: + break ; - if (sock->qtr != NULL) - qtimer_free(sock->qtr) ; - if (sock->t_timer != NULL) - thread_cancel(sock->t_timer) ; + case VOUT_FILE: + case VOUT_PIPE: + case VOUT_SH_CMD: + break ; - sock->v_timeout = 0 ; - sock->qtr = NULL ; - sock->t_timer = NULL ; -} ; + case VOUT_CONFIG: + break ; -/*------------------------------------------------------------------------------ - * Dealing with an I/O error on VTY socket - * - * If this is the first error for this VTY, produce suitable log message. - * - * If is a "monitor", turn that off, *before* issuing log message. - */ -static int -uty_sock_error(vty_io vio, const char* what) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + case VOUT_DEV_NULL: + break ; - /* can no longer be a monitor ! *before* any logging ! */ - uty_set_monitor(vio, 0) ; + case VOUT_STDOUT: + fprintf(stdout, "%% %s\n", reason) ; + break ; - /* if this is the first error, log it */ - if (vio->sock.error_seen == 0) - { - const char* type ; - switch (vio->type) - { - case VTY_TERM: - type = "VTY Terminal" ; - break ; - case VTY_SHELL_SERV: - type = "VTY Shell Server" ; - break ; - default: - zabort("unknown VTY type for uty_sock_error()") ; - } ; - - vio->sock.error_seen = errno ; - uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", - type, what, vio->sock.fd, errtoa(vio->sock.error_seen, 0).str) ; - } ; + case VOUT_STDERR: + fprintf(stderr, "%% %s\n", reason) ; + break ; - return -1 ; + default: + zabort("unknown VOUT type") ; + } ; } ; /*============================================================================== - * Readiness and the VTY_TERM type VTY. - * - * For VTY_TERM the driving force is write ready. This is used to prompt the - * VTY_TERM when there is outstanding output (obviously), but also if there - * is buffered input in the keystroke stream. - * - * The VTY_TERM uses read ready only when it doesn't set write ready. Does - * not set both at once. - * - * So there is only one, common, uty_ready function, which: - * - * 1. attempts to clear any output it can. + * vio_vf level operations + */ + +/*------------------------------------------------------------------------------ + * Create and initialise a new vio_vf structure. * - * The state of the output affects the CLI, so must always do this before - * before invoking the CLI. + * There are no errors, yet. * - * If this write enters the "--more--" state, then will have tried to - * write away the prompt. + * This leaves most things unset/NULL/false. Caller will need to: * - * 2. invokes the CLI + * - uty_vin_push() and/or uty_vout_push() * - * Which will do either the standard CLI stuff or the special "--more--" - * stuff. + * - once those are done, the following optional items remain to be set + * if they are required: * - * 3. attempts to write any output there now is. + * read_timeout -- default = 0 => no timeout + * write_timeout -- default = 0 => no timeout * - * If the CLI generated new output, as much as possible is written away - * now. + * - for pipes the child state needs to be set, and for out pipes the return + * state needs to be set in this vio_vf (the master) and in the next + * vout (the slave). * - * If this write enters the "--more--" state, then it returns now_ready, - * if the prompt was written away, which loops back to the CLI. + * NB: if there is no fd for this vio_vf, it should be set to -1, and the + * type (recommend vfd_none) and io_type are ignored. * - * Note that this is arranging: + * A VTY_STDOUT or a VTY_STDERR (output only, to the standard I/O) can + * be set up without an fd. * - * a. to write away the "--more--" prompt as soon as the tranche of output to - * which it refers, completes + * A VTY_CONFIG_READ can be set up with the fd of the input file, but + * MUST be type = vfd_file and io_type = vfd_io_read -- because the fd + * is for input only. The required VOUT_STDERR will be set by + * uty_vout_push(). * - * b. to enter the cli_more_wait CLI for the first time immediately after the - * "--more--" prompt is written away. + * NB: if the parent vio is blocking, then the vf will be vfd_io_ps_blocking, + * and so will any vfd. An individual vf may be set blocking by setting + * vfd_io_blocking or vfd_io_ps_blocking in the io_type. * - * The loop limits itself to one trache of command output each time. + * For vfd_io_ps_blocking, the vfd stuff handles all files, pipes etc. + * non-blocking. The vf simulates blocking by local pselect. As far as + * the vfd level is concerned, once the file, pipe etc. is open, there is + * no difference between blocking and non-blocking except that blocking + * vfd (of either type) are not allowed to set read/write ready. * - * Resets the timer because something happened. + * NB: the name is XSTRDUP() into the vio -- so the caller is responsible for + * disposing of its copy, if required. */ -static void -uty_ready(vty_io vio) +extern vio_vf +uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, + vfd_io_type_t io_type) { - enum vty_readiness ready ; + vio_vf vf ; VTY_ASSERT_LOCKED() ; - vio->cmd_out_done = 0 ; /* not done any command output yet */ + vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ; - uty_write(vio) ; /* try to clear outstanding stuff */ - do - { - ready = uty_cli(vio) ; /* do any CLI work... */ - ready |= uty_write(vio) ; /* ...and any output that generates */ - } while (ready >= now_ready) ; + /* Zeroising the structure has set: + * + * vio = X -- set below + * name = X -- set below + * + * vin_type = VIN_NONE -- see uty_vin_push() + * vin_state = vf_closed -- see uty_vin_push() + * vin_next = NULL -- see uty_vin_push() + * + * context = NULL -- see uty_vin_new_context() + * + * cli = NULL -- no CLI, yet + * + * ibuf = NULL -- none, yet -- see uty_vin_push() + * cl = NULL -- none, yet -- see uty_vin_push() + * line_complete = false -- see uty_vin_push() + * line_number = 0 -- nothing yet + * line_step = 0 -- see uty_vin_push() + * + * vout_type = VOUT_NONE -- see uty_vout_push() + * vout_state = vf_closed -- see uty_vout_push() + * vout_next = NULL -- see uty_vout_push() + * + * obuf = NULL -- none -- see uty_vout_push() + * + * depth_mark = 0 -- see uty_vout_push() + * + * blocking = X -- see below + * + * vfd = NULL -- no vfd, yet + * + * read_timeout = 0 -- none + * write_timeout = 0 -- none + * + * child = NULL -- none -- see uty_pipe_read/write_open() + * + * pr_state = vf_closed -- no pipe return vfd + * -- see uty_pipe_read/write_open() + * pr_vfd = NULL -- no vfd -- ditto + * pr_timeout = 0 -- none -- ditto + * + * ps_state = vf_closed -- no pipe stderr return vfd + * -- see uty_pipe_read/write_open() + * ps_vfd = NULL -- no vfd -- ditto + * ps_timeout = 0 -- none -- ditto + * ps_buf = NULL -- none, yet + */ + confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ; + confirm(vf_closed == 0) ; + confirm(QSTRING_INIT_ALL_ZEROS) ; + + if (vio->blocking) + io_type |= vfd_io_ps_blocking ; /* inherit blocking state */ + + vf->vio = vio ; + vf->blocking = (io_type & (vfd_io_blocking | vfd_io_ps_blocking)) != 0 ; + + if (name != NULL) + vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ; - uty_sock_set_readiness(&vio->sock, ready) ; - uty_sock_restart_timer(&vio->sock) ; + if (fd >= 0) + vf->vfd = vio_vfd_new(fd, type, io_type, vf) ; + + return vf ; } ; -/*============================================================================== - * Reading from VTY_TERM. +/*------------------------------------------------------------------------------ + * Close the read side of the given vio_vf -- if can. * - * The select/pselect call-back ends up in uty_read_ready(). + * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully + * close it, and the vio_vfd will have been freed. * - * Note that uty_write_ready() also calls uty_read_ready, in order to kick the - * current CLI. - */ - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking CLI + * For not-final close, if there is any other related I/O, then waits until + * that has completed. For example, with a VIN_PIPE: + * + * * waits for any return input to complete, and pushes it to the relevant + * vout. + * + * * waits to collect the child, and its termination state. + * + * This means that a not-final close may return errors. For not-blocking vf + * may return waiting state. For blocking vf, may block and may later return + * timeout error. + * + * For a final close, if there is any related I/O then will attempt to complete + * it -- but will give up if would block. I/O errors on final close are + * ignored. Final close always returns CMD_SUCCESS. + * + * Returns: CMD_SUCCESS -- closed + * CMD_WAITING -- waiting to complete close <=> non-blocking + * <=> not "final" + * CMD_IO_ERROR -- error or timeout <=> try again + * <=> not "final" */ -static void -vty_read_qnexus(qps_file qf, void* file_info) +static cmd_return_code_t +uty_vf_read_close(vio_vf vf, bool final) { - vty_io vio = file_info; + cmd_return_code_t ret ; - VTY_LOCK() ; + VTY_ASSERT_CAN_CLOSE_VF(vf) ; - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + ret = CMD_SUCCESS ; - uty_ready(vio) ; + if (vf->vin_state == vf_closed) + return ret ; /* quit if already closed */ - VTY_UNLOCK() ; -} + if (vf->vin_state == vf_open) + vf->vin_state = vf_end ; /* don't try to read any more ! */ + else + qassert(vf->vin_state == vf_end) ; -/*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking CLI - */ -static int -vty_read_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); + /* Do the vfd level read close and mark the vf no longer read_open */ + if (vf->vin_type < VIN_SPECIALS) + vf->vfd = vio_vfd_read_close(vf->vfd) ; - VTY_LOCK() ; + /* Now the vin_type specific clean up. */ + switch(vf->vin_type) + { + case VIN_NONE: + zabort("invalid VIN_NONE") ; + break ; - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + case VIN_TERM: + ret = uty_term_read_close(vf, final) ; + break ; - vio->sock.t_read = NULL ; /* implicitly */ - uty_ready(vio); + case VIN_VTYSH: + zabort("tba VIN_VTYSH") ; + break ; - VTY_UNLOCK() ; - return 0 ; -} + case VIN_CONFIG: + ret = uty_config_read_close(vf, final) ; + break ; -/*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the keystroke stream - * - * Steal keystroke if required -- see keystroke_input() - * - * Returns: 0 => nothing available - * > 0 => read at least one byte - * -1 => EOF (or not open, or failed) - */ -extern int -uty_read (vty_io vio, keystroke steal) -{ - unsigned char buf[500] ; - int get ; + case VIN_FILE: + ret = uty_file_read_close(vf, final) ; + break ; - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open */ + case VIN_PIPE: + ret = uty_pipe_read_close(vf, final) ; + break ; - get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; - if (get >= 0) - keystroke_input(vio->key_stream, buf, get, steal) ; - else if (get < 0) - { - if (get == -1) - uty_sock_error(vio, "read") ; + case VIN_DEV_NULL: + ret = CMD_SUCCESS ; + break ; - vio->sock.read_open = 0 ; - keystroke_input(vio->key_stream, NULL, 0, steal) ; + default: + zabort("unknown VIN type") ; + } ; + + if ((ret == CMD_SUCCESS) || final) + { + vf->vin_state = vf_closed ; + assert(vf->pr_state == vf_closed) ; - get = -1 ; + ret = CMD_SUCCESS ; } ; - return get ; + return ret ; } ; -/*============================================================================== - * The write sock action for VTY_TERM type VTY +/*------------------------------------------------------------------------------ + * Close the write side of the given vio_vf, if can. * - * There are two sets of buffering: + * Discards anything beyond the current end_mark, and clears the end_mark. * - * cli -- command line -- which reflects the status of the command line + * Pushes any outstanding output, and if is pipe will attempt to collect + * the child. On not-final close, if cannot complete everything, will return + * CMD_WAITING for non-blocking, or block (and may time out). On final close, + * will do as much as possible without blocking, and will then close even if + * there is outstanding output or child has not been collected. * - * cmd -- command output -- which is written to the file only while - * cmd_out_enabled. + * For not-final close, for example, with a VOUT_PIPE: * - * The cli output takes precedence. + * * waits for any return input to complete, and pushes it to the relevant + * vout. * - * Output of command stuff is subject to line_control, and may go through the - * "--more--" mechanism. - */ - -static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; -static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to write -> try to empty buffers - */ -static void -vty_write_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info ; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - uty_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- thread: ready to write -> try to empty buffers - */ -static int -vty_write_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - - vio->sock.t_write = NULL; /* implicitly */ - uty_ready(vio) ; - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Write as much as possible of what there is. + * * waits to collect the child, and its termination state. * - * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, - * empty. + * This means that a not-final close may return errors. * - * Note that if !write_open, or becomes !write_open, then the FIFOs are empty - * and all output instantly successful. + * For a final close, if there is any related I/O then will attempt to complete + * it -- but will give up if would block. I/O errors on final close are + * ignored. Will close the vfd and return CMD_SUCCESS. * - * Sets write on if prevented from writing everything available for output - * by write() threatening to block. + * If this is the vout_base, unless "final", does NOT actually close the vf or + * the vfd -- so the vout_base will still work and is left vf_open. + * The effect is, essentially, to try to empty out any buffers, but not to + * do anything that would prevent further output. This is used so that a + * command loop can close the vout_base in the usual way, waiting until all + * output is flushed, but when uty_close() is finally called, it can output + * any close reason there is to hand. * - * Returns: write_ready if should now set write on - * now_ready if should loop back and try again - * not_ready otherwise + * Returns: CMD_SUCCESS -- closed + * CMD_WAITING -- waiting to complete close <=> non-blocking + * <=> not "final" + * CMD_IO_ERROR -- error or timeout <=> try again + * <=> not "final" + * + * NB: must not have open vins at this or a higher level in the stack. + * + * NB: does not at this stage discard the obuf. */ -static enum vty_readiness -uty_write(vty_io vio) +static cmd_return_code_t +uty_vf_write_close(vio_vf vf, bool final) { - int ret ; - - VTY_ASSERT_LOCKED() ; - - ret = -1 ; - while (vio->sock.write_open) - { - /* Any outstanding line control output takes precedence */ - if (vio->cmd_lc != NULL) - { - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - if (ret != 0) - break ; - } - - /* Next: empty out the cli output */ - ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; - if (ret != 0) - break ; - - /* Finished now if not allowed to progress the command stuff */ - if (!vio->cmd_out_enabled) - return not_ready ; /* done all can do */ - - /* Last: if there is something in the command buffer, do that */ - if (!vio_fifo_empty(&vio->cmd_obuf)) - { - if (vio->cmd_out_done) - break ; /* ...but not if done once */ - - vio->cmd_out_done = 1 ; /* done this once */ - - assert(!vio->cli_more_wait) ; - - if (vio->cmd_lc != NULL) - ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - else - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ; - - /* If moved into "--more--" state@ - * - * * the "--more--" prompt is ready to be written, so do that now - * - * * if that completes, then want to run the CLI *now* to perform the - * first stage of the "--more--" process. - */ - if (vio->cli_more_wait) - { - ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; - if (ret == 0) - return now_ready ; - } ; - - if (ret != 0) - break ; - } - - /* Exciting stuff: there is nothing left to output... - * - * ... watch out for half closed state. - */ - if (vio->half_closed) - { - if (vio->close_reason != NULL) - { - vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ - - struct vty* vty = vio->vty ; - if (vio->cli_drawn || vio->cli_dirty) - vty_out(vty, VTY_NEWLINE) ; - vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; - - vio->cmd_in_progress = 0 ; + cmd_return_code_t ret ; + bool base ; - vio->close_reason = NULL ; /* MUST discard now... */ - continue ; /* ... and write away */ - } ; + VTY_ASSERT_CAN_CLOSE_VF(vf) ; - if (!vio->closed) /* avoid recursion */ - uty_close(vio) ; + ret = CMD_SUCCESS ; - return not_ready ; /* it's all over */ - } ; + if (vf->vout_state == vf_closed) + return ret ; /* quit if already closed */ - /* For VTY_TERM: if the command line is not drawn, now is a good - * time to do that. - */ - if (vio->type == VTY_TERM) - if (uty_cli_draw_if_required(vio)) - continue ; /* do that now. */ - - /* There really is nothing left to output */ - return not_ready ; - } ; + base = (vf == vf->vio->vout_base) ; - /* Arrives here if there is more to do, or failed (or was !write_open) */ + /* Must always close vin before closing vout at the same level. + * + * Note that cannot currently be a pipe return slave, because if was + * slave to a VOUT_PIPE/VOUT_SH_CMD that vout must have been closed + * already. + */ + qassert( (vf->vio->vin_depth < vf->vio->vout_depth) + || ((vf->vio->vin_depth == 0) && (vf->vio->vout_depth == 0)) ) ; + qassert(vf->vin_state == vf_closed) ; + qassert(vf->vio->obuf == vf->obuf) ; - if (ret >= 0) - return write_ready ; + /* If there is anything in the obuf beyond the end_mark, then it is + * assumed to be surplus to requirements, and we clear the end_mark. + */ + vio_fifo_back_to_end_mark(vf->obuf, false) ; - /* If is write_open, then report the error + /* The vout_type specific close functions will attempt to write + * everything away. + * + * If "final", will only keep going until would block -- at which point will + * bring everything to a shuddering halt. * - * If still read_open, let the reader pick up and report the error, when it - * has finished anything it has buffered. + * NB: at this point vout_state is not vf_closed. */ - if (vio->sock.write_open) - { - if (!vio->sock.read_open) - uty_sock_error(vio, "write") ; + switch(vf->vout_type) + { + case VOUT_NONE: + zabort("invalid VOUT_NONE") ; + break ; - vio->sock.write_open = 0 ; /* crash close write */ - } ; + case VOUT_TERM: + ret = uty_term_write_close(vf, final) ; + break ; - /* For whatever reason, is no longer write_open -- clear all buffers. - */ - vio_fifo_clear(&vio->cli_obuf) ; /* throw away cli stuff */ - uty_out_clear(vio) ; /* throw away cmd stuff */ + case VOUT_VTYSH: + break ; - vio->close_reason = NULL ; /* too late for this */ + case VOUT_FILE: + case VOUT_CONFIG: + ret = uty_file_write_close(vf, final) ; + break ; - return not_ready ; /* NB: NOT blocked by I/O */ -} ; + case VOUT_PIPE: + case VOUT_SH_CMD: + ret = uty_pipe_write_close(vf, final) ; + break ; -/*------------------------------------------------------------------------------ - * Write as much as possible -- for "monitor" output. - * - * Outputs only: - * - * a. outstanding line control stuff. - * - * b. contents of CLI buffer - * - * And: - * - * a. does not report any errors. - * - * b. does not change anything except the state of the buffers. - * - * In particular, for the qpthreaded world, does not attempt to change - * the state of the qfile or any other "thread private" structures. - * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed (or !write_open) - */ -static int -uty_write_monitor(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; + case VOUT_DEV_NULL: + case VOUT_STDOUT: + case VOUT_STDERR: + ret = CMD_SUCCESS ; + break ; - if (!vio->sock.write_open) - return -1 ; + default: + zabort("unknown VOUT type") ; + } ; - if (vio->cmd_lc != NULL) + if (((ret == CMD_SUCCESS) && !base) || final) { - int ret ; - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + if (vf->vout_type < VOUT_SPECIALS) + vf->vfd = vio_vfd_close(vf->vfd) ; + else + assert(vf->vfd == NULL) ; + + vf->vout_state = vf_closed ; - if (ret != 0) - return ret ; + ret = CMD_SUCCESS ; } ; - return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + return ret ; } ; /*------------------------------------------------------------------------------ - * Write the given FIFO to output -- subject to possible line control. + * Free the given vio_vf structure and all its contents. * - * Note that even if no "--more--" is set, will have set some height, so - * that does not attempt to empty the FIFO completely all in one go. + * Expects the vfd to already have been closed, but will close it if not. * - * If the line control becomes "paused", it is time to enter "--more--" state - * -- unless the FIFO is empty (or "--more--" is not enabled). + * Expects any cli to be closed, but will close it if not. * - * NB: expects that the sock is write_open - * - * Returns: > 0 => blocked or completed one tranche - * 0 => all gone - * < 0 => failed + * Assumes has been removed from any and all lists ! */ -static int -uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +static vio_vf +uty_vf_free(vio_vf vf) { - int ret ; - char* src ; - size_t have ; + assert((vf->vin_state == vf_closed) && (vf->vout_state == vf_closed) + && (vf->pr_state == vf_closed) + && (vf->ps_state == vf_closed)) ; - /* Collect another line_control height's worth of output. - * - * Expect the line control to be empty at this point, but it does not have - * to be. - */ - vio_lc_set_pause(lc) ; /* clears lc->paused */ + XFREE(MTYPE_VTY_NAME, vf->name) ; - src = vio_fifo_get_rdr(vf, &have) ; + assert(vf->cli == NULL) ; - while ((src != NULL) && (!lc->paused)) - { - size_t take ; - take = vio_lc_append(lc, src, have) ; - src = vio_fifo_step_rdr(vf, &have, take) ; - } ; + vf->ibuf = vio_fifo_free(vf->ibuf) ; + vf->cl = qs_reset(vf->cl, free_it) ; + vf->obuf = vio_fifo_free(vf->obuf) ; - vio->cli_dirty = (lc->col != 0) ; + vf->context = cmd_context_free(vf->context, false) ; /* not a copy */ - /* Write the contents of the line control */ - ret = uty_write_lc(vio, vf, lc) ; + vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */ + vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; /* for completeness */ + vf->ps_vfd = vio_vfd_close(vf->ps_vfd) ; /* for completeness */ - if (ret < 0) - return ret ; /* give up now if failed. */ + vf->ps_buf = vio_fifo_free(vf->ps_buf) ; - if ((ret == 0) && vio_fifo_empty(vf)) - return 0 ; /* FIFO and line control empty */ + XFREE(MTYPE_VTY, vf) ; - /* If should now do "--more--", now is the time to prepare for that. - * - * Entering more state issues a new prompt in the CLI buffer, which can - * be written once line control write completes. - * - * The "--more--" cli will not do anything until the CLI buffer has - * cleared. - */ - if (lc->paused && vio->cli_more_enabled) - uty_cli_enter_more_wait(vio) ; - - return 1 ; /* FIFO or line control, not empty */ + return NULL ; } ; +/*============================================================================== + * vio_vf level read/write ready and timeout setting + */ + /*------------------------------------------------------------------------------ - * Write contents of line control (if any). + * Set required read ready state. Applies the current read timeout. * - * NB: expects that the sock is write_open + * Forces off if: vf->vin_state != vf_open * - * NB: does nothing other than write() and buffer management. + * Does nothing if: vf->vin_state == vf_closed + * or: vf->vfd == NULL * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed + * NB: must NOT be a "blocking" vf */ -static int -uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +extern void +uty_vf_set_read(vio_vf vf, on_off_b how) { - int ret ; + if (vf->vin_state != vf_open) + how = off ; - ret = vio_lc_write_nb(vio->sock.fd, lc) ; - - if (ret <= 0) - vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ - - return ret ; + if (vf->vin_state != vf_closed) + vio_vfd_set_read(vf->vfd, how, vf->read_timeout) ; } ; /*------------------------------------------------------------------------------ - * Start command output -- clears down the line control. + * Set required read ready timeout -- if already read on, restart it. * - * Requires that that current line is empty -- restarts the line control - * on the basis that is at column 0. + * If this is a blocking vf, will set the timeout value, but since can never + * be "read on", will never attempt to restart any timer ! */ extern void -uty_cmd_output_start(vty_io vio) +uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) { - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; + vf->read_timeout = read_timeout ; + + if (!vf->blocking) + uty_vf_set_read(vf, on) ; } ; /*------------------------------------------------------------------------------ - * Set the effective height for line control (if any) + * Set required write ready state. Applies the current write timeout. * - * If using line_control, may enable the "--more--" output handling. + * Forces off if: vf->vout_state != vf_open * - * If not, want some limit on the amount of stuff output at a time. + * Does nothing if: vf->vout_state == vf_closed + * or: vf->vfd == NULL * - * Sets the line control window width and height. - * Sets cli_more_enabled if "--more--" is enabled. + * NB: must NOT be a "blocking" vf */ extern void -uty_set_height(vty_io vio) +uty_vf_set_write(vio_vf vf, on_off_b how) { - bool on ; - - on = 0 ; /* default state */ - - if ((vio->cmd_lc != NULL) && !vio->half_closed) - { - int height ; - - height = 0 ; /* default state */ - - if ((vio->width) != 0) - { - /* If window size is known, use lines or given height */ - if (vio->lines >= 0) - height = vio->lines ; - else - { - /* Window height, leaving one line from previous "page" - * and one line for the "--more--" -- if at all possible - */ - height = vio->height - 2 ; - if (height < 1) - height = 1 ; - } ; - } - else - { - /* If window size not known, use lines if that has been set - * explicitly for this terminal. - */ - if (vio->lines_set) - height = vio->lines ; - } ; + if (vf->vout_state != vf_open) + how = off ; - if (height > 0) - on = 1 ; /* have a defined height */ - else - height = 200 ; /* but no "--more--" */ + if (vf->vout_state != vf_closed) + vio_vfd_set_write(vf->vfd, how, vf->write_timeout) ; +} ; - vio_lc_set_window(vio->cmd_lc, vio->width, height) ; - } ; +/*------------------------------------------------------------------------------ + * Set required write ready timeout -- if already write on, restart it. + * + * If this is a blocking vf, will set the timeout value, but since can never + * be "write on", will never attempt to restart any timer ! + */ +extern void +uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) +{ + vf->write_timeout = write_timeout ; - vio->cli_more_enabled = on ; + if (!vf->blocking) + uty_vf_set_write(vf, on) ; } ; /*============================================================================== - * Timer for VTY_TERM (and VTY_SHELL_SERV). + * I/O error and timeout reporting. + * + * Posts error information and locus to the vio, which is signalled by a + * CMD_IO_ERROR return code. */ /*------------------------------------------------------------------------------ - * Timer has expired. + * Dealing with an I/O error or time-out on the given vio_vf: + * + * * sets the vf->vin_state, vf->vout_state, vf->pr_state or vf->ps_state to + * vf_end + * + * Note that this does not signal the error -- it means that any further + * I/O on this vin/vout is to be avoided. + * + * Errors and timeouts in either vin or vout also force any pipe return + * and/or pipe stderr return to vf_end. + * + * Errors and timeouts in either pipe return or pipe stderr return also + * force all of vin, vout, pipe return and pipe stderr return to vf_end. + * + * * if vio is a "monitor", turn that off, *before* issuing log message + * wrt to either the vin_base or the vout_base. + * + * * produce suitable log message. * - * If half_closed, then this is curtains -- have waited long enough ! + * * insert suitable message in vio->ebuf * - * Otherwise, half close the VTY and leave it to the death-watch to sweep up. + * * set the vio->err_depth to 0 if this is an error in vin_base or + * vout_base, or to no more than 1 for errors anywhere else. + * + * * signals CMD_IO_ERROR to the command loop -- which is how the pselect() + * process communicates with the command loop. + * + * Returns: CMD_IO_ERROR -- which may be returned to the command loop, as + * well as the vio->signal. */ -static void -uty_timer_expired (vty_io vio) +extern cmd_return_code_t +uty_vf_error(vio_vf vf, vio_err_type_t err_type, int err) { - VTY_ASSERT_LOCKED() ; - - if (vio->half_closed) - return uty_close(vio) ; /* curtains */ + vty_io vio = vf->vio ; - uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ - } ; + VTY_ASSERT_LOCKED() ; -/*------------------------------------------------------------------------------ - * Callback -- qnexus: deal with timer timeout. - */ -static void -vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) -{ - vty_io vio = timer_info ; + /* Set the error level and, if required, turn off any log monitoring *before* + * issuing any logging message. + */ + if ((vf == vio->vin_base) || (vf == vio->vout_base)) + { + vio->err_depth = 0 ; + uty_set_monitor(vio, off) ; + } + else + { + if (vio->err_depth > 1) + vio->err_depth = 1 ; + } ; - VTY_LOCK() ; + /* Set vf->vin_state, vf->vout_state or vf->pr_state, as required. + */ + switch (err_type) + { + case verr_none: + zabort("verr_io_none invalid") ; + break ; - uty_timer_expired(vio); + case verr_io_vin: + case verr_to_vin: + qassert(vf->vin_state != vf_closed) ; - VTY_UNLOCK() ; -} + vf->vin_state = vf_end ; + break ; -/*------------------------------------------------------------------------------ - * Callback -- thread: deal with timer timeout. - */ -static int -vty_timer_thread (struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); + case verr_io_vout: + case verr_to_vout: + qassert(vf->vout_state != vf_closed) ; - VTY_LOCK() ; + vf->vout_state = vf_end ; + break ; - vio->sock.t_timer = NULL ; /* implicitly */ + case verr_io_pr: + case verr_to_pr: + qassert(vf->pr_state != vf_closed) ; + uty_pipe_return_stop(vf) ; + break ; - uty_timer_expired(vio) ; + case verr_io_ps: + case verr_to_ps: + qassert(vf->ps_state != vf_closed) ; + uty_pipe_return_stop(vf) ; + break ; - VTY_UNLOCK() ; - return 0; -} + default: + zabort("unknown verr_xxxx") ; + } ; -/*============================================================================== - * VTY Listener(s) - * - * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY. - */ + /* If there is a pipe return (still active), then stop it now -- no point + * continuing after main vin/vout has failed. + * + * Ditto pipe stderr return. + */ + if (vf->pr_state == vf_open) + vf->pr_state = vf_end ; -typedef struct vty_listener* vty_listener ; + if (vf->ps_state == vf_open) + vf->ps_state = vf_end ; -struct vty_listener -{ - vty_listener next ; /* ssl type list */ + /* Log the error and add an error message to the vio->ebuf. + */ + zlog_warn("%s", uty_error_message(vf, err_type, err, true).str) ; - enum vty_type type ; + vio_fifo_printf(uty_cmd_get_ebuf(vio), "\n%s\n", + uty_error_message(vf, err_type, err, false).str) ; - struct vio_sock sock ; -}; + /* Signal to the command loop, if required, and return CMD_IO_ERROR. + * + * One or both will be collected in the command loop "hiatus" and dealt + * with -- it does not matter if both arrive. + */ + uty_cmd_signal(vio, CMD_IO_ERROR) ; -/* List of listeners so can tidy up. */ -static vty_listener vty_listeners_list = NULL ; - -/* Prototypes for listener stuff */ -static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ; -static int uty_serv_sock(const char* addr, unsigned short port) ; -static int uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) ; -static int uty_serv_vtysh(const char *path) ; -static int vty_accept_thread(struct thread *thread) ; -static void vty_accept_qnexus(qps_file qf, void* listener) ; -static int uty_accept(vty_listener listener, int listen_sock) ; -static int uty_accept_term(vty_listener listener) ; -static int uty_accept_shell_serv (vty_listener listener) ; - -static void uty_serv_start_listener(int fd, enum vty_type type) ; + return CMD_IO_ERROR ; +} ; /*------------------------------------------------------------------------------ - * If possible, will use getaddrinfo() to find all the things to listen on. + * Construct error message for given I/O or time-out error */ +extern verr_mess_t +uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) +{ + QFB_QFS(verr_mess, qfs) ; -#if defined(HAVE_IPV6) && !defined(NRL) -# define VTY_USE_ADDRINFO 1 -#else -# define VTY_USE_ADDRINFO 0 -#endif + const char* name ; + const char* where ; + const char* what ; + bool vout ; + bool io ; + int fd ; -/*------------------------------------------------------------------------------ - * Open VTY listener(s) - * - * addr -- address ) to listen for VTY_TERM connections - * port -- port ) - * path -- path for VTYSH connections -- if VTYSH_ENABLED - */ -extern void -uty_open_listeners(const char *addr, unsigned short port, const char *path) -{ VTY_ASSERT_LOCKED() ; - /* If port is set to 0, do not listen on TCP/IP at all! */ - if (port) + vout = false ; + fd = -1 ; + what = NULL ; + + switch (err_type & verr_mask) { - int n ; + case verr_vin: + vout = false ; + what = "read" ; + if (log && (vf->vfd != NULL)) + fd = vio_vfd_fd(vf->vfd) ; + break ; - if (VTY_USE_ADDRINFO) - n = uty_serv_sock_addrinfo(addr, port); - else - n = uty_serv_sock(addr, port); + case verr_vout: + vout = true ; + what = "write" ; + if (log && (vf->vfd != NULL)) + fd = vio_vfd_fd(vf->vfd) ; + break ; - if (n == 0) - uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ; - } + case verr_pr: + vout = true ; + what = "pipe return" ; + if (log) + fd = vio_vfd_fd(vf->pr_vfd) ; + break ; - /* If want to listen for vtysh, set up listener now */ - if (VTYSH_ENABLED && (path != NULL)) - uty_serv_vtysh(path) ; -} ; + case verr_ps: + vout = (vf->vout_state != vf_closed) ; + what = "stderr return" ; + if (log) + fd = vio_vfd_fd(vf->ps_vfd) ; + break ; + } ; -/*------------------------------------------------------------------------------ - * Close VTY listener - * - * addr -- address ) to listen for VTY_TERM connections - * port -- port ) - * path -- path for VTYSH connections -- if VTYSH_ENABLED - */ -extern void -uty_close_listeners(void) -{ - vty_listener listener ; + name = vf->name ; + where = NULL ; - VTY_ASSERT_LOCKED() ; + io = (err_type & verr_to) == 0 ; + confirm((verr_to != 0) && (verr_io == 0)) ; - while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) + if (vout) { - uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ - XFREE(MTYPE_VTY, listener) ; - } ; -} ; + switch (vf->vout_type) + { + case VOUT_NONE: + zabort("VOUT_NONE invalid") ; + break ; -/*------------------------------------------------------------------------------ - * Open listener(s) for VTY_TERM -- using getaddrinfo(). - * - * Returns: number of listeners successfully opened. - */ -static int -uty_serv_sock_addrinfo (const char *hostname, unsigned short port) -{ -#if VTY_USE_ADDRINFO + case VOUT_TERM: + where = "Terminal" ; + if (!log) + name = NULL ; + break ; + + case VOUT_VTYSH: + where = "VTY Shell" ; + if (!log) + name = NULL ; + break ; -# ifndef HAVE_IPV6 -# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? -# endif + case VOUT_FILE: + where = "File" ; + break ; - int ret; - int n ; - struct addrinfo req; - struct addrinfo *ainfo; - struct addrinfo *ainfo_save; - char port_str[16]; + case VOUT_PIPE: + where = "Pipe" ; + break ; - VTY_ASSERT_LOCKED() ; + case VOUT_CONFIG: + where = "Configuration file" ; + break ; - /* Want to listen, TCP-wise, on all available address families, on the - * given port. - */ - memset (&req, 0, sizeof (struct addrinfo)); - req.ai_flags = AI_PASSIVE; - req.ai_family = AF_UNSPEC; - req.ai_socktype = SOCK_STREAM; - snprintf(port_str, sizeof(port_str), "%d", port); + case VOUT_DEV_NULL: + where = "/dev/null" ; + break ; - ret = getaddrinfo (hostname, port_str, &req, &ainfo); + case VOUT_SH_CMD: + where = "Command" ; + break ; - if (ret != 0) - { - fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); - exit (1); - } + case VOUT_STDOUT: + where = "stdout" ; + break ; - /* Open up sockets on all AF_INET and AF_INET6 addresses */ - ainfo_save = ainfo; + case VOUT_STDERR: + where = "stderr" ; + break ; - n = 0 ; - do + default: + zabort("unknown vout_type") ; + break ; + } ; + } + else { - if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) - continue; + switch (vf->vin_type) + { + case VIN_NONE: + zabort("VIN_NONE invalid") ; + break ; - assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; + case VIN_TERM: + where = "Terminal" ; + if (!log) + name = NULL ; + break ; - ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype, - ainfo->ai_protocol, ainfo->ai_addr, port) ; - if (ret >= 0) - ++n ; - } - while ((ainfo = ainfo->ai_next) != NULL); + case VIN_VTYSH: + where = "VTY Shell" ; + if (!log) + fd = vio_vfd_fd(vf->vfd) ; + break ; - freeaddrinfo (ainfo_save); + case VIN_FILE: + where = "File" ; + break ; - return n ; + case VIN_PIPE: + where = "Pipe" ; + break ; -#else - zabort("uty_serv_sock_addrinfo not implemented") ; -#endif /* VTY_USE_ADDRINFO */ -} + case VIN_CONFIG: + where = "Configuration file" ; + break ; -/*------------------------------------------------------------------------------ - * Open listener(s) for VTY_TERM -- not using getaddrinfo() ! - * - * Returns: number of listeners successfully opened. - */ -static int -uty_serv_sock(const char* addr, unsigned short port) -{ - int ret; - int n ; - union sockunion su_addr ; - struct sockaddr* sa ; + case VIN_DEV_NULL: + where = "/dev/null" ; + break ; - VTY_ASSERT_LOCKED() ; + default: + zabort("unknown vin_type") ; + break ; + } ; + } ; - n = 0 ; /* nothing opened yet */ + qfs_printf(qfs, "%s %s %s", where, what, io ? "I/O error" : "time-out") ; - /* If have an address, see what kind and whether valid */ - sa = NULL ; + if (name != NULL) + qfs_printf(qfs, " '%s'", name) ; - if (addr != NULL) - { - ret = str2sockunion (addr, &su_addr) ; - if (ret == 0) - sa = &su_addr.sa ; - else - uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); - } ; + if (fd >= 0) + qfs_printf(qfs, " (fd=%d)", fd) ; - /* Try for AF_INET */ - ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ; - if (ret >= 0) - ++n ; /* opened socket */ - if (ret == 1) - sa = NULL ; /* used the address */ - -#if HAVE_IPV6 - /* Try for AF_INET6 */ - ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ; - if (ret >= 0) - ++n ; /* opened socket */ - if (ret == 1) - sa = NULL ; /* used the address */ -#endif + if (io && (err != 0)) + qfs_printf(qfs, ": %s", errtoa(err, 0).str) ; - /* If not used the address... something wrong */ - if (sa != NULL) - zlog_err("could not use address %s, to listen for VTY", addr); + qfs_term(qfs) ; + return verr_mess ; +} ; - /* Done */ - return n ; -} +/*============================================================================== + * Child care. + * + * Management of vio_child objects and the vio_childer_list. + * + * When child is registered it is placed on the vio_childer_list. It remains + * there until it is "collected", that is until a waitpid() has returned a + * "report" for the child. + * + * When SIGCHLD events go off, the return code for the child is collected, + * and saved until the pipe I/O wants it. + * + * When pipe I/O is closing a pipe and requires the return code from the + * child, if the child has not already been collected (after a SIGCHLD), + * then: + * + * * for blocking vf (configuration reading), uty_child_collect() is used, + * in which a mini-pselect is used to wait and time-out. A SIGCHLD will + * wake up the CLI pthread (or the only thread if not multi-pthreaded) + * and, if required, that will wake the waiting pthread by sending a + * Quagga SIG_INTERRUPT to it. + * + * * for non-blocking vf, utf_child_awaited() is used, in which a time-out + * timer is set, in case the SIGCHLD does not arrive in good time. When + * the child is collected or the timer goes off, a uty_cmd_signal() is + * sent. + * + * When the parent sees that the child is "collected" or "overdue", it can + * examine any report and then dismiss the child. + * + * NB: time-out while waiting to collect a child is not treated as an error, + * here -- so no uty_vf_error() is signalled. + * + * When a parent "dismisses" a child, if it has not yet been collected it is + * "smacked" -- kill(SIGTERM) -- but kept on the register until it is + * collected. When a child which has been collected is dismissed it is freed. + * + * At "curtains" the register may contain children which have been dismissed, + * but not yet collected. Should NOT contain any children with living parents. + * All children remaining on the register are smacked, and all with no living + * parents are freed. (This could leave children on the register, but avoids + * the possibility of a dangling reference from a parent.) -/*------------------------------------------------------------------------------ - * Open a VTY_TERM listener socket. + * The state of a vio_child object includes: + * + * parent -- pointer to the parent vf, if any -- set when the child is + * registered. + * + * A NULL parent pointer <=> the child is an orphan. + * + * At "curtains" all children are orphaned. + * + * collected -- this is set true when the child termination code is picked + * up (by uty_waitpid). It is forced true at "curtains". + * + * When a child is collected it is removed from the register. + * + * Once removed from the register, a child is the responsibility + * of its parent, if any. * - * The sockaddr 'sa' may be NULL or of a different address family, in which - * case "any" address is used. + * When an orphan is removed from the register it can be freed. * - * If the sockaddr 'sa' is used, only the address portion is used. + * awaited -- this is true iff the parent is non-blocking and is now + * waiting to collect the child. * - * Returns: < 0 => failed - * == 0 => OK -- did not use the sockaddr 'sa'. - * > 1 => OK -- and did use the sockaddr 'sa' + * "awaited" <=> there is a timer running. + * + * When the timer goes off "awaited" is cleared, but the timer + * still exists (but is not running). + * + * overdue -- this is set true if times out waiting for child to be + * collected, or can wait no longer ("final" close). */ -static int -uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) -{ - union sockunion su[1] ; - int sock_fd ; - int ret ; +static void uty_child_collected(vio_child child, int report) ; +static vty_timer_time vty_child_overdue(vio_timer timer, void* action_info) ; +static void uty_child_signal_parent(vio_child child) ; +static void uty_child_free(vio_child child) ; +static pid_t uty_waitpid(pid_t for_pid, int* p_report) ; +/*------------------------------------------------------------------------------ + * Set the vty_child_signal_nexus() -- if required. + * + * The SIGCHLD signal will wake up the cli thread. For blocking I/O (reading + * configuration file) may need to wake up another thread, for which the + * SIG_INTERRUPT signal is used. + * + * Note that this is set/cleared under VTY_LOCK() and its own mutex. This + * allows it to be read under its own mutex and/or VTY_LOCK(). + */ +extern void +uty_child_signal_nexus_set(vty_io vio) +{ VTY_ASSERT_LOCKED() ; - /* Is there an address and is it for this family ? */ - if ((sa != NULL) || (sa->sa_family == family)) - /* Set up sockunion containing required family and address */ - sockunion_new_sockaddr(su, sa) ; - else + qassert(vio->blocking) ; + + if (!vty_is_cli_thread()) { - /* no address or wrong family -- set up empty sockunion of - * required family */ - sockunion_init_new(su, family) ; - sa = NULL ; - } ; + qpt_mutex_lock(vty_child_signal_mutex) ; - /* Open the socket and set its properties */ - sock_fd = sockunion_socket(su, type, protocol) ; - if (sock_fd < 0) - return -1 ; + vty_child_signal_nexus = qpn_find_self() ; - ret = setsockopt_reuseaddr (sock_fd); + qpt_mutex_unlock(vty_child_signal_mutex) ; + } ; +} ; - if (ret >= 0) - ret = setsockopt_reuseport (sock_fd); +/*------------------------------------------------------------------------------ + * If there is a nexus to signal, do that. + */ +extern void +vty_child_signal_nexus_signal(void) +{ + qpt_mutex_lock(vty_child_signal_mutex) ; - if (ret >= 0) - ret = set_nonblocking(sock_fd); + if (vty_child_signal_nexus != NULL) + qpt_thread_signal(vty_child_signal_nexus->thread_id, + vty_child_signal_nexus->pselect_signal) ; -#ifdef HAVE_IPV6 - /* Want only IPv6 on AF_INET6 socket (not mapped addresses) - * - * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the - * attempt to bind to :: after binding to 0.0.0.0. - */ - if ((ret >= 0) && (family == AF_INET6)) - ret = setsockopt_ipv6_v6only(sock_fd) ; -#endif + qpt_mutex_unlock(vty_child_signal_mutex) ; +} - if (ret >= 0) - ret = sockunion_bind (sock_fd, su, port, (sa == NULL)) ; +/*------------------------------------------------------------------------------ + * Clear the vty_child_signal_nexus() -- if required. + * + * Note that this is set/cleared under VTY_LOCK() and its own mutex. This + * allows it to be read under its own mutex and/or VTY_LOCK(). + */ +extern void +uty_child_signal_nexus_clear(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; - if (ret >= 0) - ret = sockunion_listen (sock_fd, 3); + assert(vio->blocking) ; - if (ret < 0) + if (!vty_is_cli_thread()) { - close (sock_fd); - return -1 ; - } + qpt_mutex_lock(vty_child_signal_mutex) ; - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock_fd, VTY_TERM) ; + vty_child_signal_nexus = NULL ; - /* Return OK and signal whether used address or not */ - return (sa != NULL) ? 1 : 0 ; + qpt_mutex_unlock(vty_child_signal_mutex) ; + } ; } ; /*------------------------------------------------------------------------------ - * Open a VTY_SHEL_SERV listener socket (UNIX domain). + * New child. * - * Returns: < 0 => failed - * >= 0 => OK + * NB: must register child promptly (under VTY_LOCK, at same time as fork) + * in order to avoid missing the SIG_CHLD ! */ -static int -uty_serv_vtysh(const char *path) +extern vio_child +uty_child_register(pid_t pid, vio_vf parent) { - int ret; - int sock, sa_len, path_len ; - struct sockaddr_un sa_un ; - mode_t old_mask; - struct zprivs_ids_t ids; + vio_child child ; VTY_ASSERT_LOCKED() ; - /* worry about the path length */ - path_len = strlen(path) + 1 ; - if (path_len >= (int)sizeof(sa_un.sun_path)) - { - uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); - return -1 ; - } ; - - /* First of all, unlink existing socket */ - unlink (path); - - /* Make UNIX domain socket. */ - sock = socket (AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) - { - uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", - errtoa(errno, 0).str) ; - return -1 ; - } - - /* Bind to the required path */ - memset (&sa_un, 0, sizeof(sa_un)); - sa_un.sun_family = AF_UNIX; - strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1); - - sa_len = SUN_LEN(&sa_un) ; - -#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN - sa_un.sun_len = sa_len ; -#endif + child = XCALLOC(MTYPE_VTY, sizeof(struct vio_child)) ; - old_mask = umask (0007); + /* Zeroising has set: + * + * list -- NULLs -- added to vio_childer_list, below + * + * parent -- NULL -- parent vf set, below + * + * pid -- X -- child pid set below + * collected -- false -- not yet collected + * report -- X -- not relevant until collected + * + * awaited -- false -- not waiting for child + * overdue -- false -- child not overdue + * timer -- NULL -- no waiting timer set + */ + child->parent = parent ; + child->pid = pid ; - ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ; - if (ret < 0) - uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); + sdl_push(vio_childer_list, child, list) ; - if (ret >= 0) - ret = set_nonblocking(sock); + return child ; +} ; - if (ret >= 0) - { - ret = listen (sock, 5); - if (ret < 0) - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, - errtoa(errno, 0).str) ; - } ; +/*------------------------------------------------------------------------------ + * Set waiting for child to be collected -- if not already set. + * + * This is for !vf->blocking. + * + * If not already waiting for child, set timer. + * If is already waiting, leave existing timer running. + */ +extern void +uty_child_awaited(vio_child child, vty_timer_time timeout) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; - zprivs_get_ids(&ids); + qassert(child->parent != NULL) ; + qassert(!child->parent->blocking) ; - if (ids.gid_vty > 0) + if (child->awaited) { - /* set group of socket */ - if ( chown (path, -1, ids.gid_vty) ) - uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", - errtoa(errno, 0).str) ; + qassert(child->timer != NULL) ; } - - umask (old_mask); - - /* Give up now if failed along the way */ - if (ret < 0) + else { - close (sock) ; - return -1 ; - } ; + child->awaited = true ; - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_SHELL_SERV) ; + if (child->timer == NULL) + child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ; - return 0 ; + vio_timer_set(child->timer, timeout) ; + } ; } ; /*------------------------------------------------------------------------------ - * Socket is open -- set a VTY listener going + * Clear waiting for child to be collected, if that is set, and discard + * any timer. + * + * NB: child may already be orphaned. * - * Note that the vyt_listener structure is passed to the accept action function. + * NB: timer is not discarded when goes off, so will still exist, even + * though is no longer "awaited". + * + * If has a timer, then must be in the CLI thread -- which will be, because + * all !vf->blocking child handling is done in the CLI thread. */ static void -uty_serv_start_listener(int fd, enum vty_type type) +uty_child_not_awaited(vio_child child) { - vty_listener listener ; - - listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); + VTY_ASSERT_LOCKED() ; - ssl_push(vty_listeners_list, listener, next) ; - uty_sock_init_new(&listener->sock, fd, listener) ; + if (child->awaited) /* "awaited" => not orphan & timer running */ + { + qassert(child->parent != NULL) ; + qassert(!child->parent->blocking) ; + qassert(child->timer != NULL) ; - listener->type = type ; + child->awaited = false ; + } ; - if (vty_cli_nexus) - listener->sock.action.read.qnexus = vty_accept_qnexus ; - else - listener->sock.action.read.thread = vty_accept_thread ; + if (child->timer != NULL) /* => in CLI thread */ + { + VTY_ASSERT_CLI_THREAD() ; - uty_sock_set_read(&listener->sock, on) ; + child->timer = vio_timer_reset(child->timer, free_it) ; + } ; } ; /*------------------------------------------------------------------------------ - * Accept action for the thread world -- create and dispatch VTY + * See if parent can collect child -- directly. + * + * This is for vf->blocking, or for "final" !vf->blocking. + * + * If can, will collect now -- marking collected and taking off the + * vio_childer_list. + * + * If cannot immediately collect, if final mark overdue and return. + * + * Otherwise wait for up to timeout seconds for a suitable SIGCHLD or related + * wake-up signal. + * + * Returns: true <=> collected + * false => timed out or final */ -static int -vty_accept_thread(struct thread *thread) +extern bool +uty_child_collect(vio_child child, vty_timer_time timeout, bool final) { - vty_listener listener = THREAD_ARG(thread) ; - int result ; + bool first ; - VTY_LOCK() ; + VTY_ASSERT_LOCKED() ; - result = uty_accept(listener, THREAD_FD(thread)); + qassert(child->parent != NULL) ; + qassert(child->parent->blocking || final) ; + qassert(child->pid > 0) ; - uty_sock_set_read(&listener->sock, on) ; + uty_child_not_awaited(child) ; /* clear flag & timer */ - VTY_UNLOCK() ; - return result ; -} ; + first = true ; + while (1) + { + pid_t pid ; + int report ; -/*------------------------------------------------------------------------------ - * Accept action for the qnexus world -- create and dispatch VTY - */ -static void -vty_accept_qnexus(qps_file qf, void* listener) -{ - VTY_LOCK() ; + qps_mini_t qm ; + sigset_t* sig_mask = NULL ; - uty_accept(listener, qf->fd); + /* If already collected, or succeed in collecting, we are done. */ + if (child->collected) + return true ; /* already collected */ - VTY_UNLOCK() ; -} + pid = uty_waitpid(child->pid, &report) ; -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV - */ -static int -uty_accept(vty_listener listener, int listen_sock) -{ - VTY_ASSERT_LOCKED() ; + if (pid == child->pid) + { + uty_child_collected(child, report) ; /* not orphan */ + return true ; /* collected */ + } ; - assert(listener->sock.fd == listen_sock) ; + /* If "final" or got an error, mark child overdue and give up */ + if (final || (pid < 0)) + { + child->overdue = true ; + return false ; /* overdue */ + } ; - switch (listener->type) - { - case VTY_TERM: - return uty_accept_term(listener) ; + /* Need to wait -- if this is the first time through, prepare for + * that. + */ + if (first) + { + qps_mini_set(qm, -1, 0, 6) ; - case VTY_SHELL_SERV: - return uty_accept_shell_serv(listener) ; + if (vty_is_cli_thread()) + sig_mask = NULL ; + else + sig_mask = vty_child_signal_nexus->pselect_mask ; - default: - zabort("unknown vty type") ; + first = false ; + } ; + + /* Wait on pselect. */ + if (qps_mini_wait(qm, sig_mask, true) == 0) + final = true ; /* timed out => now final */ } ; } ; /*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM + * Dismiss child + * + * If already collected, free the child. + * + * If not collected, smack but leave to be collected in due course (or free + * now at "curtains"). + * + * This may be called in any thread -- but must be CLI thread if there is + * a timer, that is: must be CLI thread if this is/was non-blocking. + * + * When the register is closed, is done in CLI thread. + * + * NB: child will have been freed if was collected or this is "curtains". */ -static int -uty_accept_term(vty_listener listener) +extern void +uty_child_dismiss(vio_child child, bool curtains) { - int sock_fd; - union sockunion su; - int ret; - unsigned int on; - struct prefix *p ; - VTY_ASSERT_LOCKED() ; - /* We can handle IPv4 or IPv6 socket. */ - sockunion_init_new(&su, AF_UNSPEC) ; - - sock_fd = sockunion_accept (listener->sock.fd, &su); + uty_child_not_awaited(child) ; - if (sock_fd < 0) + if (child->parent != NULL) { - if (sock_fd == -1) - uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", - errtoa(errno, 0).str) ; - return -1; - } - - /* Really MUST have non-blocking */ - ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ - if (ret < 0) - { - close(sock_fd) ; - return -1 ; + child->parent->child = NULL ; + child->parent = NULL ; /* orphan from now on */ } ; - /* New socket is open... worry about access lists */ - p = sockunion2hostprefix (&su); - ret = 0 ; /* so far, so good */ - - if ((p->family == AF_INET) && vty_accesslist_name) - { - /* VTY's accesslist apply. */ - struct access_list* acl ; - - if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - ret = -1 ; - } - -#ifdef HAVE_IPV6 - if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) + if (child->collected) + uty_child_free(child) ; + else { - /* VTY's ipv6 accesslist apply. */ - struct access_list* acl ; + qassert(child->pid > 0) ; - if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - ret = -1 ; - } -#endif /* HAVE_IPV6 */ - - prefix_free (p); + kill(child->pid, SIGKILL) ; /* hasten the end */ - if (ret != 0) - { - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; - close (sock_fd); - return 0; + if (curtains) + uty_child_collected(child, 0) ; /* orphan: so free it */ } ; - - /* Final options (optional) */ - on = 1 ; - ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, - (void*)&on, sizeof (on)); - if (ret < 0) - uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", - sock_fd, errtoa(errno, 0).str) ; - - /* All set -- create the VTY_TERM */ - uty_new_term(sock_fd, &su); - - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, - sock_fd) ; - - return 0; -} +} ; /*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_SHELL_SERV + * At "curtains" -- dismiss any children left in the register. */ -static int -uty_accept_shell_serv (vty_listener listener) +extern void +vty_child_close_register(void) { - int sock_fd ; - int ret ; - int client_len ; - struct sockaddr_un client ; - - VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; - client_len = sizeof(client); - memset (&client, 0, client_len); + while (vio_childer_list != NULL) + uty_child_dismiss(vio_childer_list, true) ; /* curtains */ +} ; - sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, - (socklen_t *) &client_len) ; +/*------------------------------------------------------------------------------ + * See whether any children are ready for collection, and check each one + * against the register. + * + * Perform waitpid( , , WNOHANG) until no child is returned, and process + * each one against the register. + * + * The is done when a SIGCHLD is routed through the event mechanism. + * + * If another SIGCHLD occurs while this is being done, that will later cause + * another call of this function -- which may find that there are no children + * to be collected. + * + * This is also done when about to block waiting for a child. + * + * Set any children that can be collected, collected and signal to any parents + * that their children are now ready. + */ +extern void +uty_sigchld(void) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; - if (sock_fd < 0) + while (1) { - uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", - errtoa(errno, 0).str) ; - return -1; - } + vio_child child ; + pid_t pid ; + int report ; - /* Really MUST have non-blocking */ - ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ - if (ret < 0) - { - close(sock_fd) ; - return -1 ; - } ; + pid = uty_waitpid((pid_t)-1, &report) ; - /* All set -- create the VTY_SHELL_SERV */ - if (VTYSH_DEBUG) - printf ("VTY shell accept\n"); + if (pid <= 0) + break ; - uty_new_shell_serv(sock_fd) ; + child = vio_childer_list ; + while (1) + { + if (child == NULL) + { + zlog_err("waitpid(-1) returned pid %d, which is not registered", + pid) ; + break ; + } ; - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); - return 0; -} + if (child->pid == pid) + { + /* Remove child from register and set "collected". Turn off + * any timer and clear "awaited". + * + * If child is not orphaned: set report and signal parent if + * required. + * + * Otherwise: can free the child now. + */ + uty_child_collected(child, report) ; -/*============================================================================== - * Reading from the VTY_SHELL_SERV type sock. - * - * The select/pselect call-back ends up in utysh_read_ready(). - */ + break ; + } ; + + child = sdl_next(child, list) ; + } ; + } ; +} ; /*------------------------------------------------------------------------------ - * Ready to read -> kicking the "SHELL_SERV CLI" + * Have collected a child. + * + * * if a parent is waiting, signal them * - * End up here when there is something ready to be read. + * * clear any awaited state and discard any timer. * - * Will also end up here if an error has occurred, the other end has closed, - * this end has half closed, etc. This fact is used to kick the CLI even when - * there is no data to be read. + * * remove from the register * - * Note that nothing is actually read here -- reading is done in the CLI itself, - * if required. + * * if the child has a parent, set the child's report (the parent is now + * responsible for the child). * - * The CLI decides whether to re-enable read, or enable write, or both. + * otherwise, can free the child now. */ static void -utysh_read_ready(vty_io vio) +uty_child_collected(vio_child child, int report) { - uty_sock_set_read(&vio->sock, off) ; + qassert(!child->collected) ; /* can only collect once */ - /* TODO: need minimal "CLI" for VTY_SHELL_SERV - * NB: when output from command is flushed out, must append the - * following four bytes: '\0' '\0' '\0' <ret> - * Where <ret> is the command return code. - */ + if (child->timer != NULL) + VTY_ASSERT_CLI_THREAD() ; /* must be to clear timer */ + + if (child->awaited) + uty_child_signal_parent(child) ; + uty_child_not_awaited(child) ; /* clear flag and timer */ + + child->collected = true ; /* remove from register */ + sdl_del(vio_childer_list, child, list) ; + + if (child->parent == NULL) + uty_child_free(child) ; + else + child->report = report ; } ; /*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" + * Set child as overdue -- vio_timer action routine. */ -static void -vtysh_read_qnexus(qps_file qf, void* file_info) +static vty_timer_time +vty_child_overdue(vio_timer timer, void* action_info) { - vty_io vio = file_info; + vio_child child ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + child = action_info ; + assert(timer == child->timer) ; - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + if (child->awaited) /* ignore if no longer awaited */ + { + uty_child_signal_parent(child) ; /* clears "awaited" */ - utysh_read_ready(vio) ; + child->overdue = true ; + } ; VTY_UNLOCK() ; -} + + return 0 ; /* stop timer */ +} ; /*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" + * Signal that child is ready -- collected or overdue -- clears "awaited". + * + * Must be "awaited" -- so not "blocking" */ -static int -vtysh_read_thread(struct thread *thread) +static void +uty_child_signal_parent(vio_child child) { - vty_io vio = THREAD_ARG (thread); + qassert(child->awaited) ; + qassert(child->parent != NULL) ; + qassert(!child->parent->vio->blocking) ; - VTY_LOCK() ; + uty_cmd_signal(child->parent->vio, CMD_SUCCESS) ; - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + child->awaited = false ; +} ; - vio->sock.t_read = NULL ; /* implicitly */ - utysh_read_ready(vio); +/*------------------------------------------------------------------------------ + * Free the child -- caller must ensure that any parent has dismissed the child, + * and that it is collected (so not on the vio_childer_list) and that there + * is no timer running. + */ +static void +uty_child_free(vio_child child) +{ + VTY_ASSERT_LOCKED() ; - VTY_UNLOCK() ; - return 0 ; -} + if (child != NULL) + { + qassert(child->collected) ; + qassert(child->parent == NULL) ; + qassert(child->timer == NULL) ; + + XFREE(MTYPE_VTY, child) ; + } ; +} ; /*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the command line buffer - * - * Lines coming in are terminated by '\0'. - * - * Assumes that the incoming command line is empty or otherwise incomplete. - * - * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping - * when get '\0' or empties the "buf". - * - * When empties "buf", reads a lump from the sock. - * - * Returns: 0 => command line is incomplete - * 1 => have a complete command line - * -1 => EOF (or not open, or failed) + * Wrapper for waitpid() -- deal with and log any errors. */ -extern int -utysh_read (vty_io vio, qstring cl, qstring buf) +static pid_t +uty_waitpid(pid_t for_pid, int* p_report) { - int get ; - char* cp ; - char* ep ; - size_t have ; + pid_t pid ; while (1) { - /* process what there is in the buffer */ - if (buf->len > buf->cp) - { - cp = qs_cp_char(buf) ; - ep = qs_ep_char(buf) ; - have = ep - cp ; - - ep = memchr(cp, '\0', have) ; - if (ep != NULL) - have = ep - cp ; /* have upto, but excluding '\0' */ + pid = waitpid(for_pid, p_report, WNOHANG) ; - if (have > 0) /* take what have */ - { - qs_insert(cl, cp, have) ; - cl->cp += have ; - buf->cp += have ; - } ; + if (pid == 0) + return pid ; /* nothing to be had */ - if (ep != NULL) /* if found '\0' */ - { - qs_term(cl) ; /* '\0' terminate */ - ++buf->cp ; /* step past it */ - return 1 ; /* have a complete line <<<<<<<<<<<<< */ - } - } ; + if (pid > 0) /* got an answer */ + { + if ((for_pid < 0) || (pid == for_pid)) + return pid ; - /* buffer is empty -- try and get some more stuff */ - assert(buf->len == buf->cp) ; + /* This is absolutely impossible. If for_pid is > 0, then + * the only valid response > 0 is for_pid !! + * + * Don't know what to do with this, but treating it as a + * "nothing to be had" return seems safe. + */ + zlog_err("waitpid(%d) returned pid=%d", for_pid, pid) ; - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ + return -1 ; + } ; - qs_need(buf, 500) ; /* need a reasonable lump */ - qs_clear(buf) ; /* set cp = len = 0 */ + if (errno == EINTR) + continue ; /* loop on "Interrupted" */ - get = read_nb(vio->sock.fd, buf->body, buf->size) ; - if (get > 0) - buf->len = get ; - else if (get == 0) - return 0 ; /* have an incomplete line <<<<<<<<<<<< */ - else - { - if (get == -1) - uty_sock_error(vio, "read") ; - - vio->sock.read_open = 0 ; + /* Got an error other than EINTR, which is almost impossible... + * + * (1) ECHILD means that the given pid is not a child or there are + * no children. + * + * This is possible if this is called following a SIGCHLD, and + * all children have been collected between the signal and the + * uty_sigchld(). + * + * Note that only call uty_waitpid() for a specific child if it + * is known to not yet have been collected. + * + * (2) only other known error is EINVAL -- invalid options... + * absolutely impossible. + */ + if ((errno != ECHILD) || (for_pid > 0)) + zlog_err("waitpid(%d) returned %s", for_pid, errtoa(errno, 0).str) ; - return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ - } ; + return -1 ; } ; } ; /*============================================================================== - * Output to vty which are set to "monitor". - * - * This is VERY TRICKY. - * - * If not running qpthreaded, then the objective is to get the message away - * immediately -- do not wish it to be delayed in any way by the thread - * system. - * - * So proceed as follows: - * - * a. wipe command line -- which adds output to the CLI buffer - * - * b. write the CLI buffer to the sock and any outstanding line control. - * - * c. write the monitor output. - * - * If that does not complete, put the tail end to the CLI buffer. - * - * d. restore any command line -- which adds output to the CLI buffer - * - * e. write the CLI buffer to the sock - * - * If that all succeeds, nothing has changed as far as the VTY stuff is - * concerned -- except that possibly some CLI output was sent before it got - * round to it. - * - * Note that step (b) will deal with any output hanging around from an - * earlier step (e). If cannot complete that, then does not add fuel to the - * fire -- but the message will be discarded. - * - * If that fails, or does not complete, then can set write on, to signal that - * there is some output in the CLI buffer that needs to be sent, or some - * error to be dealt with. - * - * The output should be tidy. - * - * To cut down the clutter, step (d) is performed only if the command line - * is not empty (or if in cli_more_wait). Once a the user has started to enter - * a command, the prompt and the command will remain visible. - * - * When logging an I/O error for a vty that happens to be a monitor, the - * monitor-ness has already been turned off. The monitor output code does not - * attempt to log any errors, sets write on so that the error will be picked - * up that way. - * - * However, in the event of an assertion failure, it is possible that an - * assertion will fail inside the monitor output. The monitor_busy flag - * prevents disaster. It is also left set if I/O fails in monitor output, so - * will not try to use the monitor again. - * - * Note that an assertion which is false for all vty monitors will recurse - * through all the monitors, setting each one busy, in turn ! - * - - - * TODO: sort out write on in the qpthreads world ?? + * VTY Listener(s) * - * The problem is that the qpselect structure is designed to be accessed ONLY - * within the thread to which it belongs. This makes it impossible for the - * monitor output to set/clear read/write on the vty sock... so some way - * around this is required. + * Have listeners for VTY_TERMINAL and VTY_SHELL_SERVER types of VTY. */ +/* List of listeners so can tidy up. */ +static vio_listener vty_listeners_list = NULL ; + /*------------------------------------------------------------------------------ - * Output logging information to all vty which are set to "monitor". + * Open VTY listener(s) for VTY_TERMINAL and VTY_SHELL_SERVER. + * + * addr -- address ) to listen for VTY_TERMINAL connections + * port -- port ) + * path -- path for VTY_SHELL_SERVER connections -- if VTYSH_ENABLED */ extern void -uty_log(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va) +uty_open_listeners(const char *addr, unsigned short port, const char *path) { - vty_io vio ; - VTY_ASSERT_LOCKED() ; - vio = sdl_head(vio_monitors_base) ; - - if (vio == NULL) - return ; /* go no further if no "monitor" vtys */ - - /* Prepare line for output. */ - uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */ - - /* write to all known "monitor" vty - * - */ - while (vio != NULL) - { - if (!vio->monitor_busy) - { - int ret ; - - vio->monitor_busy = 1 ; /* close the door */ + /* If port is set to 0, do not listen for VTY_TERMINAL at all! */ + if (port) + uty_term_open_listeners(addr, port) ; - uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ +#if 0 + /* If want to listen for vtysh, set up listener now */ + if (VTYSH_ENABLED && (path != NULL)) + uty_serv_vtysh(path) ; +#endif +} ; - ret = uty_write_monitor(vio) ; - if (ret == 0) - { - ret = write_nb(vio->sock.fd, ll->line, ll->len) ; - - if (ret >= 0) - { - ret = uty_cli_post_monitor(vio, ll->line + ret, - ll->len - ret) ; - if (ret > 0) - ret = uty_write_monitor(vio) ; - } ; - } ; +/*------------------------------------------------------------------------------ + * Create listener and set it ready to accept. + * + * Adds to list of listeners for close down. + */ +extern void +uty_add_listener(int fd, vio_vfd_accept* accept_action) +{ + vio_listener vl ; - if (ret != 0) - /* need to prod */ ; + VTY_ASSERT_LOCKED() ; - if (ret >= 0) - vio->monitor_busy = 0 ; - } ; + vl = vio_listener_new(fd, accept_action) ; - vio = sdl_next(vio, mon_list) ; - } ; + ssl_push(vty_listeners_list, vl, next) ; } ; /*------------------------------------------------------------------------------ - * Async-signal-safe version of vty_log for fixed strings. - * - * This is last gasp operation. + * Close all VTY listeners */ -void -vty_log_fixed (const char *buf, size_t len) +extern void +uty_close_listeners(void) { - vty_io vio ; + vio_listener listener ; - /* Write to all known "monitor" vty - * - * Forget all the niceties -- about to die in any case. - */ - vio = sdl_head(vio_monitors_base) ; - while (vio != NULL) - { - write(vio->sock.fd, buf, len) ; - write(vio->sock.fd, "\r\n", 2) ; + VTY_ASSERT_LOCKED() ; - vio = sdl_next(vio, mon_list) ; + while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) + { + vio_listener_close(listener) ; /* no ceremony, no flowers */ } ; } ; |