summaryrefslogtreecommitdiffstats
path: root/lib/vty_io.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vty_io.c')
-rw-r--r--lib/vty_io.c3637
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 */
} ;
} ;