diff options
author | Chris Hall <chris.hall@highwayman.com> | 2011-02-13 22:53:14 +0000 |
---|---|---|
committer | Chris Hall <chris.hall@highwayman.com> | 2011-02-13 22:53:14 +0000 |
commit | 64be6d766a65dc0749d17f5023d714678e9c96a6 (patch) | |
tree | f97e04068600d0851687b58d25ff198d1eb6cc73 /lib/vty_command.c | |
parent | 8443ca08672e0cf5b779f59db9d556dadf763de7 (diff) | |
download | quagga-ex10p.tar.bz2 quagga-ex10p.tar.xz |
Initial commit to seed the "pipework" branchex10p
This is a major revision of the command processing, in order to support
new lexical level for command lines, plus all the necessary I/O
redirection for the pipes.
This is version 0.99.15ex10p.
This supports:
< filename
<+ filename
.... > filename
.... >> filename
.... >*
Also contains all lexical level handling of '...', "...." and \x in order
to allow use of '>' et al if required.
Updated command line completion and help is a work in progress.
Diffstat (limited to 'lib/vty_command.c')
-rw-r--r-- | lib/vty_command.c | 863 |
1 files changed, 863 insertions, 0 deletions
diff --git a/lib/vty_command.c b/lib/vty_command.c new file mode 100644 index 00000000..26a9ec6a --- /dev/null +++ b/lib/vty_command.c @@ -0,0 +1,863 @@ +/* VTY for command execution + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "misc.h" +#include "stdio.h" + +#include "vty_command.h" +#include "command_local.h" +#include "command_queue.h" +#include "command_execute.h" +#include "command_parse.h" +#include "vty_local.h" +#include "vty_io.h" +#include "vty_cli.h" +#include "vio_fifo.h" +#include "vty_io_file.h" +#include "list_util.h" +#include "qstring.h" + +/*============================================================================== + * Variables etc. + */ + +/*------------------------------------------------------------------------------ + * Prototypes + */ +static void uty_show_error_location(vio_vf vf) ; +static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ; + +/*============================================================================== + * There are two command loops -- cmd_read_config() and cq_process(). These + * functions support those command loops: + * + * * uty_cmd_prepare() + * + * After opening or closing a vin/vout object, the state of the execution + * context must be set to reflect the current top of the vin/vout stack. + * + * * uty_cmd_dispatch_line() + * + * This is for command lines which come from "push" sources, eg the + * Telnet VTY CLI or the VTYSH. + * + * Hands command line over to the vty_cli_nexus message queue. + * + * NB: from this point onwards, the vio is vio->cmd_running ! + * + * * vty_cmd_fetch_line() + * + * This is for command lines which are "pulled" by one of the command + * execution loops, eg files and pipes. If not in cmd_read_config()) + * can return CMD_WAITING. + * + * * vty_cmd_open_in_pipe_file() + * * vty_cmd_open_in_pipe_shell() + * * vty_cmd_open_out_pipe_file() + * * vty_cmd_open_out_pipe_shell() + * + * These do the I/O side of the required opens, pushing the new vty_vf + * onto the vin/vout stack. + * + * * vty_cmd_success() + * + * When a command returns success, this is called either to push all + * buffered output to the current vout, or to discard all buffered + * output (which is what cmd_read_config() does). + * + * If required, pops any vout(s). + * + * * vty_cmd_loop_exit() + * + * When a command has completed, successfully or otherwise, this is + * called to deal with any errors and return control to. + * + * + * + * + */ + +/*------------------------------------------------------------------------------ + * After opening or closing a vin/vout object, update the vty->exec context. + */ +extern void +uty_cmd_prepare(vty_io vio) +{ + cmd_exec exec = vio->vty->exec ; + + exec->parse_type = vio->vin->parse_type ; + + exec->out_enabled = vio->vout->out_enabled ; + exec->reflect_enabled = vio->vin->reflect_enabled && + vio->vout->out_enabled ; +} ; + +/*------------------------------------------------------------------------------ + * This pushes the command line into the vty_cli_message queue, where it will + * be parsed and executed etc. + * + * NB: from this moment on, vio->cmd_running is true, so even if the vty is + * closed, it cannot be fully closed and then reaped until the flag + * is cleared. + * + * While vio->cmd_running the command side is responsible for the vty + * and the vty->exec stuff. + */ +extern cmd_return_code_t +uty_cmd_dispatch(vty_io vio, cmd_do_t to_do, qstring line) +{ + VTY_ASSERT_LOCKED() ; + + vio->cmd_running = true ; + uty_cmd_prepare(vio) ; /* make sure */ + + cq_dispatch(vio->vty, to_do, line) ; + + return CMD_WAITING ; +} ; + +/*------------------------------------------------------------------------------ + * Handle a "special" command -- anything not cmd_do_command + */ +extern cmd_return_code_t +vty_cmd_special(vty vty) +{ + cmd_return_code_t ret ; + + ret = CMD_SUCCESS ; + /* All other nodes... */ + switch (vty->exec->to_do) + { + case cmd_do_nothing: + break ; + + case cmd_do_eof: + ret = CMD_CLOSE ; + break ; + + case cmd_do_command: + zabort("invalid cmd_do_command") ; + break ; + + case cmd_do_ctrl_d: + zabort("invalid cmd_do_ctrl_d") ; + break ; + + case cmd_do_ctrl_c: + case cmd_do_ctrl_z: + ret = cmd_end(vty) ; + break ; + + default: /* must now be cmd_do_auth or cmd_do_auth_enable */ + VTY_LOCK() ; + ret = uty_cli_auth(vty->vio->vin->cli) ; + VTY_UNLOCK() ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Fetch the next command line to be executed. + * + * If possible, get another command line to execute -- pops pipes as required + * and reflects command line when have one, if required. + * + * Action depends on the type of the current input: + * + * VIN_NONE invalid + * + * VIN_TERM ) return CMD_EOF + * VIN_VTYSH ) + * + * VIN_FILE ) + * VIN_PIPE ) fetch another line if can. + * VIN_CONFIG ) + * + * VIN_DEV_NULL nothing: return CMD_EOF + * + * Returns: CMD_SUCCESS => OK -- have a line ready to be processed. + * + * vty->exec->line points at the line. + * + * or: CMD_EOF => nothing to fetch -- note that if pops back to the + * VIN_TERM or VIN_VTYSH level, then will return + * CMD_EOF. + * + * If was VIN_TERM or VIN_VTYSH, signals CMD_EOF to + * exit from the command loop. + * + * or: CMD_WAITING => OK -- but waiting for command line to arrive. + * + * or: .... + */ +extern cmd_return_code_t +vty_cmd_fetch_line(vty vty) +{ + cmd_return_code_t ret ; + vty_io vio ; + qstring* p_line ; + + VTY_LOCK() ; + + vio = vty->vio ; /* once locked */ + p_line = &vty->exec->line ; + + *p_line = NULL ; + vty->exec->to_do = cmd_do_nothing ; + + while (1) + { + vio_vf vf ; + + vf = vio->vin ; + + if (vf->vin_state != vf_open) + { + switch (vf->vin_state) + { + case vf_closed: + case vf_eof: + ret = CMD_EOF ; + break ; + + case vf_error: + ret = CMD_IO_ERROR ; + break ; + + case vf_closing: + ret = CMD_CLOSE ; + break ; + + default: + zabort("invalid vf->vin_state") ; + break ; + } ; + break ; + } ; + + switch (vf->vin_type) + { + case VIN_NONE: + zabort("invalid VIN_NONE") ; + break ; + + case VIN_TERM: + case VIN_VTYSH: + case VIN_DEV_NULL: + ret = CMD_EOF ; + break ; + + /* fall through */ + + case VIN_FILE: + case VIN_PIPE: + case VIN_CONFIG: + ret = uty_file_fetch_command_line(vf, p_line) ; + + if (ret == CMD_SUCCESS) + { + vty->exec->to_do = cmd_do_command ; + } + else if ((ret == CMD_EOF) && (vio->vin_depth > 0)) + { + uty_vin_close(vio) ; + uty_cmd_prepare(vio) ; + continue ; + } ; + break ; + + default: + zabort("unknown vin_type") ; + } ; + + break ; + } ; + + VTY_UNLOCK() ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Reflect the command line to the current vio->obuf. + * + * Advances the end_mark past the reflected line, so that output (in particular + * error stuff) is separate. + */ +extern void +vty_cmd_reflect_line(vty vty) +{ + vio_fifo obuf ; + qstring line ; + + VTY_LOCK() ; + + obuf = vty->vio->obuf ; /* once locked */ + line = vty->exec->line ; + + vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ; + vio_fifo_put_byte(obuf, '\n') ; + + uty_out_accept(vty->vio) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Open the given file as in in pipe, if possible. + * + * Puts error messages to vty if fails. + */ +extern cmd_return_code_t +vty_cmd_open_in_pipe_file(vty vty, qstring name, bool reflect) +{ + cmd_return_code_t ret ; + + VTY_LOCK() ; + + ret = uty_file_read_open(vty->vio, name, reflect) ; + + if (ret == CMD_SUCCESS) + uty_cmd_prepare(vty->vio) ; + + VTY_UNLOCK() ; + + return ret ; +} ; + +extern cmd_return_code_t +vty_cmd_open_in_pipe_shell(vty vty) +{ + return CMD_SUCCESS ; +} ; + +extern cmd_return_code_t +vty_cmd_open_out_pipe_file(vty vty, qstring name, bool append) +{ + cmd_return_code_t ret ; + + VTY_LOCK() ; + + ret = uty_file_write_open(vty->vio, name, append) ; + + if (ret == CMD_SUCCESS) + uty_cmd_prepare(vty->vio) ; + + VTY_UNLOCK() ; + + return ret ; +} ; + +extern cmd_return_code_t +vty_cmd_open_out_dev_null(vty vty) +{ + vio_vf vf ; + + VTY_LOCK() ; + + vf = uty_vf_new(vty->vio, "dev_null", -1, vfd_none, vfd_io_none) ; + uty_vout_open(vty->vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ; + + vf->out_enabled = false ; + + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; + +extern cmd_return_code_t +vty_cmd_open_out_pipe_shell(vty vty) +{ + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Command has completed successfully. + * + * An output generated by the command is now pushed if exec->out_enabled, + * or discarded. + * + * If the vin_depth < vout_depth, then close vouts until the depth is equal. + */ +extern cmd_return_code_t +vty_cmd_success(vty vty) +{ + vty_io vio ; + + VTY_LOCK() ; + vio = vty->vio ; /* once locked */ + + if (!vio_fifo_empty_tail(vio->obuf)) + { + if (vty->exec->out_enabled) + uty_cmd_out_push(vio) ; + else + uty_out_clear(vio) ; + } ; + + /* Deal with closing out pipe on individual command */ + while (vio->vout_depth > vio->vin_depth) + { + uty_vout_close(vio, false) ; /* ignore failures TODO */ + uty_cmd_prepare(vio) ; /* set new state */ + } ; + + VTY_UNLOCK() ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * If there is anything after the end_mark, push it to be written, now. + * + * See uty_cmd_out_push() below. + */ +extern void +vty_cmd_out_push(vty vty) +{ + VTY_LOCK() ; + uty_cmd_out_push(vty->vio) ; + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * If there is anything after the end_mark, push it to be written, now. + * + * For most outputs we kick the I/O side so that the pselect processing deals + * with the required write. For VOUT_STDERR and VOUT_STDOUT, output goes + * directly to standard I/O. + * + * Advances the end_mark past the stuff pushed. + * + * NB: takes no notice of vf->out_enabled... this is used when outputting + * any error messages when reading the configuration file. + * + * TODO: worry about closing state ! + */ +extern void +uty_cmd_out_push(vty_io vio) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + vf = vio->vout ; + + assert(vio->obuf == vf->obuf) ; + + uty_out_accept(vio) ; /* advance the end mark */ + + if (vf->vout_state != vf_open) + { + switch (vf->vout_state) + { + case vf_closed: + case vf_eof: + ret = CMD_EOF ; /* TODO what's to do ? */ + break ; + + case vf_error: + ret = CMD_IO_ERROR ; + break ; + + case vf_closing: + ret = CMD_CLOSE ; /* TODO continue with output ? */ + break ; + + default: + zabort("invalid vf->vout_state") ; + break ; + } ; + + return ; /* TODO errors ?? */ + } ; + + switch (vio->vout->vout_type) + { + case VOUT_NONE: + zabort("invalid vout_none") ; + break ; + + case VOUT_TERM: + uty_cli_out_push(vf->cli) ; + break ; + + case VOUT_VTYSH: + /* Kick the writer */ + break ; + + case VOUT_FILE: + uty_file_out_push(vf) ; + break ; + + case VOUT_PIPE: + /* Kick the writer */ + break ; + + case VOUT_CONFIG: + /* Kick the writer */ + break ; + + case VOUT_DEV_NULL: + uty_out_clear(vio) ; /* clear fifo, keep end mark */ + break ; + + case VOUT_STDOUT: + vio_fifo_fwrite(vio->obuf, stdout) ; + break ; + + case VOUT_STDERR: + vio_fifo_fwrite(vio->obuf, stderr) ; + break ; + + default: + zabort("unknown vout_type") ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * The command loop has exited, with the given return code. + * + * There are a number of return codes that are treated as success. On any + * of those, does vty_cmd_success() (which has no effect if already done). + * + * If not a success return code, then close down any pipe-work, and create + * suitable error message. + * + * If is any form of success, returns CMD_SUCCESS -- otherwise returns the + * return code as given. + */ +extern cmd_return_code_t +vty_cmd_loop_exit(vty vty, cmd_return_code_t ret) +{ + bool close ; + + /* Deal with the several names of success and the many names of failure. + */ + close = false ; + + switch(ret) + { + case CMD_CLOSE: + case CMD_EOF: + close = true ; + fall_through ; + + case CMD_SUCCESS: + case CMD_EMPTY: + ret = vty_cmd_success(vty) ; + break ; + + default: + /* If not CMD_SUCCESS, the treat as some sort of error: + * + * * close down all pipes -- leaving just the vin_base/vout_base. + * * create error messages in the vout_base. + */ + vty_cmd_failed(vty, ret) ; + break ; + } ; + + /* They think it's all over... */ + + VTY_LOCK() ; + + vty->vio->cmd_running = false ; + + if (vty->vio->vin->vin_type == VIN_TERM) + uty_cli_done_command(vty->vio->vin->cli, vty->node) ; + + /* ... it is now. */ + + if (close) + uty_close(vty->vio, false, NULL) ; + + VTY_UNLOCK() ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Dealing with error of some kind. + * + * Error is reported on the vout_base. Whatever the command has written + * so far will be appended to the reporting of the error location. + * + * If there is only one vout, then: + * + * - if is VOUT_TERM, then need to wipe the command line. The failing + * line will be the last output line on screen (unless monitor output, + * which can do nothing about). + * + * - if is VOUT_VTYSH, then the failing line will be the last output line + * on the screen. + * + * - if is VOUT_xxx, then nothing will have been reflected. + * + * In any event, need to separate out any output from the failing command, + * so that can report error location and type, before showing the error + * message(s). + * + * NB: does not expect to see all the possible CMD_XXX return codes (see + * below), but treats all as a form of error ! + */ +static void +vty_cmd_failed(vty vty, cmd_return_code_t ret) +{ + vty_io vio ; + vio_fifo tbuf ; + ulen eloc ; + + VTY_LOCK() ; + vio = vty->vio ; /* once locked */ + + /* Copy command output to temporary, and remove from the obuf. + * + * Any command output should be messages explaining the error, and we want + * those (a) on the base vout, and (b) after description of where the error + * occurred. + */ + tbuf = vio_fifo_copy_tail(NULL, vio->obuf) ; + vio_fifo_back_to_end_mark(vio->obuf, true) ; + + /* Can now close everything in the vout stack, so all further output + * is to the vout_base. + */ + uty_vout_close_stack(vio, false) ; /* not "final" */ + + /* Process the vin stack to generate the error location(s) */ + uty_show_error_location(vio->vin) ; + + if (vio->vin->vin_type == VIN_TERM) + eloc = uty_cli_prompt_len(vio->vin->cli) ; + else + eloc = 0 ; + + /* Can now close everything in the vin stack */ + uty_vin_close_stack(vio) ; + + /* Now any additional error message if required */ + switch (ret) + { + qstring qs ; + + case CMD_WARNING: + if (vio_fifo_empty(tbuf)) + uty_out(vio, "%% WARNING: non-specific warning\n") ; + break ; + + case CMD_ERROR: + if (vio_fifo_empty(tbuf)) + uty_out(vio, "%% ERROR: non-specific error\n") ; + break ; + + case CMD_IO_ERROR: + break ; + + case CMD_ERR_PARSING: + qs = qs_set_fill(NULL, eloc + vio->vty->exec->parsed->eloc, "....") ; + uty_out(vio, "%s^\n%% %s\n", qs_string(qs), + vio->vty->exec->parsed->emess) ; + qs_reset(qs, free_it) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_out(vio, "%% Unknown command.\n") ; + break; + + case CMD_ERR_AMBIGUOUS: + uty_out(vio, "%% Ambiguous command.\n"); + break; + + case CMD_ERR_INCOMPLETE: + uty_out(vio, "%% Command incomplete.\n"); + break; + + case CMD_SUCCESS: + case CMD_SUCCESS_DAEMON: + case CMD_CLOSE: + case CMD_EMPTY: + case CMD_WAITING: + case CMD_EOF: + case CMD_COMPLETE_FULL_MATCH: + case CMD_COMPLETE_MATCH: + case CMD_COMPLETE_LIST_MATCH: + case CMD_COMPLETE_ALREADY: + default: + uty_out(vio, "%% Unexpected return code (%d).\n", (int)ret) ; + break ; + } ; + + /* Now stick the tbuf onto the end of the output & discard tbuf. */ + vio_fifo_copy(vio->obuf, tbuf) ; + vio_fifo_reset(tbuf, free_it) ; + + uty_cmd_out_push(vio) ; + + VTY_UNLOCK() ; +} ; + +static void +uty_show_error_location(vio_vf vf) +{ + vio_vf vf_next ; + + /* Recurse until hit end of the vin stack */ + vf_next = ssl_next(vf, vin_next) ; + + if (vf_next != NULL) + uty_show_error_location(vf_next) ; + else + assert(vf == vf->vio->vin_base) ; + + /* On the way back, add the error location for each vin entry */ + switch (vf->vin_type) + { + case VIN_NONE: + zabort("invalid VIN_NONE") ; + break ; + + case VIN_TERM: + /* Wipe the current command line */ +// uty_term_show_error_location(vf, loc) ; + break ; + + case VIN_VTYSH: + break ; + + case VIN_FILE: + + case VIN_PIPE: + + case VIN_CONFIG: + uty_out(vf->vio, "%% on line %d of %s:\n", vf->line_number, vf->name) ; + uty_out(vf->vio, "%s\n", qs_make_string(vf->cl)) ; + break ; + + case VIN_DEV_NULL: + break ; + + default: + zabort("unknown vin_type") ; + } ; +} ; + +/*============================================================================== + * Configuration node/state handling + * + * At most one VTY may hold the configuration symbol of power at any time. + */ + +/*------------------------------------------------------------------------------ + * Attempt to gain the configuration symbol of power + * + * If succeeds, set the given node. + * + * Returns: true <=> now own the symbol of power. + */ +extern bool +vty_config_lock (struct vty *vty, enum node_type node) +{ + bool result; + + VTY_LOCK() ; + + if (!host.config) + { + vty->config = true ; + host.config = true ; + } ; + + result = vty->config; + + if (result) + vty->node = node ; + + VTY_UNLOCK() ; + + return result; +} + +/*------------------------------------------------------------------------------ + * Give back the configuration symbol of power -- if own it. + * + * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + */ +extern void +vty_config_unlock (struct vty *vty, enum node_type node) +{ + VTY_LOCK() ; + uty_config_unlock(vty, node); + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Give back the configuration symbol of power -- if own it. + * + * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + */ +extern void +uty_config_unlock (struct vty *vty, node_type_t node) +{ + VTY_ASSERT_LOCKED() ; + + if (host.config && vty->config) + { + vty->config = false ; + host.config = false ; + } + + assert(node <= MAX_NON_CONFIG_NODE) ; + vty->node = node ; +} ; + + + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------------ + * Result of command is to close the input. + * + * Posts the reason for the close. + * + * Returns: CMD_CLOSE + */ +extern cmd_return_code_t +uty_cmd_close(vty_io vio, const char* reason) +{ + vio->close_reason = qs_set(NULL, reason) ; + return CMD_CLOSE ; +} ; + + |