diff options
Diffstat (limited to 'lib/command_execute.c')
-rw-r--r-- | lib/command_execute.c | 524 |
1 files changed, 331 insertions, 193 deletions
diff --git a/lib/command_execute.c b/lib/command_execute.c index cbf260b9..32698d94 100644 --- a/lib/command_execute.c +++ b/lib/command_execute.c @@ -40,135 +40,226 @@ /*------------------------------------------------------------------------------ * Construct cmd_exec object and initialise. * - * The following are set by uty_cmd_prepare() which is called after something - * has been pushed/popped on the vin/vout stacks, and before any command - * execution starts: + * This is done when a command loop is entered. * - * - parse_type - * - reflect_enabled - * - out_enabled - * - * to reflect the then current VTY state. + * The initial cmd_context reflects the vty->type and initial vty->node. */ extern cmd_exec cmd_exec_new(vty vty) { - cmd_exec exec ; + cmd_exec exec ; + cmd_context context ; exec = XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_exec)) ; /* Zeroising has set: * - * vty = X -- set below + * vty = X -- set below * - * line = NULL -- no command line, yet - * to_do = cmd_do_nothing + * action = all zeros * - * parse_type = cmd_parse_standard + * context = NULL -- see below * - * reflect_enabled = false -- not enabled - * out_enabled = false -- not enabled + * parsed = NULL -- see below * - * parsed all zeros -- empty parsed object (embedded) + * password_failures = 0 -- none, yet * - * state = exec_null - * locus = NULL -- not significant in exec_null - * ret = CMD_SUCCESS + * state = exec_null + * locus = NULL -- not significant in exec_null + * ret = CMD_SUCCESS * - * cq = NULL -- no mqb (qpthreads) - * -- no thread (legacy thread) + * cq = NULL -- no mqb (qpthreads) + * -- no thread (legacy thread) */ - confirm(cmd_do_nothing == 0) ; - confirm(cmd_parse_standard == 0) ; - confirm(CMD_PARSED_INIT_ALL_ZEROS) ; - confirm(exec_null == 0) ; - confirm(CMD_SUCCESS == 0) ; + confirm(CMD_ACTION_ALL_ZEROS) ; /* action */ + confirm(exec_null == 0) ; /* state */ + confirm(CMD_SUCCESS == 0) ; /* ret */ + + exec->vty = vty ; + + exec->parsed = cmd_parsed_new() ; + + /* Initialise the context */ + VTY_ASSERT_LOCKED() ; + + exec->context = context = cmd_context_new() ; + + context->node = vty->node ; + + context->parse_execution = true ; + context->parse_only = false ; + context->reflect_enabled = false ; + + context->onode = NULL_NODE ; + + context->dir_cd = qpath_dup(host.cwd) ; + context->dir_home = qpath_dup(host.config_dir) ; + + switch (vty->type) + { + case VTY_TERMINAL: + context->full_lex = true ; + + context->parse_strict = false ; + context->parse_no_do = false ; + context->parse_no_tree = false ; + + context->can_enable = context->node >= ENABLE_NODE ; + context->can_auth_enable = true ; + + context->dir_here = qpath_dup(context->dir_cd) ; + + break ; + + case VTY_SHELL_SERVER: + zabort("missing VTY_SHELL_SERVER context") ; + + context->full_lex = true ; + + context->parse_strict = false ; + context->parse_no_do = false ; + context->parse_no_tree = false ; + + context->can_enable = true ; + context->can_auth_enable = false ; - exec->vty = vty ; + context->dir_here = qpath_dup(context->dir_cd) ; + + break ; + + case VTY_CONFIG_READ: + context->full_lex = false ; + + context->parse_strict = true ; + context->parse_no_do = true ; + context->parse_no_tree = false ; + + context->can_enable = true ; + context->can_auth_enable = false ; + + context->dir_here = qpath_dup(context->dir_home) ; + + break ; + + default: + zabort("vty type unknown to cmd_exec_new()") ; + } ; return exec ; } ; /*------------------------------------------------------------------------------ - * Destroy cmd_exec object. + * Make a new context -- returns a completely empty context object. */ -extern cmd_exec -cmd_exec_free(cmd_exec exec) +extern cmd_context +cmd_context_new(void) { - cmd_parsed_reset(exec->parsed, keep_it) ; + return XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_context)) ; +} ; +/*------------------------------------------------------------------------------ + * Save current context and update for new, child vin. + * + * - updates the dir_here if required + * + * - cannot inherit can_enable unless is ENABLE_NODE or better + * + * - cannot inherit can_auth_enable, no how + */ +extern cmd_context +cmd_context_new_save(cmd_context context, qpath file_here) +{ + cmd_context saved ; + saved = cmd_context_new() ; - XFREE(MTYPE_CMD_EXEC, exec) ; + *saved = *context ; /* copy as is */ - return NULL ; -} ; + /* The saved copy of the context now owns the current paths, so now need + * to duplicate (or set new) paths. + */ + context->dir_cd = qpath_dup(saved->dir_cd) ; + context->dir_home = qpath_dup(saved->dir_home) ; + + if (file_here == NULL) + context->dir_here = qpath_dup(saved->dir_here) ; + else + { + context->dir_here = qpath_dup(file_here) ; + qpath_shave(context->dir_here) ; + } ; + /* The inheritance of can_enable is tricky. Will not bequeath can_enable + * is is not already in ENABLE_NODE or better ! + */ + if (context->node <= ENABLE_NODE) + context->can_enable = false ; -/*============================================================================== - * - */ + context->can_auth_enable = false ; + + return saved ; +} ; /*------------------------------------------------------------------------------ - * Set new node. + * Restore given context -- frees the copy restored from. * - * If old node >= CONFIG_NODE, and new node < CONFIG_NODE, give up the config - * symbol of power. + * Has to free the directories in the context being restored to. * - * Returns: CMD_SUCCESS -- OK - * CMD_CLOSE -- if new node == NODE_NULL + * Returns NULL. */ -static cmd_return_code_t -cmd_set_node(vty vty, node_type_t node) +extern cmd_context +cmd_context_restore(cmd_context dst, cmd_context src) { - if ((vty->node >= MIN_CONFIG_NODE) && (node < MIN_CONFIG_NODE)) - vty_config_unlock(vty, node) ; - else - vty->node = node ; + assert(src != NULL) ; + + qpath_free(dst->dir_cd) ; + qpath_free(dst->dir_home) ; + qpath_free(dst->dir_here) ; + + *dst = *src ; /* copy as is */ - return (vty->node != NULL_NODE) ? CMD_SUCCESS : CMD_CLOSE ; + return cmd_context_free(src, true) ; } ; /*------------------------------------------------------------------------------ - * Command line "end" command + * Free the given context. * - * Falls back to the current node's end_to node. If leaves configuration - * mode, give away the configuration symbol of power. - * - * Generally, for all configuration nodes end -> NODE_ENABLE (releasing the - * configuration lock), and all other nodes end does nothing. - * - * Returns: CMD_SUCCESS -- OK - * CMD_CLOSE -- if new node == NODE_NULL + * If the context is a copy of an existing context, then must not free the + * directories -- they will be freed when that existing context is freed. */ -extern cmd_return_code_t -cmd_end(vty vty) +extern cmd_context +cmd_context_free(cmd_context context, bool copy) { - return cmd_set_node(vty, cmd_node_end_to(vty->node)) ; + if (context != NULL) + { + if (!copy) + { + qpath_free(context->dir_cd) ; + qpath_free(context->dir_home) ; + qpath_free(context->dir_here) ; + } ; + + XFREE(MTYPE_CMD_EXEC, context) ; /* sets context = NULL */ + } ; + + return context ; } ; /*------------------------------------------------------------------------------ - * Command line "exit" command -- aka "quit" - * - * Falls back to the current node's exit_to node. If leaves configuration - * mode, give away the configuration symbol of power. - * - * Generally: - * - * - for all configuration nodes > NODE_CONFIG exit -> parent node. - * - * - for NODE_CONFIG exit -> ENABLE_NODE (and release configuration symbol - * of power) - * - * - for all nodes < NODE_CONFIG -> close the VTY - * - * Returns: CMD_SUCCESS -- OK - * CMD_CLOSE -- if new node == NODE_NULL + * Destroy cmd_exec object. */ -extern cmd_return_code_t -cmd_exit(vty vty) +extern cmd_exec +cmd_exec_free(cmd_exec exec) { - return cmd_set_node(vty, cmd_node_exit_to(vty->node)) ; + if (exec != NULL) + { + exec->parsed = cmd_parsed_free(exec->parsed) ; + exec->context = cmd_context_free(exec->context, false) ; /* not a copy */ + + XFREE(MTYPE_CMD_EXEC, exec) ; + } ; + + return NULL ; } ; /*============================================================================== @@ -236,16 +327,12 @@ cmd_execute_command(struct vty *vty, * execution is converted to CMD_SUCCESS. Note that any CMD_WARNING returned * by command parsing (or in execution of any default 'first_cmd'). * - * Returns: cmd_return_code for last command - * vty->buf is last line processed - * vty->lineno is number of last line processed (1 is first) - * - * If the file is empty, will return CMD_SUCCESS. + * Returns: cmd_return_code for last command or I/O operation * - * If + * when returns, the entire vin/vout stack will have been closed. * - * If return code is not CMD_SUCCESS, the the output buffering contains the - * output from the last command attempted. + * If reaches EOF on the config file, returns CMD_SUCCESS. If anything else + * happens, will generate an error message and exits the command loop. */ extern cmd_return_code_t cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) @@ -254,89 +341,122 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) cmd_parsed parsed = exec->parsed ; cmd_return_code_t ret; + ret = CMD_SUCCESS ; /* so far, so good */ + while (1) { - /* Need a command line, pops pipes as required */ - ret = vty_cmd_fetch_line(vty) ; /* sets exec->line */ + /* Deal with anything which is not success ! + */ + if ((ret != CMD_SUCCESS) && (ret != CMD_EMPTY)) + { + /* Will drop straight out of the loop if have anything other + * than CMD_HIATUS, CMD_EOF or CMD_CLOSE, which are all signals + * that some adjustment to the vin/vout stacks is required, + * or that we are all done here. + * + * Everything else is deemed to be an error that stops the + * command loop. + */ + if ((ret != CMD_HIATUS) && (ret != CMD_EOF) && (ret != CMD_CLOSE)) + break ; + + ret = vty_cmd_hiatus(vty, ret) ; + /* for CMD_EOF & CMD_HIATUS only */ + if (ret == CMD_EOF) + return CMD_SUCCESS ; /* eof on the config file is + the expected outcome ! */ + if (ret != CMD_SUCCESS) + break ; + } ; + + /* If all is well, need another command line */ + + ret = vty_cmd_fetch_line(vty) ; /* sets exec->action */ if (ret != CMD_SUCCESS) - break ; /* stop on any and all problems */ + continue ; /* Parse the command line we now have */ - cmd_tokenise(parsed, exec->line) ; - ret = cmd_parse_command(parsed, vty->node, - exec->parse_type | cmd_parse_execution) ; + assert(exec->action->to_do == cmd_do_command) ; - if (ret == CMD_EMPTY) - continue ; /* easy if empty line */ + cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ; + ret = cmd_parse_command(parsed, exec->context) ; if (ret != CMD_SUCCESS) - break ; /* stop on *any* parsing issue */ + continue ; /* Special handling before first active line. */ if (first_cmd != NULL) { if (first_cmd != parsed->cmd) { - ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; + ret = (*first_cmd->func)(first_cmd, vty, -1, NULL) ; if (ret != CMD_SUCCESS) - break ; /* stop on *any* issue with "default" */ + continue ; } ; first_cmd = NULL ; } ; /* reflection now..... */ - if (exec->reflect_enabled) - vty_cmd_reflect_line(vty) ; + if (exec->reflect) + { + ret = vty_cmd_reflect_line(vty) ; + if (ret != CMD_SUCCESS) + continue ; + } ; /* Pipe work, if any */ if ((parsed->parts & cmd_parts_pipe) != 0) { ret = cmd_open_pipes(vty) ; if (ret != CMD_SUCCESS) - break ; + continue ; } ; /* Command execution, if any */ if ((parsed->parts & cmd_part_command) != 0) - { - ret = cmd_execute(vty) ; + ret = cmd_execute(vty) ; - if (ret != CMD_SUCCESS) - { - /* If ignoring warnings, treat CMD_WARNING as CMD_SUCCESS */ - if (ignore_warning && (ret == CMD_WARNING)) - ret = CMD_SUCCESS ; - - /* Treat CMD_CLOSE as CMD_SUCCESS */ - else if (ret == CMD_CLOSE) - ret = CMD_SUCCESS ; - - /* Everything else -> stop */ - else - break ; - } ; - } ; - - /* When we get here the last command was CMD_SUCCESS ! - * (Or CMD_WARNING and ignore_warning.) - * - * Deals with closing out pipe(s) if required. - */ - vty_cmd_success(vty) ; + /* Deal with success (or suppressed warning). */ + if ((ret == CMD_SUCCESS) || ((ret == CMD_WARNING) && ignore_warning)) + ret = vty_cmd_success(vty) ; } ; - /* Deal with any errors */ - if (ret == CMD_EOF) - return CMD_SUCCESS ; + /* Arrives here if: + * + * - vty_cmd_fetch_line() returns anything except CMD_SUCCESS, CMD_EOF or + * CMD_HIATUS -- which are not errors. + * + * - any other operation returns anything except CMD_SUCCESS + * (or CMD_WARNING, if they are being ignored), CMD_EOF or CMD_CLOSE. + * + * Deal with any errors -- generate suitable error messages and close back + * to (but excluding) vout_base. + * + * CMD_SUCCESS and CMD_EMPTY are impossible at this point -- they should + * have been dealt with in the loop. + * + * CMD_EOF is also impossible -- vty_cmd_fetch_line() or vty_cmd_hiatus() + * can return that, but that will have been dealt with. + * + * CMD_CLOSE is also impossible -- commands can return that, but that will + * have been dealt with. + * + * CMD_WAITING is not valid for blocking vio ! + */ + assert(ret != CMD_SUCCESS) ; + assert(ret != CMD_EMPTY) ; + assert(ret != CMD_EOF) ; + assert(ret != CMD_CLOSE) ; + assert(ret != CMD_WAITING) ; + + vty_cmd_hiatus(vty, ret) ; return ret ; } ; /*============================================================================== */ -static qstring cmd_get_pipe_file_name(cmd_parsed parsed, uint ti, uint nt) ; - /*------------------------------------------------------------------------------ * Open in and/or out pipes @@ -352,109 +472,127 @@ cmd_open_pipes(vty vty) cmd_exec exec = vty->exec ; cmd_parsed parsed = exec->parsed ; cmd_return_code_t ret ; + vty_io vio ; + + VTY_LOCK() ; + vio = vty->vio ; ret = CMD_SUCCESS ; + uty_cmd_depth_mark(vio) ; /* about to push vin and/or vout */ + /* Deal with any in pipe stuff */ if ((parsed->parts & cmd_part_in_pipe) != 0) { - if ((parsed->in_pipe & cmd_pipe_file) != 0) - { - qstring name ; - name = cmd_get_pipe_file_name(parsed, parsed->first_in_pipe, - parsed->num_in_pipe) ; + qstring args ; - ret = vty_cmd_open_in_pipe_file(vty, name, - (parsed->in_pipe & cmd_pipe_reflect) != 0) ; + args = cmd_tokens_concat(parsed, parsed->first_in_pipe, + parsed->num_in_pipe) ; - qs_reset(name, free_it) ; - } + if ((parsed->in_pipe & cmd_pipe_file) != 0) + ret = uty_cmd_open_in_pipe_file(vio, exec->context, args, + parsed->in_pipe) ; else if ((parsed->in_pipe & cmd_pipe_shell) != 0) - { - } + ret = uty_cmd_open_in_pipe_shell(vio, exec->context, args, + parsed->in_pipe) ; else zabort("invalid in pipe state") ; - if (ret != CMD_SUCCESS) - return ret ; + qs_free(args) ; } ; /* Deal with any out pipe stuff */ - if ((parsed->parts & cmd_part_out_pipe) != 0) + if (((parsed->parts & cmd_part_out_pipe) != 0) && (ret == CMD_SUCCESS)) { - if ((parsed->out_pipe & cmd_pipe_file) != 0) - { - qstring name ; - name = cmd_get_pipe_file_name(parsed, parsed->first_out_pipe, - parsed->num_out_pipe) ; + qstring args ; - ret = vty_cmd_open_out_pipe_file(vty, name, - ((parsed->out_pipe & cmd_pipe_append) != 0)) ; + args = cmd_tokens_concat(parsed, parsed->first_out_pipe, + parsed->num_out_pipe) ; - qs_reset(name, free_it) ; - } + if ((parsed->out_pipe & cmd_pipe_file) != 0) + ret = uty_cmd_open_out_pipe_file(vio, exec->context, args, + parsed->out_pipe) ; else if ((parsed->out_pipe & cmd_pipe_shell) != 0) - { - ret = vty_cmd_open_out_dev_null(vty) ; - } + ret = uty_cmd_open_out_pipe_shell(vio, exec->context, args, + parsed->out_pipe) ; else if ((parsed->out_pipe & cmd_pipe_dev_null) != 0) - { - ret = vty_cmd_open_out_dev_null(vty) ; - } + ret = uty_cmd_open_out_dev_null(vio) ; else zabort("invalid out pipe state") ; - if (ret != CMD_SUCCESS) - return ret ; + qs_free(args) ; } ; - return CMD_SUCCESS ; + VTY_UNLOCK() ; + return ret ; } ; /*------------------------------------------------------------------------------ - * Get pipe file name + * Command Execution. * - * Returns a brand new qstring that must be discarded after use. + * If !parse_only, set vty->node and dispatch the command. * - * Pro tem this just gets the token value !! TODO - */ -static qstring -cmd_get_pipe_file_name(cmd_parsed parsed, uint ti, uint nt) -{ - cmd_token t ; - - assert(nt == 2) ; - - t = cmd_token_get(parsed->tokens, ti + 1) ; - - return qs_copy(NULL, t->qs) ; -} ; - -/*------------------------------------------------------------------------------ - * Command Execution + * Returns: CMD_SUCCESS -- it all want very well + * CMD_WARNING -- not so good: warning message sent by vty_out() + * CMD_ERROR -- not so good: warning message sent by vty_out() + * CMD_CLOSE -- close the current input * - * Returns: + * NB: the distinction between CMD_WARNING and CMD_ERROR is that CMD_WARNING + * may be ignored when reading a configuration file. * - * - have command ready for execution -- CMD_SUCCESS - * - reached end of command stream -- CMD_CLOSE - * - encounter error of some kind -- CMD_WARNING, CMD_ERROR, etc + * NB: no other returns are acceptable ! */ extern cmd_return_code_t cmd_execute(vty vty) { - cmd_parsed parsed = vty->exec->parsed ; - cmd_command cmd = parsed->cmd ; + cmd_parsed parsed = vty->exec->parsed ; + cmd_context context = vty->exec->context ; + cmd_command cmd = parsed->cmd ; + cmd_return_code_t ret ; - node_type_t onode ; - onode = vty->node ; vty->node = parsed->cnode ; - ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed), - cmd_arg_vector_argv(parsed)) ; + if (context->parse_only) + ret = CMD_SUCCESS ; + else + ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed), + cmd_arg_vector_argv(parsed)) ; + + if (ret == CMD_SUCCESS) + { + /* If the node is changed by the command, do that now and make sure + * that the configuration symbol of power is straight. + * + * If the new node is >= CONFIG_NODE, then MUST already have acquired + * the symbol of power (otherwise the command would have failed !) + * + * If the new node is < CONFIG_NODE, then we will here release the + * symbol of power iff we are at the vin_base ! + * + * If the new node is NULL_NODE, then treat as CMD_CLOSE. + */ + if (context->node != parsed->nnode) + { + context->node = parsed->nnode ; + vty_cmd_config_lock_check(vty, context->node) ; + + if (context->node == NULL_NODE) + ret = CMD_CLOSE ; + } ; - if (((parsed->parts & cmd_part_do) != 0) && (vty->node == ENABLE_NODE)) - vty->node = onode ; + /* The command should (no longer) change the vty->node, but if it does, + * it had better be to the same as what the parser expected -- for if + * not, that will break "parse_only" and generally cause confusion. + */ + qassert((vty->node == parsed->cnode) || (vty->node == parsed->nnode)) ; + } + else + { + /* Enforce restrictions on return codes. */ + assert((ret == CMD_WARNING) || (ret == CMD_ERROR) + || (ret == CMD_CLOSE)) ; + } ; return ret ; } ; |