From 5cae7eea451f2b7d65b5892e2c1dafc70f8b836e Mon Sep 17 00:00:00 2001 From: Chris Hall Date: Sun, 13 Feb 2011 23:11:45 +0000 Subject: Second tranche of updates for pipework branch. modified: bgpd/bgp_connection.c modified: bgpd/bgp_debug.c modified: bgpd/bgp_engine.h modified: bgpd/bgp_main.c modified: bgpd/bgp_packet.c modified: bgpd/bgp_peer.c modified: bgpd/bgp_route.c modified: bgpd/bgp_routemap.c modified: bgpd/bgp_session.c modified: bgpd/bgp_vty.c modified: bgpd/bgpd.c modified: bgpd/bgpd.h modified: configure.ac modified: isisd/dict.h modified: isisd/isis_misc.c modified: isisd/isis_routemap.c modified: isisd/isis_spf.c modified: lib/Makefile.am modified: lib/command.c modified: lib/command.h modified: lib/command_execute.h modified: lib/command_parse.c modified: lib/command_parse.h modified: lib/command_queue.c modified: lib/command_queue.h modified: lib/elstring.h modified: lib/heap.c modified: lib/if.c modified: lib/if.h modified: lib/keychain.c modified: lib/keystroke.c modified: lib/keystroke.h modified: lib/list_util.c modified: lib/list_util.h modified: lib/log.c modified: lib/log.h modified: lib/memory.c modified: lib/memory.h modified: lib/memtypes.c modified: lib/misc.h modified: lib/mqueue.c modified: lib/mqueue.h deleted: lib/node_type.h modified: lib/pthread_safe.c modified: lib/qfstring.c modified: lib/qiovec.c modified: lib/qiovec.h modified: lib/qpath.c modified: lib/qpnexus.c modified: lib/qpnexus.h modified: lib/qpselect.c modified: lib/qpthreads.h modified: lib/qstring.c modified: lib/qstring.h modified: lib/qtime.c modified: lib/qtime.h modified: lib/qtimers.c modified: lib/qtimers.h modified: lib/routemap.c modified: lib/symtab.h modified: lib/thread.h deleted: lib/uty.h modified: lib/vector.c modified: lib/vector.h modified: lib/version.h.in modified: lib/vio_fifo.c modified: lib/vio_fifo.h modified: lib/vio_lines.c modified: lib/vio_lines.h modified: lib/vty.c modified: lib/vty.h modified: lib/vty_cli.c modified: lib/vty_cli.h modified: lib/vty_io.c modified: lib/vty_io.h modified: lib/vty_io_basic.c modified: lib/vty_io_basic.h modified: lib/vty_io_file.c modified: lib/vty_io_file.h modified: lib/vty_io_shell.c modified: lib/vty_io_term.c modified: lib/vty_io_term.h modified: lib/vty_local.h modified: lib/vty_pipe.c modified: lib/workqueue.h modified: lib/zebra.h modified: ospf6d/ospf6_lsa.c modified: ripngd/ripngd.c modified: tests/test-list_util.c modified: tests/test-vector.c modified: vtysh/vtysh.c modified: vtysh/vtysh_config.c --- lib/vty_io_file.c | 2688 ++++------------------------------------------------- 1 file changed, 201 insertions(+), 2487 deletions(-) (limited to 'lib/vty_io_file.c') diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c index ed2c35e8..2d17276a 100644 --- a/lib/vty_io_file.c +++ b/lib/vty_io_file.c @@ -1,7 +1,6 @@ -/* VTY Log Functions - * Copyright (C) 1997, 98 Kunihiro Ishiguro +/* VTY I/O for Files * - * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman * * This file is part of GNU Zebra. * @@ -20,2589 +19,304 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ +#include "misc.h" -#include "zebra.h" +#include "command_local.h" -#include "vty.h" -#include "vty_io.h" -#include "vty_cli.h" -#include "qstring.h" -#include "keystroke.h" - -#include "memory.h" - -#include "prefix.h" -#include "filter.h" -#include "privs.h" -#include "sockunion.h" -#include "network.h" - -#include -#include /* for VTYSH */ -#include - -#define VTYSH_DEBUG 0 +#include "vty_io_file.h" +#include "vty_io_basic.h" /*============================================================================== - * VTY Log Output -- provides a "vty" interface to logging. - * - * Once a VTY_LOG object is created, can be used as an (output only) vty. - * - * All output is sent to the logging system, - * - * - * During command processing the output sent here is held until the command - * completes. - */ - -static int uty_config_write(vty_io vio, bool all) ; - -/*------------------------------------------------------------------------------ - * VTY output function -- cf fprintf - * - * 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 - * - * 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) -{ - vty_io vio ; - int ret ; - - 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") ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Clear the contents of the command output FIFO etc. + * VTY File Output * - * 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. + * This is for input and output of configuration files and piped stuff. * - * Takes no notice of any errors ! + * When reading the configuration (and piped stuff in the configuration) I/O + * is blocking... nothing else can run while this is going on. Otherwise, + * all I/O is non-blocking. */ -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) ; -} ; /*============================================================================== * 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) ; - -/*============================================================================== - * Creation and destruction of VTY objects - */ /*------------------------------------------------------------------------------ - * Allocate new vty struct - * - * Allocates and initialises basic vty and vty_io structures, setting the - * given type. - * - * Note that where is not setting up a vty_sock, this *may* be called from - * any thread. * - * NB: may not create a VTY_CONFIG_WRITE type vty directly * - * see: vty_open_config_write() and vty_close_config_write() - * - * 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 !) - * - * the sock_fd is ignored for everything else. - * - * Returns: new vty */ -extern struct vty * -uty_new(enum vty_type type, int sock_fd) +extern cmd_return_code_t +uty_file_read_open(vty_io vio, qstring name, bool reflect) { - struct vty *vty ; - struct 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") ; - } ; + const char* name_str ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; - /* Basic allocation */ - vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); - vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; + name_str = qs_make_string(name) ; - vty->vio = vio ; - vio->vty = vty ; + iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */ - /* 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) - * - * cli_drawn = 0 -- not drawn - * cli_dirty = 0 -- not dirty - * cli_prompt_len = 0 ) - * cli_extra_len = 0 ) not material - * cli_echo_suppress = 0 ) - * - * cli_prompt_node = 0 -- not material - * cli_prompt_set = 0 -- so prompt needs to be constructed - * - * 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--" - * - * cli_more_enabled = 0 -- not enabled for "--more--" - * - * cmd_out_done = 0 -- not material - * - * cli_do = 0 == cli_do_nothing - * - * cmd_lc = NULL -- no line control - * - * fail = 0 -- no login failures yet - * - * hist = empty vector - * hp = 0 -- at the beginning - * hindex = 0 -- the beginning - * - * width = 0 -- unknown console width - * height = 0 -- unknown console height - * - * lines = 0 -- no limit - * lines_set = 0 -- no explicit setting - * - * monitor = 0 -- not a monitor - * monitor_busy = 0 -- not a busy monitor - * - * config = 0 -- not holder of "config" mode - */ - confirm(cli_do_nothing == 0) ; - confirm(AUTH_NODE == 0) ; /* default node type */ + /* Do the basic file open. */ + fd = uty_vfd_file_open(name_str, iot) ; - vio->type = type ; - - /* 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 - */ - - /* 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) ; + if (fd < 0) + { + uty_out(vio, "%% Could not open input file %s\n", name_str) ; - vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ + return CMD_WARNING ; + } - vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; + uty_vin_open(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ; - /* Place on list of known vio/vty */ - sdl_push(vio_list_base, vio, vio_list) ; + vf->parse_type = cmd_parse_strict ; + vf->reflect_enabled = reflect ; - return vty; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * 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) +extern cmd_return_code_t +uty_file_write_open(vty_io vio, qstring name, bool append) { - struct vty *vty ; - vty_io vio ; - enum vty_readiness ready ; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + const char* name_str ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_TERM, sock_fd) ; - vio = vty->vio ; + iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */ - /* Allocate and initialise a keystroke stream TODO: CSI ?? */ - vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + if (append) + iot |= vfd_io_append ; - /* 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 ; - } ; + name_str = qs_make_string(name) ; - /* The text form of the address identifies the VTY */ - vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); + /* Do the basic file open. */ + fd = uty_vfd_file_open(name_str, iot) ; - /* Set the initial node */ - if (no_password_check) + if (fd < 0) { - 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; + uty_out(vio, "%% Could not open output file %s\n", name_str) ; - /* 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; + return CMD_WARNING ; } - 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); - } ; + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; + uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; - /* Now start the CLI and set a suitable state of readiness */ - ready = uty_cli_start(vio) ; - uty_sock_set_readiness(&vio->sock, ready) ; + vf->out_enabled = true ; - return vty; + return CMD_SUCCESS ; } ; -/*------------------------------------------------------------------------------ - * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. +/*============================================================================== + * Command line fetch from a file or pipe. * - * Returns: new vty + * Returns: CMD_SUCCESS -- have another command line ready to go + * CMD_WAITING -- do not have a command line at the moment + * CMD_EOF -- ran into EOF + * CMD_IO_ERROR -- ran into an I/O error */ -static struct vty * -uty_new_shell_serv(int sock_fd) +extern cmd_return_code_t +uty_file_fetch_command_line(vio_vf vf, qstring* line) { - struct vty *vty ; - vty_io vio ; - - VTY_ASSERT_LOCKED() ; - - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_SHELL_SERV, sock_fd) ; - vio = vty->vio ; + assert(vf->vin_state == vf_open) ; - /* Set the action functions */ - if (vty_cli_nexus) + if (vf->line_complete) { - 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 ; - } ; + vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */ - vty->node = VIEW_NODE; + vf->line_complete = false ; + vf->line_number += vf->line_step ; - /* 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() ; + qs_set_len_nn(vf->cl, 0) ; + vf->line_step = 0 ; + } ; - 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) + while (1) { - vio->monitor = 0 ; - sdl_del(vio_monitors_base, vio, mon_list) ; - } -} ; - -/*------------------------------------------------------------------------------ - * Return "name" of VTY - * - * For VTY_TERM this is the IP address of the far end of the telnet connection. - */ -extern const char* -uty_get_name(vty_io vio) -{ - return (vio->name != NULL) ? vio->name : "?" ; -} ; - -/*------------------------------------------------------------------------------ - * Closing down VTY for reading. - * - * For VTY_TERM (must be in CLI thread): - * - * * shut the socket for reading - * * discard all buffered input, setting it to "EOF" - * * turns off any monitor status ! - * * drop down to RESTRICTED_NODE - * - * For VTY_SHELL_SERV (must be in CLI thread): - * - * * shut the socket for reading - * * discard all buffered input - * * drop down to RESTRICTED_NODE - * - * In all cases: - * - * * place on death watch - * * set the vty half_closed - * * sets the reason for closing (if any given) - * - * 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.) - */ -extern void -uty_half_close (vty_io vio, const char* reason) -{ - VTY_ASSERT_LOCKED() ; + char* s, * p, * q, * e ; + size_t have ; + ulen len ; - if (vio->half_closed) - return ; + s = vio_fifo_get(vf->ibuf, &have) ; - if (reason != NULL) - vio->close_reason = reason ; + /* If nothing in hand, try and get some more. + * + * Either exits or loops back to set s & have. + */ + if (have == 0) + { + int get ; - /* Do the file side of things - * - * Note that half closing the file sets a new timeout, sets read off - * and write on. - */ - uty_sock_half_close(&vio->sock) ; - uty_set_monitor(vio, 0) ; + get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ; - /* Discard everything in the keystroke stream and force it to EOF */ - if (vio->key_stream != NULL) - keystroke_stream_set_eof(vio->key_stream) ; + if (get > 0) + continue ; /* loop back */ - /* Turn off "--more--" so that all output clears without interruption. - * - * If is sitting on a "--more--" prompt, then exit the wait_more CLI. - */ - vio->cli_more_enabled = 0 ; + if (get == 0) + return CMD_WAITING ; /* need to set read ready ! */ - if (vio->cli_more_wait) - uty_cli_exit_more_wait(vio) ; + if (get == -1) + ; /* register error */ - /* 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. - */ - if (!vio->cmd_in_progress) - vio->cmd_out_enabled = 1 ; + if (get == -2) + return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ; + } ; - /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, RESTRICTED_NODE) ; + /* Try to find a '\n' -- converting all other control chars to ' ' + * + * When we find '\n' step back across any trailing ' ' (which includes + * any control chars before the '\n'). + * + * This means that we cope with "\r\n" line terminators. But not anything + * more exotic. + */ + p = s ; + e = s + have ; /* have != 0 */ + q = NULL ; - /* Log closing of VTY_TERM */ - if (vio->type == VTY_TERM) - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; + while (p < e) + { + if (*p++ < 0x20) + { + if (*(p-1) != '\n') + { + *(p-1) = ' ' ; /* everything other than '\n' */ + continue ; + } ; - /* Move to the death watch list */ - sdl_del(vio_list_base, vio, vio_list) ; - sdl_push(vio_death_watch, vio, vio_list) ; + ++vf->line_step ; /* got a '\n' */ - vio->half_closed = 1 ; -} ; + q = p ; /* point just past '\n' */ + do --q ; while ((q > s) && (*(q-1) == ' ')) ; + /* discard trailing "spaces" */ + break ; + } ; + } ; -/*------------------------------------------------------------------------------ - * 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() ; + /* Step past what have just consumed -- we have a hold_mark, so + * stuff is still in the fifo. + */ + vio_fifo_step(vf->ibuf, p - s) ; - /* 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 found '\n', then we have a line fragment that needs to be + * appended to any previous line fragments. + * + * Loops back to try to get some more form the fifo. + */ + if (q == NULL) + { + qs_append_n(vf->cl, s, p - s) ; + continue ; + } ; - /* 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 */ + /* If we have nothing so far, set alias to point at what we have in + * the fifo. Otherwise, append to what we have. + * + * End up with: s = start of entire line, so far. + * p = end of entire line so far. + */ + len = q - s ; /* length to add */ + if (qs_len_nn(vf->cl) == 0) + { + qs_set_alias_n(vf->cl, s, len) ; + p = q ; + } + else + { + if (len != 0) + qs_append_n(vf->cl, s, len) ; - vio->closed = 1 ; /* now closed (stop uty_write() - from recursing) */ + s = qs_char_nn(vf->cl) ; + p = s + qs_len_nn(vf->cl) ; - if (vio->sock.write_open) - uty_write(vio) ; /* last gasp attempt */ + if ((len == 0) && (p > s) && (*(p-1) == ' ')) + { + /* Have an empty end of line section, and the last character + * of what we have so far is ' ', so need now to trim trailing + * spaces off the stored stuff. + */ + do --p ; while ((p > s) && (*(p-1) == ' ')) ; - uty_sock_close(&vio->sock) ; + qs_set_len_nn(vf->cl, p - s) ; + } ; + } ; - } ; + /* Now worry about we have a trailing '\'. */ - /* 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) ; + if ((p == s) || (*(p-1) != '\\')) + break ; /* no \ => no continuation => success */ - vio->key_stream = keystroke_stream_free(vio->key_stream) ; + /* Have a trailing '\'. + * + * If there are an odd number of '\', strip the last one and loop + * round to collect the continuation. + * + * If there are an even number of '\', then this is not a continuation. + * + * Note that this rule deals with the case of the continuation line + * being empty... e.g. ....\\\ n n -- where n is '\n' + */ + q = p ; + do --q ; while ((q > s) && (*(q-1) == '\\')) ; - qs_reset(&vio->cli_prompt_for_node, keep_it) ; - qs_reset(&vio->cl, keep_it) ; + if (((p - q) & 1) == 0) + break ; /* even => no continuation => success */ - { - qstring line ; - while ((line = vector_ream(vio->hist, keep_it)) != NULL) - qs_reset_free(line) ; - } ; + qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */ - /* 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_reset(&vio->clx, keep_it) ; - vio->vty->buf = NULL ; + continue ; /* loop back to fetch more */ } ; -} ; - -/*============================================================================== - * For writing configuration file by command, temporarily redirect output to - * an actual file. - */ - -/*------------------------------------------------------------------------------ - * Set the given fd as the VTY_FILE output. - */ -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 ; + /* Success have a line in hand */ - vio->type = VTY_CONFIG_WRITE ; - vio->file_fd = fd ; - vio->file_error = 0 ; + vf->line_complete = true ; + *line = vf->cl ; - VTY_UNLOCK() ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Write away configuration file stuff -- all or just the full lump(s). + * Command output push to a file or pipe. * - * Returns: > 0 => blocked - * 0 => all gone (up to last lump if !all) - * < 0 => failed -- see vio->file_error + * Returns: CMD_SUCCESS -- done + * CMD_IO_ERROR -- ran into an I/O error */ -static int -uty_config_write(vty_io vio, bool all) +extern cmd_return_code_t +uty_file_out_push(vio_vf vf) { - int ret ; + assert(vf->vout_state == vf_open) ; - VTY_ASSERT_LOCKED() ; - - if (vio->file_error == 0) - { - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; - - if (ret < 0) - vio->file_error = errno ; - } + if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0) + return CMD_SUCCESS ; 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->type = vio->real_type ; - vio->file_fd = -1 ; - vio->file_error = 0 ; - - VTY_UNLOCK() ; - - return err ; -} ; - -/*============================================================================== - * vio_sock level operations - */ - -/*------------------------------------------------------------------------------ - * Initialise a new vio_sock structure. - * - * Requires that: the vio_sock structure is not currently in use. - * - * if fd >= 0 then: sock is open and ready read and write - * otherwise: sock is not open - * - * there are no errors, yet. - * - * Sets timeout to no timeout at all -- timeout is optional. - * - * NB: MUST be in the CLI thread if the fd is >= 0 ! - */ -static void -uty_sock_init_new(vio_sock sock, int fd, void* info) -{ - 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); - } ; + return CMD_IO_ERROR ; } ; /*------------------------------------------------------------------------------ - * Restart the timer. - * - * If a timeout time is set, then start or restart the timer with that value. - * - * If no timeout time is set, and the timer is running, unset it. + * Tidy up after input file has been closed */ -static void -uty_sock_restart_timer(vio_sock sock) +extern void +uty_file_read_close(vio_vf vf) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->v_timeout != 0) - { - assert(sock->action.timer.anon != NULL) ; - - 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) ; - } ; - - sock->timer_running = 1 ; - } - else if (sock->timer_running) - { - if (vty_cli_nexus) - { - if (sock->qtr != NULL) - qtimer_unset(sock->qtr) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer) ; - } ; - - sock->timer_running = 0 ; - } ; + return ; } ; /*------------------------------------------------------------------------------ - * Set read on/off + * Flush output buffer and close. * - * Returns: the on/off state set + * Returns: true <=> buffer (now) empty */ -static bool -uty_sock_set_read(vio_sock sock, bool on) +extern bool +uty_file_write_close(vio_vf vf, bool final) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->fd < 0) - return 0 ; - - if (on) - { - 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) ; - } ; - } - else - { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_read_mbit) ; - else - { - if (sock->t_read != NULL) - thread_cancel (sock->t_read) ; - } ; - } ; - - return on ; -} ; - -/*------------------------------------------------------------------------------ - * Set write on/off - * - * Returns: the on/off state set - */ -static bool -uty_sock_set_write(vio_sock sock, bool on) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->fd < 0) - return 0 ; - - if (on) - { - assert(sock->action.write.anon != NULL) ; - - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; - else - { - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; - - 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) ; - else - { - if (sock->t_write != NULL) - thread_cancel (sock->t_write) ; - } ; - } ; - - return on ; -} ; - -/*------------------------------------------------------------------------------ - * Set read/write readiness -- for VTY_TERM - * - * Note that for VTY_TERM, set only one of read or write, and sets write for - * preference. - */ -extern void -uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - uty_sock_set_read(sock, (ready == read_ready)) ; - uty_sock_set_write(sock, (ready >= write_ready)) ; -} ; - -/*------------------------------------------------------------------------------ - * Set a new timer value. - */ -extern void -uty_sock_set_timer(vio_sock sock, unsigned long timeout) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - sock->v_timeout = timeout ; - if (sock->timer_running) - uty_sock_restart_timer(sock) ; -} ; - -/*------------------------------------------------------------------------------ - * 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() ; - - sock->read_open = 0 ; /* make sure */ - - if (sock->fd < 0) - return ; /* nothing more if no socket */ - - VTY_ASSERT_CLI_THREAD() ; - - shutdown(sock->fd, SHUT_RD) ; /* actual half close */ - - 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) ; -} ; - -/*------------------------------------------------------------------------------ - * Close given vio_sock, completely -- shut down any timer. - * - * Structure is cleared of everything except the last error ! - * - * NB: if there is a socket, MUST be in the CLI thread - */ -static void -uty_sock_close(vio_sock sock) -{ - 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 (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 ; - } ; - - sock->fd = -1 ; - - if (sock->t_read != NULL) - thread_cancel(sock->t_write) ; - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; - - sock->t_read = NULL ; - sock->t_write = NULL ; - - sock->info = NULL ; - sock->action.read.anon = NULL ; - sock->action.write.anon = NULL ; - sock->action.timer.anon = NULL ; - - if (sock->qtr != NULL) - qtimer_free(sock->qtr) ; - if (sock->t_timer != NULL) - thread_cancel(sock->t_timer) ; - - sock->v_timeout = 0 ; - sock->qtr = NULL ; - sock->t_timer = NULL ; -} ; - -/*------------------------------------------------------------------------------ - * 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() ; - - /* can no longer be a monitor ! *before* any logging ! */ - uty_set_monitor(vio, 0) ; - - /* 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) ; - } ; - - return -1 ; -} ; - -/*============================================================================== - * 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. - * - * The state of the output affects the CLI, so must always do this before - * before invoking the CLI. - * - * If this write enters the "--more--" state, then will have tried to - * write away the prompt. - * - * 2. invokes the CLI - * - * Which will do either the standard CLI stuff or the special "--more--" - * stuff. - * - * 3. attempts to write any output there now is. - * - * If the CLI generated new output, as much as possible is written away - * now. - * - * If this write enters the "--more--" state, then it returns now_ready, - * if the prompt was written away, which loops back to the CLI. - * - * Note that this is arranging: - * - * a. to write away the "--more--" prompt as soon as the tranche of output to - * which it refers, completes - * - * b. to enter the cli_more_wait CLI for the first time immediately after the - * "--more--" prompt is written away. - * - * The loop limits itself to one trache of command output each time. - * - * Resets the timer because something happened. - */ -static void -uty_ready(vty_io vio) -{ - enum vty_readiness ready ; - - VTY_ASSERT_LOCKED() ; - - vio->cmd_out_done = 0 ; /* not done any command output yet */ - - 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) ; - - uty_sock_set_readiness(&vio->sock, ready) ; - uty_sock_restart_timer(&vio->sock) ; -} ; - -/*============================================================================== - * Reading from VTY_TERM. - * - * The select/pselect call-back ends up in uty_read_ready(). - * - * Note that uty_write_ready() also calls uty_read_ready, in order to kick the - * current CLI. - */ - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking CLI - */ -static void -vty_read_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 -- threads: ready to read -> kicking CLI - */ -static int -vty_read_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_read = NULL ; /* implicitly */ - uty_ready(vio); - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * 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 ; - - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open */ - - 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") ; - - vio->sock.read_open = 0 ; - keystroke_input(vio->key_stream, NULL, 0, steal) ; - - get = -1 ; - } ; - - return get ; -} ; - -/*============================================================================== - * The write sock action for VTY_TERM type VTY - * - * There are two sets of buffering: - * - * cli -- command line -- which reflects the status of the command line - * - * cmd -- command output -- which is written to the file only while - * cmd_out_enabled. - * - * The cli output takes precedence. - * - * 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. - * - * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, - * empty. - * - * Note that if !write_open, or becomes !write_open, then the FIFOs are empty - * and all output instantly successful. - * - * Sets write on if prevented from writing everything available for output - * by write() threatening to block. - * - * Returns: write_ready if should now set write on - * now_ready if should loop back and try again - * not_ready otherwise - */ -static enum vty_readiness -uty_write(vty_io vio) -{ - 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 ; - - vio->close_reason = NULL ; /* MUST discard now... */ - continue ; /* ... and write away */ - } ; - - if (!vio->closed) /* avoid recursion */ - uty_close(vio) ; - - return not_ready ; /* it's all over */ - } ; - - /* 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 ; - } ; - - /* Arrives here if there is more to do, or failed (or was !write_open) */ - - if (ret >= 0) - return write_ready ; - - /* If is write_open, then report the error - * - * If still read_open, let the reader pick up and report the error, when it - * has finished anything it has buffered. - */ - if (vio->sock.write_open) - { - if (!vio->sock.read_open) - uty_sock_error(vio, "write") ; - - vio->sock.write_open = 0 ; /* crash close write */ - } ; - - /* 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 */ - - vio->close_reason = NULL ; /* too late for this */ - - return not_ready ; /* NB: NOT blocked by I/O */ -} ; - -/*------------------------------------------------------------------------------ - * 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() ; - - if (!vio->sock.write_open) - return -1 ; - - if (vio->cmd_lc != NULL) - { - int ret ; - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - - if (ret != 0) - return ret ; - } ; - - return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; -} ; - -/*------------------------------------------------------------------------------ - * Write the given FIFO to output -- subject to possible line control. - * - * 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. - * - * If the line control becomes "paused", it is time to enter "--more--" state - * -- unless the FIFO is empty (or "--more--" is not enabled). - * - * NB: expects that the sock is write_open - * - * Returns: > 0 => blocked or completed one tranche - * 0 => all gone - * < 0 => failed - */ -static int -uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) -{ - int ret ; - char* src ; - size_t have ; - - /* 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 */ - - src = vio_fifo_get_rdr(vf, &have) ; - - while ((src != NULL) && (!lc->paused)) - { - size_t take ; - take = vio_lc_append(lc, src, have) ; - src = vio_fifo_step_rdr(vf, &have, take) ; - } ; - - vio->cli_dirty = (lc->col != 0) ; - - /* Write the contents of the line control */ - ret = uty_write_lc(vio, vf, lc) ; - - if (ret < 0) - return ret ; /* give up now if failed. */ - - if ((ret == 0) && vio_fifo_empty(vf)) - return 0 ; /* FIFO and line control empty */ - - /* 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 */ -} ; - -/*------------------------------------------------------------------------------ - * Write contents of line control (if any). - * - * NB: expects that the sock is write_open - * - * NB: does nothing other than write() and buffer management. - * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed - */ -static int -uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) -{ - int ret ; - - ret = vio_lc_write_nb(vio->sock.fd, lc) ; - - if (ret <= 0) - vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Start command output -- clears down the line control. - * - * Requires that that current line is empty -- restarts the line control - * on the basis that is at column 0. - */ -extern void -uty_cmd_output_start(vty_io vio) -{ - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; -} ; - -/*------------------------------------------------------------------------------ - * Set the effective height for line control (if any) - * - * If using line_control, may enable the "--more--" output handling. - * - * If not, want some limit on the amount of stuff output at a time. - * - * Sets the line control window width and height. - * Sets cli_more_enabled if "--more--" is enabled. - */ -extern void -uty_set_height(vty_io vio) -{ - 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 (height > 0) - on = 1 ; /* have a defined height */ - else - height = 200 ; /* but no "--more--" */ - - vio_lc_set_window(vio->cmd_lc, vio->width, height) ; - } ; - - vio->cli_more_enabled = on ; -} ; - -/*============================================================================== - * Timer for VTY_TERM (and VTY_SHELL_SERV). - */ - -/*------------------------------------------------------------------------------ - * Timer has expired. - * - * If half_closed, then this is curtains -- have waited long enough ! - * - * Otherwise, half close the VTY and leave it to the death-watch to sweep up. - */ -static void -uty_timer_expired (vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->half_closed) - return uty_close(vio) ; /* curtains */ - - uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ - } ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: deal with timer timeout. - */ -static void -vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) -{ - vty_io vio = timer_info ; - - VTY_LOCK() ; - - uty_timer_expired(vio); - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- thread: deal with timer timeout. - */ -static int -vty_timer_thread (struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - vio->sock.t_timer = NULL ; /* implicitly */ - - uty_timer_expired(vio) ; - - VTY_UNLOCK() ; - return 0; -} - -/*============================================================================== - * VTY Listener(s) - * - * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY. - */ - -typedef struct vty_listener* vty_listener ; - -struct vty_listener -{ - vty_listener next ; /* ssl type list */ - - enum vty_type type ; - - struct vio_sock sock ; -}; - -/* 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) ; - -/*------------------------------------------------------------------------------ - * If possible, will use getaddrinfo() to find all the things to listen on. - */ - -#if defined(HAVE_IPV6) && !defined(NRL) -# define VTY_USE_ADDRINFO 1 -#else -# define VTY_USE_ADDRINFO 0 -#endif - -/*------------------------------------------------------------------------------ - * 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) - { - int n ; - - if (VTY_USE_ADDRINFO) - n = uty_serv_sock_addrinfo(addr, port); - else - n = uty_serv_sock(addr, port); - - if (n == 0) - uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ; - } - - /* If want to listen for vtysh, set up listener now */ - if (VTYSH_ENABLED && (path != NULL)) - uty_serv_vtysh(path) ; -} ; - -/*------------------------------------------------------------------------------ - * 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 ; - - VTY_ASSERT_LOCKED() ; - - while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) - { - uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ - XFREE(MTYPE_VTY, listener) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * 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 - -# ifndef HAVE_IPV6 -# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? -# endif - - int ret; - int n ; - struct addrinfo req; - struct addrinfo *ainfo; - struct addrinfo *ainfo_save; - char port_str[16]; - - VTY_ASSERT_LOCKED() ; - - /* 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); - - ret = getaddrinfo (hostname, port_str, &req, &ainfo); - - if (ret != 0) - { - fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); - exit (1); - } - - /* Open up sockets on all AF_INET and AF_INET6 addresses */ - ainfo_save = ainfo; - - n = 0 ; - do - { - if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) - continue; - - assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; - - 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); - - freeaddrinfo (ainfo_save); - - return n ; - -#else - zabort("uty_serv_sock_addrinfo not implemented") ; -#endif /* VTY_USE_ADDRINFO */ -} - -/*------------------------------------------------------------------------------ - * 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 ; - - VTY_ASSERT_LOCKED() ; - - n = 0 ; /* nothing opened yet */ - - /* If have an address, see what kind and whether valid */ - sa = NULL ; - - 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); - } ; - - /* 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 not used the address... something wrong */ - if (sa != NULL) - uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); - - /* Done */ - return n ; -} - -/*------------------------------------------------------------------------------ - * Open a VTY_TERM listener socket. - * - * The sockaddr 'sa' may be NULL or of a different address family, in which - * case "any" address is used. - * - * If the sockaddr 'sa' is used, only the address portion is used. - * - * Returns: < 0 => failed - * == 0 => OK -- did not use the sockaddr 'sa'. - * > 1 => OK -- and did use the sockaddr 'sa' - */ -static int -uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) -{ - union sockunion su ; - int sock ; - int ret ; - - 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 - { - /* no address or wrong family -- set up empty sockunion of - * required family */ - sockunion_init_new(&su, family) ; - sa = NULL ; - } ; - - /* Open the socket and set its properties */ - sock = sockunion_socket(family, type, protocol) ; - if (sock < 0) - return -1 ; - - ret = sockopt_reuseaddr (sock); - - if (ret >= 0) - ret = sockopt_reuseport (sock); - - if (ret >= 0) - ret = set_nonblocking(sock); - -#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) - /* Want only IPV6 on ipv6 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) && (sa->sa_family == AF_INET6)) - { - int on = 1; - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); - } -#endif - - if (ret >= 0) - ret = sockunion_bind (sock, &su, port, sa) ; - - if (ret >= 0) - ret = sockunion_listen (sock, 3); - - if (ret < 0) - { - close (sock); - return -1 ; - } - - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_TERM) ; - - /* Return OK and signal whether used address or not */ - return (sa != NULL) ? 1 : 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Open a VTY_SHEL_SERV listener socket (UNIX domain). - * - * Returns: < 0 => failed - * >= 0 => OK - */ -static int -uty_serv_vtysh(const char *path) -{ - int ret; - int sock, sa_len, path_len ; - struct sockaddr_un sa_un ; - mode_t old_mask; - struct zprivs_ids_t ids; - - 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 - - old_mask = umask (0007); - - 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); - - if (ret >= 0) - ret = set_nonblocking(sock); - - if (ret >= 0) - { - ret = listen (sock, 5); - if (ret < 0) - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, - errtoa(errno, 0).str) ; - } ; - - zprivs_get_ids(&ids); - - if (ids.gid_vty > 0) - { - /* 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) ; - } - - umask (old_mask); - - /* Give up now if failed along the way */ - if (ret < 0) - { - close (sock) ; - return -1 ; - } ; - - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_SHELL_SERV) ; - - return 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Socket is open -- set a VTY listener going - * - * Note that the vyt_listener structure is passed to the accept action function. - */ -static void -uty_serv_start_listener(int fd, enum vty_type type) -{ - vty_listener listener ; - - listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); - - ssl_push(vty_listeners_list, listener, next) ; - uty_sock_init_new(&listener->sock, fd, listener) ; - - listener->type = type ; - - if (vty_cli_nexus) - listener->sock.action.read.qnexus = vty_accept_qnexus ; - else - listener->sock.action.read.thread = vty_accept_thread ; - - uty_sock_set_read(&listener->sock, on) ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action for the thread world -- create and dispatch VTY - */ -static int -vty_accept_thread(struct thread *thread) -{ - vty_listener listener = THREAD_ARG(thread) ; - int result ; - - VTY_LOCK() ; - - result = uty_accept(listener, THREAD_FD(thread)); - - uty_sock_set_read(&listener->sock, on) ; - - VTY_UNLOCK() ; - return result ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action for the qnexus world -- create and dispatch VTY - */ -static void -vty_accept_qnexus(qps_file qf, void* listener) -{ - VTY_LOCK() ; - - uty_accept(listener, qf->fd); - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV - */ -static int -uty_accept(vty_listener listener, int listen_sock) -{ - VTY_ASSERT_LOCKED() ; - - assert(listener->sock.fd == listen_sock) ; - - switch (listener->type) - { - case VTY_TERM: - return uty_accept_term(listener) ; - - case VTY_SHELL_SERV: - return uty_accept_shell_serv(listener) ; - - default: - zabort("unknown vty type") ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM - */ -static int -uty_accept_term(vty_listener listener) -{ - 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); - - if (sock_fd < 0) - { - 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 ; - } ; - - /* 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) - { - /* VTY's ipv6 accesslist apply. */ - struct access_list* acl ; - - 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); - - if (ret != 0) - { - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; - close (sock_fd); - return 0; - } ; - - /* 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 - */ -static int -uty_accept_shell_serv (vty_listener listener) -{ - int sock_fd ; - int ret ; - int client_len ; - struct sockaddr_un client ; - - VTY_ASSERT_LOCKED() ; - - client_len = sizeof(client); - memset (&client, 0, client_len); - - sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, - (socklen_t *) &client_len) ; - - if (sock_fd < 0) - { - uzlog (NULL, LOG_WARNING, "can't accept vty shell 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 ; - } ; - - /* All set -- create the VTY_SHELL_SERV */ - if (VTYSH_DEBUG) - printf ("VTY shell accept\n"); - - uty_new_shell_serv(sock_fd) ; - - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); - return 0; -} - -/*============================================================================== - * Reading from the VTY_SHELL_SERV type sock. - * - * The select/pselect call-back ends up in utysh_read_ready(). - */ - -/*------------------------------------------------------------------------------ - * Ready to read -> kicking the "SHELL_SERV CLI" - * - * End up here when there is something ready to be read. - * - * 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. - * - * Note that nothing is actually read here -- reading is done in the CLI itself, - * if required. - * - * The CLI decides whether to re-enable read, or enable write, or both. - */ -static void -utysh_read_ready(vty_io vio) -{ - uty_sock_set_read(&vio->sock, off) ; - - /* 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' - * Where is the command return code. - */ -} ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" - */ -static void -vtysh_read_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - utysh_read_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" - */ -static int -vtysh_read_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_read = NULL ; /* implicitly */ - utysh_read_ready(vio); - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * 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) - */ -extern int -utysh_read (vty_io vio, qstring cl, qstring buf) -{ - int get ; - char* cp ; - char* ep ; - size_t have ; - - 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' */ - - if (have > 0) /* take what have */ - { - qs_insert(cl, cp, have) ; - cl->cp += have ; - buf->cp += have ; - } ; - - if (ep != NULL) /* if found '\0' */ - { - qs_term(cl) ; /* '\0' terminate */ - ++buf->cp ; /* step past it */ - return 1 ; /* have a complete line <<<<<<<<<<<<< */ - } - } ; - - /* buffer is empty -- try and get some more stuff */ - assert(buf->len == buf->cp) ; - - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ - - qs_need(buf, 500) ; /* need a reasonable lump */ - qs_clear(buf) ; /* set cp = len = 0 */ - - 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 ; - - return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ - } ; - } ; -} ; - -/*============================================================================== - * 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 ?? - * - * 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. - */ - -/*------------------------------------------------------------------------------ - * Output logging information to all vty which are set to "monitor". - */ -extern void -uty_log(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va) -{ - 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 */ - - uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ - - 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) ; - } ; - } ; - - if (ret != 0) - /* need to prod */ ; - - if (ret >= 0) - vio->monitor_busy = 0 ; - } ; - - vio = sdl_next(vio, mon_list) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Async-signal-safe version of vty_log for fixed strings. - * - * This is last gasp operation. - */ -void -vty_log_fixed (const char *buf, size_t len) -{ - vty_io vio ; - - /* 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) ; - - vio = sdl_next(vio, mon_list) ; - } ; + return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ; } ; -- cgit v1.2.3