diff options
-rwxr-xr-x | configure.ac | 2 | ||||
-rw-r--r-- | lib/command.c | 7 | ||||
-rw-r--r-- | lib/command_common.h | 2 | ||||
-rw-r--r-- | lib/command_execute.c | 41 | ||||
-rw-r--r-- | lib/command_execute.h | 3 | ||||
-rw-r--r-- | lib/command_parse.c | 12 | ||||
-rw-r--r-- | lib/command_parse.h | 27 | ||||
-rw-r--r-- | lib/command_queue.c | 353 | ||||
-rw-r--r-- | lib/vty_cli.c | 777 | ||||
-rw-r--r-- | lib/vty_cli.h | 84 | ||||
-rw-r--r-- | lib/vty_command.c | 102 | ||||
-rw-r--r-- | lib/vty_io_term.c | 267 | ||||
-rw-r--r-- | lib/vty_io_term.h | 6 |
13 files changed, 905 insertions, 778 deletions
diff --git a/configure.ac b/configure.ac index 24c8950f..8b343086 100755 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,7 @@ ## $Id$ AC_PREREQ(2.53) -AC_INIT(Quagga, 0.99.18ex18b, [http://bugzilla.quagga.net]) +AC_INIT(Quagga, 0.99.18ex19b, [http://bugzilla.quagga.net]) AC_CONFIG_SRCDIR(lib/zebra.h) AC_CONFIG_MACRO_DIR([m4]) diff --git a/lib/command.c b/lib/command.c index ee855360..359ab0f1 100644 --- a/lib/command.c +++ b/lib/command.c @@ -994,9 +994,10 @@ DEFUN_CALL (show_version, { VTY_LOCK() ; - uty_out (vty->vio, "Quagga %s (%s).\n", QUAGGA_VERSION, - (host.name != NULL) ? host.name : "") ; - uty_out (vty->vio, "%s\n", QUAGGA_COPYRIGHT); + uty_out (vty->vio, QUAGGA_PROGNAME " %s (%s%s).\n", QUAGGA_VERSION, + (host.name != NULL) ? host.name : "", + vty_multi_nexus ? " pthreaded" : "") ; + uty_out (vty->vio, "%s\n", QUAGGA_COPYRIGHT) ; VTY_UNLOCK() ; diff --git a/lib/command_common.h b/lib/command_common.h index 4dd26753..501ebc3d 100644 --- a/lib/command_common.h +++ b/lib/command_common.h @@ -124,8 +124,6 @@ enum cmd_return_code /* Return codes from the command parser */ - CMD_EMPTY, /* parser: nothing to execute */ - CMD_ERR_PARSING, /* parser: general parser error */ CMD_ERR_NO_MATCH, /* parser: command/argument not recognised */ CMD_ERR_AMBIGUOUS, /* parser: more than on command matches */ diff --git a/lib/command_execute.c b/lib/command_execute.c index cf04bb31..bd6b6249 100644 --- a/lib/command_execute.c +++ b/lib/command_execute.c @@ -313,7 +313,7 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) { /* Deal with anything which is not success ! */ - if ((ret != CMD_SUCCESS) && (ret != CMD_EMPTY)) + if (ret != CMD_SUCCESS) { /* Will drop straight out of the loop if have anything other * than CMD_HIATUS, CMD_EOF or CMD_CLOSE, which are all signals @@ -335,22 +335,25 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) break ; } ; - /* If all is well, need another command line */ - + /* All is well, need another command line + */ ret = vty_cmd_fetch_line(vty) ; /* sets exec->action */ if (ret != CMD_SUCCESS) continue ; - /* Parse the command line we now have */ - assert(exec->action->to_do == cmd_do_command) ; + /* Parse the command line we now have -- loop if failed or there is + * nothing to execute. + */ + qassert(exec->action->to_do == cmd_do_command) ; cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ; ret = cmd_parse_command(parsed, exec->context) ; - if (ret != CMD_SUCCESS) + if ( (ret != CMD_SUCCESS) || ((parsed->parts & cmd_parts_execute) == 0) ) continue ; - /* Special handling before first active line. */ + /* Special handling before first active line. + */ if (first_cmd != NULL) { if (first_cmd != parsed->cmd) @@ -362,7 +365,8 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) first_cmd = NULL ; } ; - /* reflection now..... */ + /* reflection now..... + */ if (exec->reflect) { ret = vty_cmd_reflect_line(vty) ; @@ -370,19 +374,23 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) continue ; } ; - /* Pipe work, if any */ + /* Pipe work, if any + */ if ((parsed->parts & cmd_parts_pipe) != 0) { ret = cmd_open_pipes(vty) ; - if (ret != CMD_SUCCESS) + if ((ret != CMD_SUCCESS) || ((parsed->parts & cmd_part_command) == 0)) continue ; } ; - /* Command execution, if any */ - if ((parsed->parts & cmd_part_command) != 0) - ret = cmd_execute(vty) ; + /* Command execution + */ + qassert((parsed->parts & cmd_part_command) != 0) ; + + ret = cmd_execute(vty) ; - /* Deal with success (or suppressed warning). */ + /* Deal with success (or suppressed warning). + */ if ((ret == CMD_SUCCESS) || ((ret == CMD_WARNING) && ignore_warning)) ret = vty_cmd_success(vty) ; } ; @@ -398,8 +406,8 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) * 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_SUCCESS is 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. @@ -410,7 +418,6 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) * CMD_WAITING is not valid for blocking vio ! */ qassert(ret != CMD_SUCCESS) ; - qassert(ret != CMD_EMPTY) ; qassert(ret != CMD_EOF) ; qassert(ret != CMD_CLOSE) ; qassert(ret != CMD_WAITING) ; diff --git a/lib/command_execute.h b/lib/command_execute.h index 97033b6e..df86154c 100644 --- a/lib/command_execute.h +++ b/lib/command_execute.h @@ -44,7 +44,8 @@ enum cmd_exec_state exec_open_pipes, /* open pipes on command line */ exec_execute, /* execute standard command */ exec_special, /* execute special command */ - exec_done_cmd, /* command has completed */ + exec_cmd_done, /* command completed somehow */ + exec_cmd_success, /* command completed successfully */ exec_hiatus, /* while issues are dealt with */ exec_stopped, /* command loop has stopped */ } ; diff --git a/lib/command_parse.c b/lib/command_parse.c index 9204db70..54185428 100644 --- a/lib/command_parse.c +++ b/lib/command_parse.c @@ -3247,10 +3247,9 @@ static node_type_t cmd_auth_specials(cmd_context context, node_type_t target) ; * * - parsed->parts is what was found * - * - parsed->cmd->daemon => daemon + * NB: may be empty or comment only ! * - * CMD_EMPTY => line is empty, except perhaps for comment - * (iff parsing for execution) + * - parsed->cmd->daemon => daemon * * CMD_ERR_INCOMPLETE => "do" and nothing more * (iff parsing for execution) @@ -3317,13 +3316,10 @@ cmd_parse_command(cmd_parsed parsed, cmd_context context) */ if (((parsed->parts & cmd_part_command) == 0) && context->parse_execution) { - if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none) - return CMD_EMPTY ; /* accept empty */ - if ((parsed->parts & cmd_part_do) != 0) - return CMD_ERR_INCOMPLETE ; /* reject "do" alone */ + return CMD_ERR_INCOMPLETE ; /* reject "do" alone */ - return CMD_SUCCESS ; /* accept pipes */ + return CMD_SUCCESS ; /* accept pipes and empty */ } ; /* Level 2 parsing diff --git a/lib/command_parse.h b/lib/command_parse.h index ab93f061..42fac7c0 100644 --- a/lib/command_parse.h +++ b/lib/command_parse.h @@ -476,15 +476,18 @@ enum cmd_pipe_type /* bit significant */ cmd_pipe_file = BIT(0), cmd_pipe_shell = BIT(1), - cmd_pipe_dev_null = BIT(2), /* out pipe only -- black hole */ + cmd_pipe_dev_null = BIT(2), /* out pipe only -- black hole */ - /* For in pipes */ - cmd_pipe_reflect = BIT(4), /* + option */ + /* For in pipes + */ + cmd_pipe_reflect = BIT(4), /* + option */ - /* For out file pipes */ + /* For out file pipes + */ cmd_pipe_append = BIT(4), /* >> */ - /* For out shell pipes */ + /* For out shell pipes + */ cmd_pipe_shell_cmd = BIT(4), /* | at start of line */ } ; typedef enum cmd_pipe_type cmd_pipe_type_t ; @@ -494,15 +497,17 @@ enum cmd_parts /* bit significant */ { cmd_parts_none = 0, - cmd_part_do = BIT(0), - cmd_part_command = BIT(1), + cmd_part_do = BIT(0), /* command has leading "do" */ + cmd_part_command = BIT(1), /* command part exists */ + + cmd_part_in_pipe = BIT(2), /* in pipe part exists */ + cmd_part_out_pipe = BIT(3), /* out pipe part exists */ - cmd_part_in_pipe = BIT(2), - cmd_part_out_pipe = BIT(3), + cmd_parts_pipe = cmd_part_in_pipe | cmd_part_out_pipe, - cmd_parts_pipe = (cmd_part_in_pipe | cmd_part_out_pipe), + cmd_parts_execute = cmd_part_command | cmd_parts_pipe, - cmd_part_comment = BIT(4), + cmd_part_comment = BIT(4), /* commend part exists */ } ; typedef enum cmd_parts cmd_parts_t ; diff --git a/lib/command_queue.c b/lib/command_queue.c index b80b2c9f..5c2f7c8c 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -118,8 +118,8 @@ static void cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) ; * * Expects the vty start up process to have output some cheerful messages, * which is treated as a dummy "login" command. So the loop is entered at - * "exec_done_cmd", which will push out the output, deal with the return code, - * and loop round to fetch the first command line (if required). + * "exec_cmd_done", which will deal with the return code, push any output, and + * loop round to fetch the first command line (if required). */ extern void cq_loop_enter(vty vty, cmd_return_code_t ret) @@ -128,7 +128,7 @@ cq_loop_enter(vty vty, cmd_return_code_t ret) qassert(vty->exec->state == exec_null) ; - cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ; + cq_enqueue(vty, vty_cli_nexus, exec_cmd_done, ret) ; } ; /*------------------------------------------------------------------------------ @@ -292,152 +292,210 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) */ while (1) { - switch(state) - { - /*-------------------------------------------------------------------- - * Should not get here in exec_null state. - */ - case exec_null: - zabort("exec state == exec_null") ; - break ; + switch(state) + { + /*-------------------------------------------------------------------- + * Should not get here in exec_null state. + */ + case exec_null: + zabort("exec state == exec_null") ; + return ; + + /*-------------------------------------------------------------------- + * Hiatus state -- some return code to be dealt with ! + * + * NB: we only reach here after breaking out of the switch, or + * on cq_continue(). + */ + case exec_hiatus: + while (1) + { + /* Let vty_cmd_hiatus() deal with the return code and/or any + * stop/error/etc trap, and adjust the stack as required. + */ + ret = vty_cmd_hiatus(vty, ret) ; - /*-------------------------------------------------------------------- - * Need another command to execute => in_pipe ! - * - * If multi-threaded: may be in either thread. If vty_cmd_fetch_line() - * cannot, for any reason, return a command line, will return something - * other than CMD_SUCCESS -- which enters the exec_hiatus. - */ - case exec_fetch: - ret = vty_cmd_fetch_line(vty) ; + if (ret == CMD_SUCCESS) + break ; /* back to exec_fetch */ - if (ret != CMD_SUCCESS) - break ; + if (ret == CMD_WAITING) + return ; /* <<< DONE, pro tem */ - if (exec->action->to_do != cmd_do_command) - { - state = exec_special ; - continue ; - } ; + if (ret == CMD_IO_ERROR) + continue ; /* give error back to hiatus */ - fall_through ; /* with ret == CMD_SUCCESS */ + if (ret == CMD_STOP) + { + exec->state = exec_stopped ; + vty_cmd_loop_exit(vty) ; - /*-------------------------------------------------------------------- - * Parse command in hand - * - * If multi-threaded: may be in either thread. - */ - cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ; + return ; /* <<< DONE, permanently */ + } ; - ret = cmd_parse_command(parsed, exec->context) ; - if (ret != CMD_SUCCESS) - { - if (ret != CMD_EMPTY) - break ; /* stop on *any* parsing issue */ - - /* Empty lines from in_pipes we simply ignore. - * - * Don't expect to see them otherwise, but if we do then need - * to complete the command execution process. - */ - ret = CMD_SUCCESS ; - - state = exec_done_cmd ; - continue ; - } ; + zabort("invalid return from vty_cmd_hiatus()") ; + } ; - /* reflection now */ - if (exec->reflect) - { - ret = vty_cmd_reflect_line(vty) ; + fall_through ; - if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) - break ; /* CMD_IO_ERROR or CMD_HIATUS */ - } ; + /*-------------------------------------------------------------------- + * Need another command to execute => in_pipe ! + * + * If multi-threaded: may be in either thread. TODO !! If vty_cmd_fetch_line() + * cannot, for any reason, return a command line, will return something + * other than CMD_SUCCESS -- which enters the exec_hiatus. + */ + case exec_fetch: + ret = vty_cmd_fetch_line(vty) ; - fall_through ; + if (ret != CMD_SUCCESS) + break ; - /*-------------------------------------------------------------------- - * Pipe work if any - * - * Will receive CMD_EOF if the VTY has been closed. - * - * If multi-threaded: must be in vty_cli_nexus to proceed -- so may - * generate message to transfer to vty_cli_nexus. - */ - case exec_open_pipes: - if ((parsed->parts & cmd_parts_pipe) != 0) - { - /* If running pthreaded, do open pipes in vty_cli_nexus */ - if (exec->locus != vty_cli_nexus) - return cq_enqueue(vty, vty_cli_nexus, exec_open_pipes, ret) ; - - /* Now in vty_cli_nexus */ - ret = cmd_open_pipes(vty) ; - if (ret != CMD_SUCCESS) - break ; /* quit if open fails */ - } ; + if (exec->action->to_do != cmd_do_command) + { + state = exec_special ; + continue ; + } ; - fall_through ; /* with ret == CMD_SUCCESS */ + /* Parse command in hand + * + * If multi-threaded: may be in either thread. + */ + cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ; - /*-------------------------------------------------------------------- - * Execute command in hand (if any) - * - * If multi-threaded: some commands can run in either thread, most must - * run in the vty_cmd_nexus -- so may generate message to transfer to - * the vty_cmd_nexus. - */ - case exec_execute: - if ((parsed->parts & cmd_part_command) != 0) - { - /* If running pthreaded, do most commands in vty_cmd_nexus */ - if ((exec->locus != vty_cmd_nexus) && (!cmd_is_direct(parsed))) - return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ; + ret = cmd_parse_command(parsed, exec->context) ; + if (ret != CMD_SUCCESS) + break ; /* on *any* parsing issue */ - /* Standard command handling */ -#ifdef CONSUMED_TIME_CHECK + if ((parsed->parts & cmd_parts_execute) == 0) + { + /* Empty lines from in_pipes appear here. + * + * Don't expect to see them otherwise, but in any case need to + * complete the command execution process. + */ + state = exec_cmd_success ; + continue ; + } ; + + /* reflect if required + */ + if (exec->reflect) + { + ret = vty_cmd_reflect_line(vty) ; + + if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) + break ; /* CMD_IO_ERROR or CMD_HIATUS */ + } ; + + /*-------------------------------------------------------------------- + * Pipe work if any + * + * Will receive CMD_EOF if the VTY has been closed. + * + * If multi-threaded: must be in vty_cli_nexus to proceed -- so may + * generate message to transfer to vty_cli_nexus. + */ + case exec_open_pipes: + if ((parsed->parts & cmd_parts_pipe) != 0) + { + /* If running pthreaded, do open pipes in vty_cli_nexus + */ + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_open_pipes, ret) ; + + /* Now in vty_cli_nexus + */ + ret = cmd_open_pipes(vty) ; + if (ret != CMD_SUCCESS) + break ; /* quit if open fails */ + + /* Finish if this is pipe only + */ + if ((parsed->parts & cmd_part_command) == 0) + { + state = exec_cmd_success ; + continue ; + } ; + } ; + + fall_through ; + + /*-------------------------------------------------------------------- + * If multi-threaded: most commands run in the vty_cmd_nexus, some + * run in the vty_cli_nexus -- transfer to the required nexus, as + * required. + */ + qassert((parsed->parts & cmd_part_command) != 0); + + if (cmd_is_direct(parsed)) + { + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_execute, ret) ; + } + else { - RUSAGE_T before; - RUSAGE_T after; - unsigned long realtime, cputime; + if (exec->locus != vty_cmd_nexus) + return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ; + } - GETRUSAGE(&before); + fall_through ; + + /*-------------------------------------------------------------------- + * Execute command -- now that is in the required nexus. + */ + case exec_execute: +#ifdef CONSUMED_TIME_CHECK + { + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; + + if (!cmd_is_direct(parsed)) + GETRUSAGE(&before); #endif /* CONSUMED_TIME_CHECK */ - ret = cmd_execute(vty) ; + ret = cmd_execute(vty) ; #ifdef CONSUMED_TIME_CHECK + if (!cmd_is_direct(parsed)) + { GETRUSAGE(&after); realtime = thread_consumed_time(&after, &before, &cputime) ; if (realtime > CONSUMED_TIME_CHECK) /* Warn about CPU hog that must be fixed. */ zlog(NULL, LOG_WARNING, - "SLOW COMMAND: command took %lums (cpu time %lums): %s", + "SLOW COMMAND: command took %lums (cpu time %lums): %s", realtime/1000, cputime/1000, qs_make_string(exec->action->line)) ; - } ; -#endif /* CONSUMED_TIME_CHECK */ + } ; } ; +#endif /* CONSUMED_TIME_CHECK */ + + /*-------------------------------------------------------------------- + * Command has completed somehow -- this is the loop entry point. + */ + case exec_cmd_done: + if (ret != CMD_SUCCESS) + break ; fall_through ; /*-------------------------------------------------------------------- - * Command has completed -- if successful, push output and loop back + * Command has completed successfully, push output and loop back * to fetch another command. * - * Break if not successful, or push fails or must wait. - * * If multi-threaded: may be in either thread: * * vty_cmd_success() may set write ready -- so in vty_cmd_nexus may * generate message to vty_cli_nexus. */ - case exec_done_cmd: - if (ret == CMD_SUCCESS) - ret = vty_cmd_success(vty) ; + case exec_cmd_success: + qassert(ret == CMD_SUCCESS) ; + + ret = vty_cmd_success(vty) ; if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) - break ; /* stop */ + break ; state = exec_fetch ; continue ; @@ -450,77 +508,44 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) return cq_enqueue(vty, vty_cli_nexus, exec_special, ret) ; ret = vty_cmd_special(vty) ; - if (ret != CMD_SUCCESS) - break ; - state = exec_done_cmd ; + state = exec_cmd_done ; continue ; /*-------------------------------------------------------------------- - * Hiatus state -- some return code to be dealt with ! - * - * If we are not in the vty_cli_nexus, then must pass the problem - * to the vty_cli_nexus. If the return code is CMD_STOP, or there - * is a CMD_STOP signal, then drop the config symbol of power -- see - * uty_close(). - */ - case exec_hiatus: - if (exec->locus != vty_cli_nexus) - { - vty_cmd_check_stop(vty, ret) ; - return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ; - } ; - - while (1) - { - /* Let vty_cmd_hiatus() deal with the return code and/or any - * stop/error/etc trap, and adjust the stack as required. - */ - ret = vty_cmd_hiatus(vty, ret) ; - - if (ret == CMD_SUCCESS) - break ; /* back to exec_fetch */ - - if (ret == CMD_WAITING) - { - exec->state = exec_hiatus ; - return ; /* <<< DONE, pro tem */ - } ; - - if (ret == CMD_IO_ERROR) - continue ; /* give error back to hiatus */ - - if (ret == CMD_STOP) - { - exec->state = exec_stopped ; - vty_cmd_loop_exit(vty) ; - - return ; /* <<< DONE, permanently */ - } ; - - zabort("invalid return from vty_cmd_hiatus()") ; - } ; - - state = exec_fetch ; - continue ; /* can fetch, again */ - - /*-------------------------------------------------------------------- * Should not get here in exec_stopped state. */ case exec_stopped: zabort("exec state == exec_exit") ; - break ; + return ; /*---------------------------------------------------------------------- * Unknown exec->state ! */ default: zabort("unknown exec->state") ; - break ; + return ; } ; - /* Have broken out of the switch() => exec_hiatus */ - state = exec_hiatus ; + /* Have broken out of the switch() => exec_hiatus + * + * Something has returned a return code that causes entry to the hiatus + * state to sort out. + * + * If we are not in the vty_cli_nexus, then must pass the problem + * to the vty_cli_nexus. If the return code is CMD_STOP, or there + * is a CMD_STOP signal, then drop the config symbol of power + * immediately -- see uty_close(). + */ + if (exec->locus != vty_cli_nexus) + { + vty_cmd_check_stop(vty, ret) ; + return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ; + } ; + + state = vty->exec->state = exec_hiatus ; + + continue ; } ; } ; diff --git a/lib/vty_cli.c b/lib/vty_cli.c index a4ab8de3..d06e455e 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -60,8 +60,6 @@ static const char* telnet_newline = TELNET_NEWLINE ; static bool uty_cli_callback(keystroke_callback_args) ; static void uty_cli_update_more(vty_cli cli) ; -static void uty_cli_cancel(vty_cli cli) ; - /*------------------------------------------------------------------------------ * Construct and initialise a new CLI object -- never embedded. * @@ -102,27 +100,16 @@ uty_cli_new(vio_vf vf) * key_stream = X -- set below * * drawn = false - * - * tilde_prompt = false -- tilde prompt is not drawn - * tilde_enabled = false -- set below if ! multi-threaded + * more_drawn = false * * prompt_len = 0 -- not drawn, in any case - * extra_len = 0 -- not drawn, in any case * * prompt_node = NULL_NODE -- so not set ! * prompt_gen = 0 -- not a generation number * prompt_for_node = NULL -- not set, yet * - * dispatched = false -- see below - * in_progress = false -- see below - * blocked = false -- see below - * paused = false - * - * mon_active = false - * out_active = false - * - * more_wait = false - * more_enter = false + * state = X -- set below + * out_state = cos_idle * * more_enabled = false -- not in "--More--" state * @@ -142,6 +129,7 @@ uty_cli_new(vio_vf vf) * * olc = NULL -- see below */ + confirm(cos_idle == 0) ; confirm(NULL_NODE == 0) ; /* prompt_node & node */ confirm(cmd_do_nothing == 0) ; /* to_do */ confirm(CMD_ACTION_ALL_ZEROS) ; /* dispatch */ @@ -170,28 +158,18 @@ uty_cli_new(vio_vf vf) uty_cli_set_lines(cli, host.lines, false) ; uty_cli_update_more(cli) ; /* and update the olc etc to suit. */ - /* Enable "~ " prompt and pause timer if multi-threaded - * - * TODO decide whether to ditch the '~' prompt, given the priority given - * to commands. + /* Enable pause timer if multi-threaded */ if (vty_nexus) - { - cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile, - vty_term_pause_timeout, cli) ; -#if 0 - cli->tilde_enabled = vty_multi_nexus ; -#endif - } ; + cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile, + vty_term_pause_timeout, cli) ; - /* Ready to be started -- paused, out_active & more_wait are false. + /* Ready to be started -- note that out_state is cos_idle. * * Is started by the first call of uty_cli_want_command(), which (inter alia) * set the cli->context so CLI knows how to prompt etc. */ - cli->dispatched = true ; - cli->in_progress = true ; - cli->blocked = true ; + cli->state = cst_in_progress ; /* start up "command" is running */ return cli ; } ; @@ -227,17 +205,24 @@ uty_cli_close(vty_cli cli, bool final) /* Bring as much of the command handling to a stop as possible, and * turn off "---more--" etc. so that output can proceed. + * + * If it is drawn, we wipe the command line (issuing ^C). We can, then, + * enable output -- which turns off any "--more--" stuff, but keep any + * existing pending monitor output (though forget any pause). + * + * We set the CLI stopped, so that nothing further will happen, including + * when any in_progress command completes. */ cli->more_enabled = false ; cli->lines = 0 ; /* so will not be re-enabled */ - uty_cli_cancel(cli) ; /* " ^C\r\n" unless believed blank */ + uty_cli_cancel(cli, true) ; /* " ^C\r\n" unless believed blank */ - cli->more_wait = false ; /* exit more_wait (if was in it) */ - cli->more_enter = false ; /* and so cannot be this */ - - cli->blocked = true ; /* do not renter the CLI */ - cli->out_active = true ; /* if there is any output, it can go */ + if (cli->state == cst_dispatched) + { + cmd_action_clear(cli->dispatch) ; + cli->state = cst_active ; /* never happened */ + } ; /* Ream out the history. */ { @@ -247,32 +232,33 @@ uty_cli_close(vty_cli cli, bool final) cli->hist = NULL ; } ; - /* Empty the keystroke handling. */ + /* Empty the keystroke handling. + */ cli->key_stream = keystroke_stream_free(cli->key_stream) ; - /* Can discard active command line if not dispatched */ - if (!cli->dispatched || final) - cli->clx = qs_reset(cli->clx, free_it) ; - - /* Can discard context and parsed objects */ - cli->context = cmd_context_free(cli->context, true) ; /* its a copy */ + /* Can discard parsed objects + */ cli->parsed = cmd_parsed_free(cli->parsed) ; - /* Discard any pause_timer, and suppress */ + /* Discard any pause_timer + */ cli->pause_timer = qtimer_free(cli->pause_timer) ; - cli->paused = false ; - cli->tilde_enabled = false ; - /* If final, free the CLI object. */ + /* If final, free the CLI object and stuff that has been kept while + * the output side closes. + */ if (final) { cli->prompt_for_node = qs_free(cli->prompt_for_node) ; cli->cl = qs_free(cli->cl) ; + cli->clx = qs_free(cli->clx) ; cli->cls = qs_free(cli->cls) ; cli->cbuf = vio_fifo_free(cli->cbuf) ; cli->olc = vio_lc_free(cli->olc) ; + cli->context = cmd_context_free(cli->context, true) ; /* its a copy */ + XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */ } ; @@ -292,20 +278,60 @@ static bool uty_cli_callback(keystroke_callback_args /* void* context, keystroke stroke */) { vty_cli cli = context ; + cli_out_state_t out_state ; switch (stroke->type) { case ks_char: /* character -- uint32_t */ - if ((cli->in_progress || cli->out_active) && !cli->more_wait) + /* We pick up the ^C as a cancel iff: + * + * is already cos_cancel -- has no further effect + * + * is cst_dispatched -- cancels before even started + * + * is cst_in_progress or cst_complete, provided is not in either + * cos_more_enter or cos_more_wait -- which deal with ^C themselves. + */ + out_state = cli->out_state & cos_mask ; + + if (out_state == cos_cancel) + return true ; /* eat duplicates */ + + switch (cli->state) { - vty_io vio = cli->vf->vio ; + case cst_active: + qassert(out_state == cos_idle) ; + return false ; + + case cst_dispatched: + /* Managing to cancel before even started to run !! + * + * Clear the dispatched command, and then promote to + * cst_in_progress -- the current line contains the prompt + * and command, which the cancel handling will append ^C to. + */ + qassert(out_state == cos_idle) ; + + cmd_action_clear(cli->dispatch) ; + cli->state = cst_in_progress ; + + break ; + + case cst_in_progress: + case cst_complete: + if ((out_state == cos_more_enter) || (out_state == cos_more_wait)) + return false ; - uty_cmd_signal(vio, CMD_CANCEL) ; /* ^C */ + break ; - return true ; + default: + return false ; } ; - return false ; + cli->out_state ^= (out_state ^ cos_cancel) ; + uty_cmd_signal(cli->vf->vio, CMD_CANCEL) ; + + return true ; case ks_iac: /* Telnet command */ return uty_telnet_command(cli->vf, stroke, true) ; @@ -436,69 +462,84 @@ uty_cli_update_more(vty_cli cli) * * State of the CLI: * - * dispatched -- a command line has been dispatched, and is waiting for - * (dsp) command loop to fetch it. + * cst_active -- is busy drawing prompt, fetching input, processing same, + * etc. -- preparing the next command. + * + * cst_dispatched -- a command line has been dispatched, and is waiting for + * command loop to fetch it. + * + * Note that command line is drawn, and cursor is at + * the end of the line. + * + * cst_in_progress -- a command has been taken by the command loop, and is + * still running -- or at least no further command has + * been fetched by the command loop. + * + * The command may be an in_pipe, so there may be many + * commands to be completed before the CLI level command + * is. + * + * cst_complete -- the last CLI level command has completed, and we are + * waiting for the output to go cos_idle before continuing + * to cst_active. + * + * State of the output: * - * in_progress -- a command has been taken by the command loop, and is still - * (inp) running -- or at least no further command has been - * fetched by the command loop. + * cos_idle -- all output (other than the CLI line) is idle. * - * The command may be an in_pipe, so there may be many - * commands to be completed before the CLI level command is. + * cos_active -- the command output FIFO is being emptied. * - * or: the CLI has been closed. + * This is set when output is pushed, and cleared when + * everything is written away and is cst_complete. * - * blocked -- is in_progress and a further command is now ready to be - * (bkd) dispatched. + * cos_cancel -- all command output is frozen until the command loop + * deals with the cancel action. * - * or: the CLI has been closed. + * cos_more_enter -- in the process of entering "--more--" state * - * out_active -- the command output FIFO is being emptied. - * (oa) - * This is set when output is pushed, and cleared when - * everything is written away and in_progress is not set. - * When it is set, any current command line is wiped. + * cos_more_wait -- in the "--more--" state * - * Note that what this flag does is prevent the CLI from - * running until the output completes, and in particular - * prevents it from writing anything to the CLI buffer. + * cos_monitor -- is busy outputting monitor stuff. * - * more_wait -- is in "--more--" wait state. => out_active ! - * (mwt) - * The "--more--" CLI uses the CLI output buffer to draw - * and undraw the "--more--" prompt. The buffer will - * otherwise be empty because is out_active. + * This state "qualifier" may be applied to any of the + * above states. * - * more_enter -- is in the process of entering the "--more--" wait state, - * (men) waiting to write the "--more--" prompt and prepare the - * keystroke input. + * This is set when monitor output is ready, and when it + * is set any current command line is wiped. * - * The following are the valid combinations: + * cos_paused -- is pausing after monitor output, before continuing + * to output stuff, or redraw CLI line -- in case more + * monitor stuff rolls up. * - * dsp:inp:bkd: oa:mwt:men: - * ---:---:---:---:---:---:----------------------------------------- - * 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command - * 1 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched - * 1 : 1 : 0 : X : 0 : 0 : command fetched and running - * 1 : 1 : 1 : X : 0 : 0 : waiting for command to complete - * 0 : 0 : 0 : 1 : 0 : 0 : waiting for command output to finish - * 1 : X : X : 1 : 1 : 1 : waiting for "--more--" to start - * 1 : X : X : 1 : 1 : 0 : waiting for "--more--" response - * 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete, - * after the CLI has been closed + * This state "qualifier" may be applied to any of the + * above states. * - * There are two output FIFOs: + * If running threaded, this is set and the associated + * timer is started, when monitor output completes. + * + * NB: cos_monitor and cos_paused are mutually exclusive. + * + * NB: cos_monitor and cos_paused take priority over + * other output state. + * + * There are three output FIFOs: * * 1. for the CLI itself -- uty_cli_out and friends * - * 2. for output generated by commands -- vty_out and friends. + * 2. for monitor output + * + * 3. for output generated by commands -- vty_out and friends. + * + * The CLI FIFO is emptied whenever possible, in preference to the other + * FIFOs. + * + * The monitor FIFO is emptied in preference to the command FIFO. * - * The CLI FIFO is emptied whenever possible, in preference to the command - * FIFO. The command FIFO is emptied when out_active. While a command is - * in_progress all its output is collected in the command output buffer, - * to be written away when the command completes, or if it pushes the output + * The command FIFO is emptied when cos_active. While a command is in + * progress all its output is collected in the command output buffer, to be + * written away when the command completes, or if it pushes the output * explicitly. Note that where the CLI level command is an in_pipe, the first - * output will set out_active, and that will persist until returns to the CLI + * output will set cos_active, and that will persist until returns to the CLI * level. Between commands in the pipe, the output will be pushed, so will * output stuff more or less as it is generated. * @@ -527,7 +568,8 @@ uty_cli_update_more(vty_cli cli) * * The output process used the line_control structure to manage the output, and * occasionally enter the trivial "--more--" CLI. This is invisible to the - * main CLI. (See the more_wait and more_enter flags and their handling.) + * main CLI. (See the cos_more_wait and cos_more_enter states and their + * handling.) * * If the user decides to abandon output at the "--more--" prompt, then the * then contents of the command output FIFO are discarded. @@ -538,19 +580,10 @@ uty_cli_update_more(vty_cli cli) * When running in qnexus mode, many commands are not executed directly in the * CLI, but are queued for execution by the main "routeing" nexus. * - * The parsing of commands is context sensitive. The context depends may - * change during the execution of a command. So... it is not possible to - * dispatch a command until the previous one has completed. - * - * In qnexus mode, when a command is queued, the CLI does not go cli_blocked, - * even if some output has already been generated. This allows a further - * command to be entered while the previous one is executed. However, if the - * command is dispatched before the previous one completes, then the cli will - * block. - * - * While the previous command is executing, the current command line has a - * minimal prompt -- to show that the context is not known. When the previous - * command completes, the command line is redrawn in the new context. + * The parsing of commands is context sensitive, as is the prompt. The context + * depends may change during the execution of a command. So... it is not + * possible to read and dispatch a command until the previous one has + * completed. * *------------------------------------------------------------------------------ * Command line drawn state. @@ -561,17 +594,11 @@ uty_cli_update_more(vty_cli cli) * * The command line can be "wiped" -- see uty_cli_wipe() -- which removes all * output and prompt, and leaves the console at the start of an empty line - * where the command line used to be. - * - * On entry to the CLI, it will draw the command line again if it has been - * wiped. - * - * This mechanism is used to support the partial command line that may be - * entered while a queued command executes. - * - * It is also used for the command help/completion system. + * where the command line used to be. On entry to the CLI, it will draw the + * command line again if it has been wiped. * - * It is also used to support the "monitor" output. + * This is used for the command help/completion system and for the "monitor" + * output. */ /*============================================================================== @@ -583,7 +610,7 @@ uty_cli_update_more(vty_cli cli) static enum vty_readiness uty_cli_standard(vty_cli cli) ; static cmd_do_t uty_cli_auth(vty_cli cli) ; static enum vty_readiness uty_cli_more_wait(vty_cli cli) ; -static void uty_cli_draw(vty_cli cli) ; +static void uty_cli_draw(vty_cli cli, bool more_prompt) ; /*------------------------------------------------------------------------------ * CLI for VTY_TERMINAL @@ -606,11 +633,34 @@ uty_cli(vty_cli cli) if (cli->vf->vin_state == vf_closed) return not_ready ; /* Nothing more from CLI if closed */ - /* Standard or "--more--" CLI ? */ - if (cli->more_wait) - return uty_cli_more_wait(cli) ; - else - return uty_cli_standard(cli) ; + /* Standard or "--more--" CLI ? + * + * If cos_idle, then can run standard CLI if is cst_active or cst_complete. + * + * If cos_more_wait or cos_more_enter, then can run more_wait CLI. + * + * Note that cos_cancel, cos_monitor and cos_paused preclude entry to any + * CLI. + */ + switch (cli->out_state) + { + case cos_idle: + if (cli->state == cst_complete) + cli->state = cst_active ; + + if (cli->state == cst_active) + return uty_cli_standard(cli) ; + break ; + + case cos_more_enter: + case cos_more_wait: + return uty_cli_more_wait(cli) ; + + default: + break ; + } ; + + return not_ready ; } ; /*============================================================================== @@ -618,17 +668,18 @@ uty_cli(vty_cli cli) */ static cmd_do_t uty_cli_process(vty_cli cli) ; -static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ; +static void uty_cli_response(vty_cli cli, cmd_do_t to_do) ; -static bool uty_cli_dispatch(vty_cli cli) ; +static void uty_cli_dispatch(vty_cli cli, cmd_do_t to_do) ; static cmd_do_t uty_cli_command(vty_cli cli) ; static void uty_cli_hist_add (vty_cli cli, qstring clx) ; -static void uty_cli_pause_start(vty_cli cli) ; static void uty_cli_goto_end_if_drawn(vty_cli cli) ; /*------------------------------------------------------------------------------ - * Standard CLI for VTY_TERM -- if not blocked, runs until: + * Standard CLI for VTY_TERM + * + * Entered only if cst_active && cos_idle. Runs until: * * * runs out of keystrokes * * executes a command @@ -637,126 +688,86 @@ static void uty_cli_goto_end_if_drawn(vty_cli cli) ; * is to allow for a modicum of sharing of the system. For real keyboard input * this will make no difference at all ! * - * Returns: not_ready blocked and was blocked when entered - * write_ready if there is anything in the keystroke stream - * read_ready otherwise + * NB: it is expected that the caller will attempt to flush any output + * generated. + * + * Returns: write_ready if the keystroke buffer is not empty and is not + * blocked, which uses write_ready to return here. */ static vty_readiness_t uty_cli_standard(vty_cli cli) { - bool need_prompt ; + cmd_do_t to_do ; VTY_ASSERT_LOCKED() ; - assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */ - - /* cli_blocked is set when is waiting for a command, or some output to - * complete. - * - * NB: in both these cases, assumes that other forces are at work to - * keep things moving. - */ - if (cli->blocked || cli->out_active || cli->paused || cli->mon_active) - return not_ready ; + qassert((cli->state == cst_active) && (cli->out_state == cos_idle)) ; /* Make sure that the command line is drawn. * * If there is nothing pending, then can run the CLI until there is * something to do, or runs out of input. */ - if (!cli->drawn) - uty_cli_draw(cli) ; - - if (cli->to_do == cmd_do_nothing) - cli->to_do = cli->auth_context ? uty_cli_auth(cli) - : uty_cli_process(cli) ; + if (!cli->drawn || cli->more_drawn) + uty_cli_draw(cli, false) ; - /* If have something to do, do it if we can. */ - need_prompt = false ; + to_do = cli->to_do ; /* pick up any pending "to_do" */ - if (cli->to_do != cmd_do_nothing) - { - /* Reflect immediate response */ - uty_cli_response(cli, cli->to_do) ; - - /* If command not already in progress, dispatch this one, which may - * set the CLI blocked. - * - * Otherwise is now blocked until queued command completes. - */ - if (cli->dispatched) - cli->blocked = true ; /* waiting for previous */ - else - need_prompt = uty_cli_dispatch(cli) ; /* dispatch latest */ - } ; + if (to_do == cmd_do_nothing) + to_do = cli->auth_context ? uty_cli_auth(cli) + : uty_cli_process(cli) ; + else + cli->to_do = cmd_do_nothing ; /* clear pending "to_do" */ - /* If blocked, must wait for some other event to continue in CLI. - * - * Note that will be blocked if have just dispatched a command, and is - * not "tilde_enabled" -- which is the case if single threaded, and may be - * so for other reasons. - * - * If the keystroke stream is not empty, use write_ready as a proxy for - * CLI ready -- no point doing anything until any buffered output has been - * written away. - * - * If command prompt has been redrawn, need to kick writer to deal with - * that -- will reenter to then process any keystrokes. + /* If we have something to do, now is the time to do it. */ - assert(!cli->paused) ; - - if (cli->blocked) - return not_ready ; - - if (!keystroke_stream_empty(cli->key_stream) || need_prompt) - return write_ready ; + if (to_do != cmd_do_nothing) + uty_cli_dispatch(cli, to_do) ; - /* The keystroke stream is empty, but CLI is not blocked. + /* If not active, must wait for some other event to continue in CLI. * - * If a command is dispatched, and output is not active, and the command - * line is not drawn, then go to paused state, so that will delay output - * of the prompt slightly (or until a keystroke arrives, or the current - * command completes, or something else happens). - * - * Note that if a command has been dispatched, and is !tilde_enabled, then - * will now be blocked, so won't be here. + * Otherwise, if the keystroke stream is not empty, use write_ready as a + * proxy for CLI ready -- no point doing anything until any buffered output + * has been written away. */ - if (cli->dispatched && !cli->out_active && !cli->drawn) - uty_cli_pause_start(cli) ; - - return read_ready ; + if ((cli->state != cst_active) || keystroke_stream_empty(cli->key_stream)) + return read_ready ; + else + return write_ready ; } ; /*------------------------------------------------------------------------------ - * Dispatch the current cli->to_do -- queueing it if necessary. + * Dispatch the given "to_do". * - * Requires that are NOT blocked and NO command is queued. + * Requires that are NOT blocked and NO command is queued etc. * - * Expects to be on new blank line, and when returns will be on new, blank - * line. + * Expects to be at the end of the command line for the command to be + * executed -- issues suitable response on the command line.. * * Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty. * - * Can set cli->cl_do = and cli->cl to be a follow-on command. + * Can set cli->to_do = and cli->cl to be a follow-on command. * - * Returns: true <=> nothing, in fact, to do: prompt has been redrawn. + * If there is something to do, it is queued for the command loop to deal with, + * will then be cst_dispatched. NB: the current line is still drawn, and + * cursor is at the end of the line. + * + * If there is nothing to do, issues newline and the prompt, and returns. */ -static bool -uty_cli_dispatch(vty_cli cli) +static void +uty_cli_dispatch(vty_cli cli, cmd_do_t to_do) { qstring tmp ; - cmd_do_t to_do_now ; vty_io vio = cli->vf->vio ; VTY_ASSERT_LOCKED() ; /* About to dispatch a command, so must be in the following state. */ - qassert(!cli->dispatched && !cli->in_progress - && !cli->blocked && !cli->out_active) ; + qassert((cli->state == cst_active) && (cli->out_state == cos_idle)) ; qassert(cli->context->node == vio->vty->exec->context->node) ; - /* Set cli->clx to the command about to execute & pick up cli->to_do. + /* Set cli->clx to the command about to execute. * * Clear cli->cl and cli->to_do. */ @@ -764,18 +775,19 @@ uty_cli_dispatch(vty_cli cli) cli->clx = cli->cl ; cli->cl = tmp ; - to_do_now = cli->to_do ; /* current operation */ - cli->to_do = cmd_do_nothing ; /* clear */ qs_clear(cli->cl) ; /* set cl empty */ + /* Produce suitable response for the to_do_now */ + uty_cli_response(cli, to_do) ; + /* Dispatch command */ if (cli->auth_context) - to_do_now |= cmd_do_auth ; + to_do |= cmd_do_auth ; else { /* All other nodes... */ - switch (to_do_now) + switch (to_do) { case cmd_do_nothing: case cmd_do_ctrl_c: @@ -784,7 +796,7 @@ uty_cli_dispatch(vty_cli cli) break ; case cmd_do_command: - to_do_now = uty_cli_command(cli) ; + to_do = uty_cli_command(cli) ; break ; case cmd_do_ctrl_d: @@ -792,12 +804,12 @@ uty_cli_dispatch(vty_cli cli) break ; case cmd_do_ctrl_z: - to_do_now = uty_cli_command(cli) ; + to_do = uty_cli_command(cli) ; - if (to_do_now == cmd_do_nothing) - to_do_now = cmd_do_ctrl_z ; + if (to_do == cmd_do_nothing) + to_do = cmd_do_ctrl_z ; /* restore ^Z if line empty */ else - cli->to_do = cmd_do_ctrl_z ; /* defer the ^Z */ + cli->to_do = cmd_do_ctrl_z ; /* defer the ^Z */ break ; @@ -808,24 +820,19 @@ uty_cli_dispatch(vty_cli cli) cli->hp = cli->h_now ; /* in any event, back to the present */ - if (to_do_now != cmd_do_nothing) + if (to_do != cmd_do_nothing) { - cmd_action_set(cli->dispatch, to_do_now, cli->clx) ; - cli->dispatched = true ; + cmd_action_set(cli->dispatch, to_do, cli->clx) ; + cli->state = cst_dispatched ; uty_cmd_signal(vio, CMD_SUCCESS) ; - - cli->blocked = (to_do_now != cmd_do_command) || !cli->tilde_enabled ; } else { cmd_action_clear(cli->dispatch) ; - cli->dispatched = false ; - - uty_cli_draw(cli) ; + uty_cli_out_newline(cli) ; + uty_cli_draw(cli, false) ; } ; - - return !cli->dispatched ; } ; /*------------------------------------------------------------------------------ @@ -849,89 +856,35 @@ uty_cli_command(vty_cli cli) /*------------------------------------------------------------------------------ * Want another command line from the CLI. * - * If in_progress this <=> any previous command has completed, and output may - * be active writing the results away. Will: - * - * * clear cli->dispatched - * * clear cli->in_progress - * * clear cli->blocked - * - * May be in more_wait state -- so avoids touching that. - * - * If not in_progress, then if dispatched, we have a new command ready to pass - * to the command loop -- which we do here, and set cli->in_progress. - * - * Returns: CMD_SUCCESS -- the given action has been set to next command - * or: CMD_WAITING -- no command available (yet) - * - * Note that for the CLI eof and read time-out are handled as cmd_do_eof and - * cmd_do_timed_out -- so will float through as CMD_SUCCESS and be processed - * as commands. - * - * Write I/O errors and time-outs are signalled by uty_vf_error(), and - * therefore caught in the command loop. - * - * Read I/O errors are signalled by uty_vf_error(). Read timeout is treated - * as above. + * If have one, return it and CMD_SUCCESS and set cst_in_progress. + * Otherwise, return CMD_WAITING. */ extern cmd_return_code_t -uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context) +uty_cli_want_command(vty_cli cli, cmd_action action) { VTY_ASSERT_LOCKED() ; - if (cli->in_progress) - { - /* Previous command has completed - * - * Make sure state reflects the fact that we are now waiting for a - * command. - */ - cli->dispatched = false ; /* no longer have a dispatched command */ - cli->in_progress = false ; /* command complete */ - cli->blocked = false ; /* releases possibly blocked command */ - cli->paused = false ; /* override paused state */ - - *cli->context = *context ; /* make sure is up to date */ - cli->auth_context = ( (cli->context->node == AUTH_NODE) - || (cli->context->node == AUTH_ENABLE_NODE) ) ; - - /* If the output is owned by command output, then when the buffers - * empty, and in_progress is seen to be false, out_active will be - * cleared. - * - * If the output side is not owned by command output, rewrite the - * command line, so that prompt is up to date and visible. - * - * In any case, kick write_ready to ensure output clears and prompt is - * written and so on. - */ - if (!cli->out_active) - uty_cli_draw(cli) ; - - uty_term_set_readiness(cli->vf, write_ready) ; - } - else if (cli->dispatched) + if (cli->state == cst_dispatched) { /* New command has been dispatched -- can now pass that to the * command loop -- setting it in_progress. + * + * This is the moment we issue the newline, which we want written away. */ - assert(cli->dispatch->to_do != cmd_do_nothing) ; + qassert(cli->dispatch->to_do != cmd_do_nothing) ; + qassert((cli->out_state & cos_mask) == cos_idle) ; + cmd_action_take(action, cli->dispatch) ; - cli->in_progress = true ; - } ; + cli->state = cst_in_progress ; - return cli->in_progress ? CMD_SUCCESS : CMD_WAITING ; -} ; + uty_cli_out_newline(cli) ; + uty_term_set_readiness(cli->vf, write_ready) ; -/*------------------------------------------------------------------------------ - * Start pause timer and set paused. - */ -static void -uty_cli_pause_start(vty_cli cli) -{ - qtimer_set(cli->pause_timer, qt_add_monotonic(QTIME(0.2)), NULL) ; - cli->paused = true ; + return CMD_SUCCESS ; + } ; + + return CMD_WAITING ; } ; /*============================================================================== @@ -940,13 +893,14 @@ uty_cli_pause_start(vty_cli cli) * While command output is being cleared from its FIFO, the CLI is blocked. * * When the output side signals that "--more--" is required, it sets the - * more_wait and more_enter flags. + * cos_more_enter out state. * * The first stage of handling "--more--" is to suck the input dry, so that * (as far as is reasonably possible) does not steal a keystroke as the - * "--more--" response which was typed before the prompt was issued. + * "--more--" response which was typed before the prompt was issued. This is + * the cos_more_enter state. * - * The more_enter flag indicates that the CLI is in this first stage. + * Once is waiting for a keystroke, is in cos_more_wait state. */ /*------------------------------------------------------------------------------ @@ -959,30 +913,29 @@ uty_cli_enter_more_wait(vty_cli cli) { VTY_ASSERT_LOCKED() ; - qassert(cli->out_active && !cli->more_wait && !cli->drawn) ; + qassert((cli->out_state == cos_active) && !cli->drawn) ; - cli->more_wait = true ; /* new state */ - cli->more_enter = true ; /* drawing the "--more--" etc. */ + cli->out_state = cos_more_enter ; /* draw the "--more--" etc. */ } ; /*------------------------------------------------------------------------------ * Handle the "--more--" state. * - * If is paused or the monitor is active, do nothing -- those override the - * "--more--" state. + * Entered only if cos_more_enter or cos_more_wait. So does not get here if + * cos_monitor or cos_paused are set. * - * If more_enter is set, then need to make sure the "--more--" prompt is + * If cos_more_enter is set, then need to make sure the "--more--" prompt is * written, and that any keystrokes which arrived before the prompt is * written are taken into the keystroke stream -- so not stolen. * * Note that the "--more--" state may be interrupted by monitor output, - * which, once complete, sets more_enter again. + * which, once complete, sets cos_more_enter again. * - * If more_enter is not set, tries to steal a keystroke, and if succeeds wipes - * the "--more--" prompt and exits cli_more_wait -- and may cancel all + * If cos_more_wait is set, tries to steal a keystroke, and if succeeds wipes + * the "--more--" prompt and exits cos_more_wait -- and may cancel all * outstanding output. * - * EOF on input causes immediate exit from cli_more_state. + * EOF on input causes immediate exit from "--more--" * * Returns: read_ready -- waiting to steal a keystroke * write_ready -- waiting to draw or undraw the "--more--" prompt @@ -996,32 +949,32 @@ uty_cli_more_wait(vty_cli cli) VTY_ASSERT_LOCKED() ; - assert(cli->more_wait) ; /* must be in more_wait state ! */ + qassert( (cli->out_state == cos_more_enter) || + (cli->out_state == cos_more_wait) ) ; - if (cli->paused || cli->mon_active) - return not_ready ; - - /* Deal with the first stage of "--more--" */ - if (cli->more_enter) + /* Deal with the first stage of "--more--" + * + * If the "--more--" is not drawn, draw it. + * + * Hoover up any input there is pending -- probably none, but this seems + * tidy. + * + * If the "--more--" prompt has been written away, we are ready to steal + * a keystroke -- ie go to cos_more_wait. + * + * In any event, exit write_ready ready to keep things moving. + */ + if (cli->out_state == cos_more_enter) { - int get ; + if (!cli->drawn || !cli->more_drawn) + uty_cli_draw(cli, true) ; /* draw the "--more--" */ - if (!cli->drawn) - uty_cli_draw(cli) ; /* draw the "--more--" */ - - /* If the CLI buffer is not yet empty, then is waiting for the - * initial prompt to clear, so nothing to be done here. - */ - if (!vio_fifo_empty(cli->cbuf)) - return write_ready ; + uty_term_read(cli->vf) ; /* hoover up. */ - cli->more_enter = false ; + if (vio_fifo_empty(cli->cbuf)) + cli->out_state = cos_more_wait ; - /* empty the input buffer into the keystroke stream */ - do - { - get = uty_term_read(cli->vf) ; - } while (get > 0) ; + return write_ready ; } ; /* Try to get a stolen keystroke. If the keystroke stream has hit @@ -1078,15 +1031,13 @@ uty_cli_more_wait(vty_cli cli) * Return write_ready to tidy up the screen and, unless cleared, write * some more. */ + cli->out_state = cos_active ; /* revert to active state */ if (cancel) { vio_fifo_clear(cli->vf->obuf, false) ; - vio_lc_clear(cli->olc) ; /* clear & reset counter */ - - uty_cli_goto_end_if_drawn(cli) ; - cli->drawn = false ; + vio_lc_clear(cli->olc) ; /* clear & reset counter */ - qassert(cli->out_active) ; + cli->out_state = cos_cancel ; uty_cmd_signal(cli->vf->vio, CMD_CANCEL) ; /* ^C */ } @@ -1096,9 +1047,6 @@ uty_cli_more_wait(vty_cli cli) uty_cli_wipe(cli, 0) ; } ; - cli->more_wait = false ; /* exit more_wait */ - cli->more_enter = false ; /* tidy */ - return write_ready ; } ; @@ -1124,7 +1072,7 @@ uty_cli_more_wait(vty_cli cli) */ /*----------------------------------------------------------------------------- - * Prepare for new monitor output line. + * Prepare for new monitor output line (s). * * Wipe any existing command line. */ @@ -1135,7 +1083,7 @@ uty_cli_pre_monitor(vty_cli cli) uty_cli_wipe(cli, 0) ; - cli->mon_active = true ; /* block cli & enable empty of fifo */ + cli->out_state = (cli->out_state | cos_monitor) & ~cos_paused ; } ; /*------------------------------------------------------------------------------ @@ -1143,7 +1091,8 @@ uty_cli_pre_monitor(vty_cli cli) * * If monitor line failed to complete, append the rump to the CLI buffer. * - * If have a non-empty command line, or is cli_more_wait, redraw the command + * + * If have a non-empty command line, or is cos_more_wait, redraw the command * line. * * Returns: 0 => rump was empty and no command line stuff written @@ -1154,12 +1103,31 @@ uty_cli_post_monitor(vty_cli cli) { VTY_ASSERT_LOCKED() ; - uty_cli_pause_start(cli) ; /* do not draw prompt immediately */ + /* Clear the monitor output state. + * + * Should not be cos_paused -- but clear that too. + */ + qassert((cli->out_state & (cos_monitor | cos_paused)) == cos_monitor) ; - cli->more_enter = cli->more_wait ; - /* revert to more_enter state */ + cli->out_state &= ~(cos_monitor | cos_paused) ; + + /* If was cos_more_wait, then fall back to cos_more_enter. + * + * Note that does not set cos_cancel on top of cos_more_wait/cos_more_enter ! + */ + if (cli->out_state == cos_more_wait) + cli->out_state = cos_more_enter ; + + /* If not cos_cancel, if we have a pause_timer, then set that going, now. + * + * Note that when the cli is closed any timer is freed. + */ + if ((cli->out_state != cos_cancel) && (cli->pause_timer != NULL)) + { + qtimer_set(cli->pause_timer, qt_add_monotonic(QTIME(0.2)), NULL) ; + cli->out_state |= cos_paused ; + } ; - cli->mon_active = false ; /* unblock cli & turn off output */ } ; /*============================================================================== @@ -1272,10 +1240,9 @@ uty_cli_write_s(vty_cli cli, const char *str) extern void uty_cli_out_newline(vty_cli cli) { - uty_cli_goto_end_if_drawn(cli) ; + uty_cli_goto_end_if_drawn(cli) ; /* clears cli->drawn */ uty_cli_write(cli, telnet_newline, 2) ; - cli->drawn = false ; } ; /*------------------------------------------------------------------------------ @@ -1305,16 +1272,15 @@ uty_cli_out_wipe_n(vty_cli cli, int n) * * Clears cli->drawn. */ -static void -uty_cli_cancel(vty_cli cli) +extern void +uty_cli_cancel(vty_cli cli, bool cntrl_c) { if (cli->drawn) { - uty_cli_goto_end_if_drawn(cli) ; + uty_cli_goto_end_if_drawn(cli) ; /* clears cli->drawn */ - uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ; - - cli->drawn = false ; + if (cntrl_c) + uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ; } ; } ; @@ -1322,70 +1288,52 @@ uty_cli_cancel(vty_cli cli) * If the command line is drawn and the cursor is not at the end of the line, * move the physical cursor to the end of the line. * - * Assumes about to issue newline. + * Assumes about to issue newline, so clears cli->drawn */ static void uty_cli_goto_end_if_drawn(vty_cli cli) { - ulen after ; + if (cli->drawn && !cli->more_drawn) + { + ulen after ; + + after = qs_after_cp_nn(cli->cl) ; + + if (after != 0) + uty_cli_write(cli, qs_cp_char_nn(cli->cl), after) ; + } ; - if (cli->drawn && ( (after = qs_after_cp_nn(cli->cl)) != 0 )) - uty_cli_write(cli, qs_cp_char_nn(cli->cl), after) ; + cli->drawn = false ; } ; /*------------------------------------------------------------------------------ - * Send response to the given cmd_do - * - * If no command is in progress, then will send newline to signal that the - * command is about to be dispatched. + * Send any special response -- depending on what's to_do. * - * If command is in progress, then leaves cursor on '^' to signal that is now - * waiting for previous command to complete. + * NB: at this point MUST be at end of cli->cl. */ -static const char* cli_response [2][cmd_do_count] = +static const char* cli_response [cmd_do_count] = { - { /* when not waiting for previous command to complete */ - [cmd_do_command] = "", - [cmd_do_ctrl_c] = "^C", - [cmd_do_ctrl_d] = "^D", - [cmd_do_ctrl_z] = "^Z", - [cmd_do_eof] = "^*", - [cmd_do_timed_out] = "^!" - }, - { /* when waiting for a previous command to complete */ - [cmd_do_command] = "^", + [cmd_do_command] = NULL, [cmd_do_ctrl_c] = "^C", [cmd_do_ctrl_d] = "^D", [cmd_do_ctrl_z] = "^Z", [cmd_do_eof] = "^*", - [cmd_do_timed_out] = "^!" - } + [cmd_do_timed_out] = "^!", } ; static void uty_cli_response(vty_cli cli, cmd_do_t to_do) { const char* str ; - int len ; - assert((to_do != cmd_do_nothing) && cli->drawn + qassert((to_do != cmd_do_nothing) && cli->drawn && (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl))) ; + qassert(to_do < cmd_do_count) ; - str = (to_do < cmd_do_count) - ? cli_response[cli->dispatched ? 1 : 0][to_do] : NULL ; - assert(str != NULL) ; - - len = uty_cli_write_s(cli, str) ; + str = (to_do < cmd_do_count) ? cli_response[to_do] : NULL ; - if (cli->dispatched) - { - cli->extra_len = len ; - uty_cli_write_n(cli, telnet_backspaces, len) ; - } - else - { - uty_cli_out_newline(cli) ; - } ; + if (str != NULL) + uty_cli_write_s(cli, str) ; } ; /*------------------------------------------------------------------------------ @@ -1416,10 +1364,10 @@ uty_cli_wipe(vty_cli cli, int len) assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; /* Establish how much ahead and how much behind the cursor */ - a = cli->extra_len ; + a = 0 ; b = cli->prompt_len ; - if (!cli->more_wait) + if (!cli->more_drawn) { a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; b += qs_cp_nn(cli->cl) ; @@ -1440,7 +1388,7 @@ uty_cli_wipe(vty_cli cli, int len) uty_cli_write_n(cli, telnet_backspaces, b) ; /* Nothing there any more */ - cli->drawn = false ; + cli->drawn = false ; } ; /*------------------------------------------------------------------------------ @@ -1464,10 +1412,9 @@ uty_cli_wipe(vty_cli cli, int len) * * Sets: cli->drawn = true * cli->prompt_len = length of prompt used - * cli->extra_len = 0 */ static void -uty_cli_draw(vty_cli cli) +uty_cli_draw(vty_cli cli, bool more_prompt) { const char* prompt ; size_t l_len ; @@ -1480,19 +1427,12 @@ uty_cli_draw(vty_cli cli) p_len = 0 ; l_len = 0 ; } - else if (cli->more_wait) + else if (more_prompt) { prompt = "--more--" ; p_len = strlen(prompt) ; l_len = 0 ; } - else if (cli->dispatched) - { - /* If there is a queued command, the prompt is a minimal affair. */ - prompt = "~ " ; - p_len = strlen(prompt) ; - l_len = qs_len_nn(cli->cl) ; - } else { /* The prompt depends on the node, and is expected to include the @@ -1533,8 +1473,7 @@ uty_cli_draw(vty_cli cli) /* Set about writing the prompt and the line */ cli->drawn = true ; - cli->extra_len = false ; - + cli->more_drawn = more_prompt ; cli->prompt_len = p_len ; uty_cli_write(cli, prompt, p_len) ; @@ -2008,13 +1947,12 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke) if (stroke->type != ks_iac) return cmd_do_keystroke ; /* have a keystroke */ - /* Deal with telnet command, so invisible to upper level */ + /* Deal with telnet command, so invisible to upper level + */ uty_telnet_command(cli->vf, stroke, false) ; } else { - int get ; - qassert(stroke->type == ks_null) ; switch (stroke->value) @@ -2033,10 +1971,7 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke) break ; } ; - get = uty_term_read(cli->vf) ; /* sets eof in key_stream - if hit eof or error */ - if ((get == 0) || (get == -1)) - return cmd_do_nothing ; + return cmd_do_nothing ; } ; } ; } ; @@ -2986,7 +2921,7 @@ uty_cli_help_finish(vty_cli cli) { if (!cli->drawn) { - uty_cli_draw(cli) ; + uty_cli_draw(cli, false) ; qs_copy(cli->cls, cli->cl) ; } ; } ; diff --git a/lib/vty_cli.h b/lib/vty_cli.h index e366ecdf..13d394aa 100644 --- a/lib/vty_cli.h +++ b/lib/vty_cli.h @@ -37,6 +37,35 @@ #include "keystroke.h" /*------------------------------------------------------------------------------ + * States of the CLI and related output. + */ +typedef enum cli_state +{ + cst_active = 0, + cst_dispatched = 1, + cst_in_progress = 2, + cst_complete = 3, + +} cli_state_t ; + +typedef enum cli_out_state +{ + cos_idle = 0, + + cos_active = 1, + cos_cancel = 2, + + cos_more_enter = 3, + cos_more_wait = 4, + + cos_mask = BIT(4) - 1, + + cos_monitor = BIT(6), + cos_paused = BIT(7), + +} cli_out_state_t ; + +/*------------------------------------------------------------------------------ * The vty_cli structure pointed to by the vty_io structure. */ typedef struct vty_cli* vty_cli ; @@ -74,70 +103,33 @@ struct vty_cli * any other output can use the screen. * * In particular, must be cleared before setting - * out_active -- see below. + * cos_active -- see below. * - * tilde_enabled <=> do the "~ " one command line ahead. + * more_drawn <=> what is drawn is the "--more--" prompt. + * => drawn * * If drawn is true, the following are valid: * - * tilde_prompt -- the prompt is the "~ " - * * prompt_len -- the length of the prompt part. * (will be the "--more--" prompt in cli_more_wait) * - * extra_len -- the length of any ^X at the cursor position - * (for when blocked waiting for queued command) - * * echo_suppress -- the user part of the command line is suppressed * * NB: echo_suppress is only used for password entry. */ bool drawn ; - - bool tilde_prompt ; - bool tilde_enabled ; - + bool more_drawn ; int prompt_len ; - int extra_len ; /* "cache" for prompt -- when node or host name changes, prompt does */ node_type_t prompt_node ; name_gen_t prompt_gen ; qstring prompt_for_node ; - /* State of the CLI - * - * dispatched -- command dispatched by CLI - * in_progress -- command taken by the command loop - * blocked -- blocked until current command completes - * paused -- command dispatched and nothing else happened - * - * mon_active -- there is stuff in the logging monitor buffer - * - * out_active -- contents of the obuf FIFO are being written away - * though may be blocked in more_wait - * - * This flag <=> that the command output "owns" the screen. - * - * While this flag is set, the CLI may not write to the - * screen. - * - * Flag is cleared when obuf is empty, and is !in_progress. - * - * more_wait -- is in "--more--" wait state - * more_enter -- more_wait and waiting for "--more--" prompt to be - * written away and keystrokes to be consumed. + /* State of the CLI and its output */ - bool dispatched ; - bool in_progress ; - bool blocked ; - bool paused ; - - bool mon_active ; - bool out_active ; - - bool more_wait ; - bool more_enter ; + cli_state_t state ; + cli_out_state_t out_state ; /* This is set only if the "--more--" handling is enabled */ bool more_enabled ; @@ -199,13 +191,13 @@ extern ulen uty_cli_prompt_len(vty_cli cli) ; extern vty_readiness_t uty_cli(vty_cli cli) ; -extern cmd_return_code_t uty_cli_want_command(vty_cli cli, cmd_action action, - cmd_context context) ; +extern cmd_return_code_t uty_cli_want_command(vty_cli cli, cmd_action action) ; extern void uty_cli_out(vty_cli cli, const char *format, ...) PRINTF_ATTRIBUTE(2, 3) ; extern void uty_cli_out_newline(vty_cli cli) ; extern void uty_cli_write(vty_cli cli, const char *this, int len) ; extern void uty_cli_wipe(vty_cli cli, int len) ; +extern void uty_cli_cancel(vty_cli cli, bool cntrl_c) ; extern void uty_cli_set_lines(vty_cli cli, int lines, bool explicit) ; extern void uty_cli_set_window(vty_cli cli, int width, int height) ; diff --git a/lib/vty_command.c b/lib/vty_command.c index d195f193..79a8a294 100644 --- a/lib/vty_command.c +++ b/lib/vty_command.c @@ -121,6 +121,7 @@ static bool uty_cmd_loop_prepare(vty_io vio) ; static void uty_cmd_stopping(vty_io vio, bool exeunt) ; static cmd_return_code_t uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) ; static cmd_return_code_t vty_cmd_auth(vty vty, node_type_t* p_next_node) ; +static void uty_cmd_complete(vty_io vio) ; static void uty_cmd_out_cancel(vio_vf vf, bool base) ; static uint uty_show_error_context(vio_fifo ebuf, vio_vf vf) ; static uint uty_cmd_failed(vty_io vio, cmd_return_code_t ret) ; @@ -544,8 +545,7 @@ vty_cmd_fetch_line(vty vty) break ; case VIN_TERM: - ret = uty_term_fetch_command_line(vf, exec->action, - exec->context) ; + ret = uty_term_fetch_command_line(vf, exec->action) ; break ; case VIN_VTYSH: @@ -976,8 +976,8 @@ vty_cmd_auth(vty vty, node_type_t* p_next_node) * * The command_queue command loop runs until something happens that it * cannot immediately deal with, at which point it enters "exec_hiatus", and - * this function is called. The command loop will deal with CMD_SUCCESS and - * CMD_EMPTY, but otherwise this function must deal with: + * this function is called. The command loop will deal with CMD_SUCCESS, + * but otherwise this function must deal with: * * CMD_HIATUS -- something requires attention, eg: * @@ -1010,8 +1010,6 @@ vty_cmd_auth(vty vty, node_type_t* p_next_node) * * CMD_SUCCESS -- see below * - * CMD_EMPTY -- should not appear, but is treated as CMD_SUCCESS - * * anything else -- treated as a command or I/O or other error. * * The handling of errors depends on the type of error: @@ -1180,7 +1178,6 @@ uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) switch (ret) { case CMD_SUCCESS: - case CMD_EMPTY: case CMD_HIATUS: ret = CMD_SUCCESS ; /* OK */ break ; @@ -1320,6 +1317,11 @@ uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) * * If the vty is about to be closed, this step ensures that all output * is tidily dealt with, before uty_close() performs its "final" close. + * + * If we are also down to the vin_base, then signal that the last base + * level command has completed. Need to do this even if the vin_depth + * is zero -- so that, if required, the output side will signal that + * all I/O is complete. */ if (vio->vout_depth == 1) { @@ -1327,6 +1329,8 @@ uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) { /* Once we have cleared the output buffer etc., clear the cancel * flag and output "^C" to show what has happened. + * + * Also, for VTY_TERMINAL, clears the cos_cancel down to cos_active. */ uty_cmd_out_cancel(vio->vout, true) ; /* stop output & pipe return * is vout_base */ @@ -1350,6 +1354,9 @@ uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) ret = uty_cmd_out_push(vio->vout, false) ; + if (vio->vin_depth <= 1) + uty_cmd_complete(vio) ; + if (ret != CMD_SUCCESS) return ret ; /* CMD_WAITING or CMD_IO_ERROR */ } ; @@ -1688,19 +1695,27 @@ vty_cmd_success(vty vty) vty_io vio ; VTY_LOCK() ; - vio = vty->vio ; /* once locked */ + vio = vty->vio ; /* once locked */ ret = vio->signal ; /* signal can interrupt */ - if (ret == CMD_SUCCESS) + /* out_suppress is set only when is VTY_CONFIG_READ and the output is not + * required -- not even if there is a signal outstanding. + * + * NB: we do not need to worry about uty_cmd_complete() for VTY_CONFIG_READ. + * + * If not interrupted by a signal, push anything there is to push, and + * if we are at the base stack level, signal uty_cmd_complete(). + */ + if (vty->exec->out_suppress) + vio_fifo_back_to_end_mark(vio->obuf, true) ; /* keep end mark */ + else if (ret == CMD_SUCCESS) { if (!vio_fifo_tail_empty(vio->obuf)) - { - if (!vty->exec->out_suppress) - ret = uty_cmd_out_push(vio->vout, false) ; /* not final */ - else - vio_fifo_back_to_end_mark(vio->obuf, true) ; /* keep end mark */ - } ; + ret = uty_cmd_out_push(vio->vout, false) ; /* not final */ + + if ((vio->vin_depth == 1) && (vio->vout_depth == 1)) + uty_cmd_complete(vio) ; } ; VTY_UNLOCK() ; @@ -1709,6 +1724,60 @@ vty_cmd_success(vty vty) } ; /*------------------------------------------------------------------------------ + * A command has completed -- for whatever reason -- no further output will + * appear, so the output buffer can now emptied out (including any trailing + * line without a line ending) and when that finishes the output is, again, + * idle. + * + * This is *only* when is at the base vin and vout level (or if vin is closed + * and is at base vout). + * + * For VTY_TERMINAL, the completion of all output associated with a command + * signals the time to start fetching the next one. + * + * For VTY_SHELL_SERVER, ditto. + * + * For VTY_CONFIG_READ, nothing is required. + */ +static void +uty_cmd_complete(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + qassert((vio->vin_depth <= 1) && (vio->vout_depth == 1)) ; + + switch (vio->vin->vin_type) + { + case VIN_NONE: + zabort("invalid vin_none") ; + break ; + + case VIN_TERM: + uty_term_cmd_complete(vio->vin, vio->vty->exec->context) ; + break ; + + case VIN_VTYSH: + /* Signal end of command return */ + break ; + + case VIN_FILE: + break ; + + case VIN_PIPE: + break ; + + case VIN_CONFIG: + break ; /* do nothing ! */ + + case VIN_DEV_NULL: + break ; + + default: + zabort("unknown vin_type") ; + } ; +} ; + +/*------------------------------------------------------------------------------ * If there is anything after the end_mark, push it to be written, now. * * This is used by configuration file output, which outputs to the fifo and @@ -1858,6 +1927,9 @@ uty_cmd_out_cancel(vio_vf vf, bool base) */ vio_fifo_clear(vf->obuf, false) ; + if (vf->vout_type == VOUT_TERM) + uty_term_out_cancelled(vf) ; + if (!base && (vf->vout_state == vf_open)) vf->vout_state = vf_end ; diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c index 0660afb6..42ab941a 100644 --- a/lib/vty_io_term.c +++ b/lib/vty_io_term.c @@ -181,6 +181,11 @@ uty_term_open(int sock_fd, union sockunion *su) if (0) uty_term_dont_lflow_ahead (vf->cli) ; + /* Hoover up any currently available input -- possibly Telnet commands/ + * escapes from the other end. + */ + uty_term_read(vf) ; + /* Say hello */ vty_hello(vty); @@ -215,13 +220,53 @@ uty_term_open(int sock_fd, union sockunion *su) * CMD_IO_ERROR -- any errors are dealt with by signalling the command loop. */ extern cmd_return_code_t -uty_term_fetch_command_line(vio_vf vf, cmd_action action, cmd_context context) +uty_term_fetch_command_line(vio_vf vf, cmd_action action) { VTY_ASSERT_LOCKED() ; qassert(vf->vin_state == vf_open) ; - return uty_cli_want_command(vf->cli, action, context) ; + return uty_cli_want_command(vf->cli, action) ; +} ; + +/*------------------------------------------------------------------------------ + * Command has completed. + * + * If is cst_in_progress: + * + * -- set cst_complete + * + * -- collect current context + * + * -- if is cos_idle then force cos_active -- the obuf etc. will be + * empty, but we want to make sure that all other buffers empty out, + * and that the output side will signal when that's so. + * + * -- kick write_ready to make sure that things move along. + */ +extern void +uty_term_cmd_complete(vio_vf vf, cmd_context context) +{ + vty_cli cli = vf->cli ; + + VTY_ASSERT_LOCKED() ; + + if (cli->state == cst_in_progress) + { + cli->state = cst_complete ; + + if (cli->out_state == cos_idle) + { + vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ + cli->out_state = cos_active ; + } ; + + *cli->context = *context ; + cli->auth_context = ( (cli->context->node == AUTH_NODE) + || (cli->context->node == AUTH_ENABLE_NODE) ) ; + + uty_term_set_readiness(vf, write_ready) ; + } ; } ; /*------------------------------------------------------------------------------ @@ -269,28 +314,36 @@ uty_term_out_push(vio_vf vf, bool final) { vty_cli cli = vf->cli ; utw_ret_t done ; + cli_out_state_t out_state ; qassert(vf->vout_state == vf_open) ; qassert(!vf->blocking) ; - /* If squelching, dump anything we have in the obuf. + out_state = cli->out_state & cos_mask ; + + /* If squelching, discard anything we have in the obuf. */ if (vf->vio->cancel) vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ - /* If have something in the obuf that needs to be written, then if not - * already out_active, make sure the command line is clear, and set - * out_active. + /* If we have something in the obuf that needs to be written, then if + * is cos_idle, make sure the command line is clear, and set cos_active. + * + * Note that may be cos_idle | cos_monitor, for which we need to keep the + * monitor flag but update to cos_active -- the cli will already be wiped. */ - if (!cli->out_active && !vio_fifo_empty(vf->obuf)) + if (!vio_fifo_empty(vf->obuf) && (out_state == cos_idle)) { uty_cli_wipe(cli, 0) ; - cli->out_active = true ; + cli->out_state ^= (cos_active ^ cos_idle) ; vio_lc_counter_reset(cli->olc) ; } ; /* Give the terminal writing a shove. * + * Note that there may be stuff pending, and stuff in the cbuf, etc. We + * depend on the uty_term_write() to know all about what can be done. + * * If final, keep pushing while succeeds in writing without blocking. * * Note that is only called "final" when closing the vout, by which time @@ -310,21 +363,52 @@ uty_term_out_push(vio_vf vf, bool final) /* Deal with the result * - * If required and if not final make sure that write ready is set, so - * that the pselect() process can pursue the issue. + * If not failed and not final, set write ready if: + * + * not utw_done => output blocked. + * + * cli->out_state == cos_idle => CLI may run * * Return code depends on utw_xxx */ - if (done == utw_done) - return CMD_SUCCESS ; /* don't set write ready */ - if (done == utw_error) return CMD_IO_ERROR ; /* don't set write ready */ if (!final) - uty_term_set_readiness(vf, write_ready) ; + { + if ((done != utw_done) || (cli->out_state == cos_idle)) + uty_term_set_readiness(vf, write_ready) ; + } ; - return CMD_WAITING ; /* waiting for write ready */ + return (done == utw_done) ? CMD_SUCCESS : CMD_WAITING ; +} ; + +/*------------------------------------------------------------------------------ + * Completed the cancelling of command(s) and output. + * + * Caller is about to output "^C" to signal completion of the cancellation + * process -- sets the cli->state to cos_active, to allow final output to + * proceed, and the command loop continue. + * + * Clears any cos_paused ! + * + * If the current line is "drawn", make sure we are at the end of it, and then + * clear the "drawn" state, because "^C\n" is about to follow. + * + * Clear the line control, to make sure that cannot now interfere. + */ +extern void +uty_term_out_cancelled(vio_vf vf) +{ + vty_cli cli = vf->cli ; + + qassert((cli->out_state & cos_mask) == cos_cancel) ; + + cli->out_state = cos_active | (cli->out_state & cos_monitor) ; + + vio_lc_counter_reset(cli->olc) ; + + uty_cli_cancel(cli, false) ; } ; /*------------------------------------------------------------------------------ @@ -377,8 +461,8 @@ uty_term_close_reason(vio_vf vf, const char* reason) { vio_lc_clear(vf->cli->olc) ; - vf->cli->mon_active = false ; /* stamp on any monitor output */ - + vf->cli->out_state = cos_idle ; /* stamp on monitor or other output + * clear cancel and paused */ uty_cli_out(vf->cli, "%% %s%s", reason, uty_cli_newline) ; } ; @@ -487,7 +571,7 @@ uty_term_read_ready(vio_vfd vfd, void* action_info) qassert(vfd == vf->vfd) ; - vf->cli->paused = false ; /* read ready clears paused */ + vf->cli->out_state &= ~cos_paused ; /* read ready clears paused */ uty_term_read(vf) ; uty_term_ready(vf) ; @@ -547,6 +631,7 @@ uty_term_ready(vio_vf vf) { vty_readiness_t ready ; utw_ret_t done, done_before ; + cli_out_state_t out_state ; VTY_ASSERT_LOCKED() ; @@ -562,6 +647,7 @@ uty_term_ready(vio_vf vf) * This is because the CLI may generate more to write, and writing stuff * away may release the CLI. */ + out_state = vf->cli->out_state ; done = utw_null ; do { @@ -595,9 +681,9 @@ uty_term_ready(vio_vf vf) uty_term_set_readiness(vf, ready) ; - /* Signal the command loop if not waiting for write any more. + /* Signal the command loop if the cli->out_state has changed significantly */ - if ((ready & write_ready) == 0) + if ((vf->cli->out_state == cos_idle) && (out_state != cos_idle)) uty_cmd_signal(vf->vio, CMD_SUCCESS) ; } ; @@ -618,7 +704,7 @@ uty_term_read_timeout(vio_timer timer, void* action_info) keystroke_stream_set_eof(vf->cli->key_stream, true) ; /* timed out */ - vf->cli->paused = false ; + vf->cli->out_state &= ~cos_paused ; uty_term_ready(vf) ; @@ -639,7 +725,7 @@ uty_term_write_timeout(vio_timer timer, void* action_info) VTY_ASSERT_LOCKED() ; - vf->cli->paused = false ; + vf->cli->out_state &= ~cos_paused ; uty_vf_error(vf, verr_to_vout, 0) ; @@ -659,9 +745,9 @@ vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when) cli = timer_info ; assert(cli->pause_timer == qtr) ; - if (cli->paused) + if (cli->out_state & cos_paused) { - cli->paused = false ; + cli->out_state ^= cos_paused ; uty_term_ready(cli->vf) ; } ; @@ -675,15 +761,13 @@ vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when) */ /*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the keystroke stream + * Hoover up input and shovel into the keystroke stream * * NB: need not be in the term_ready path. Indeed, when the VTY_TERMINAL is * initialised, this is called to suck up any telnet preamble. * - * NB: the terminal is permanently read-ready, so will keep calling this - * until all input is hoovered up. For real terminals it is assumed that - * reading a lump of bytes this small will immediately empty the input - * buffer. + * NB: the terminal is permanently read-ready, so the keystroke stream + * should be permanently topped up. * * When reaches EOF on the input eof is set in the keystroke stream, and the * vfd is read closed. Read closing the vfd turns off any timeout and prevents @@ -691,23 +775,16 @@ vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when) * NOT set vf->vin_state to vf_end, because that is not true until the * keystroke stream is empty. Once eof is set in the keystroke stream, this * code will not attempt any further input from the vfd. - * - * Returns: 0 => nothing available - * > 0 => read at least one byte - * == -1 => I/O error - * == -2 => hit EOF (without reading anything else) */ -extern int +extern void uty_term_read(vio_vf vf) { keystroke_stream stream ; - unsigned char buf[500] ; - int get ; stream = vf->cli->key_stream ; if (keystroke_stream_met_eof(stream)) - return -2 ; /* already seen EOF */ + return ; /* already seen EOF */ if (vf->vin_state != vf_open) { @@ -716,33 +793,51 @@ uty_term_read(vio_vf vf) * force it to EOF. */ keystroke_stream_set_eof(vf->cli->key_stream, false) ; - return -2 ; + return ; } ; /* OK: read from input and pass result to keystroke stream. + * + * Uses a relatively small buffer, which should be fine for actual + * terminals ! */ - get = read_nb(vio_vfd_fd(vf->vfd), buf, sizeof(buf)) ; - /* -1 <=> error, -2 <=> EOF */ - if (get != 0) + while (1) { - if (get == -1) + unsigned char buf[500] ; + int get ; + + get = read_nb(vio_vfd_fd(vf->vfd), buf, sizeof(buf)) ; + /* -1 <=> error, -2 <=> EOF */ + if (get == 0) + return ; + + if (get != -1) { - /* Error: signal to command loop and force key_stream empty */ - uty_vf_error(vf, verr_io_vin, errno) ; - keystroke_stream_set_eof(vf->cli->key_stream, false) ; - /* not timed-out */ + /* Not error. get < 0 => EOF and that is set in key_stream. + */ + keystroke_input(vf->cli->key_stream, buf, get) ; } else { - /* Not error. get < 0 => EOF and that is set in key_stream. */ - keystroke_input(vf->cli->key_stream, buf, get) ; + /* Error: signal to command loop and force key_stream empty + */ + uty_vf_error(vf, verr_io_vin, errno) ; + keystroke_stream_set_eof(vf->cli->key_stream, false) ; + /* not timed-out */ } ; - if (get < 0) - vio_vfd_read_close(vf->vfd) ; - } ; + if (get > 0) + continue ; /* hoover up all there is. */ + + /* Error or EOF -- close the input half of the socket, and we're done. + * + * NB: this does not affect the vin_state, which (if not error) continues + * vf_open until hits EOF on the keystroke stream. + */ + vio_vfd_read_close(vf->vfd) ; - return get ; + return ; + } ; } ; /*============================================================================== @@ -752,8 +847,8 @@ uty_term_read(vio_vf vf) * * cli->cbuf -- command line -- reflects the status of the command line * - * vf->obuf -- command output -- which is written to the file only while - * out_active, and not blocked in more_wait. + * vf->obuf -- command output -- which is written to the terminal only while + * cos_active. * * The cli output takes precedence. * @@ -786,10 +881,14 @@ uty_term_mon_write(vio_vf vf) /*------------------------------------------------------------------------------ * Write as much as possible of what there is. * - * Move to more_wait if required. + * If there is anything in the CLI buffer, try to empty that. + * + * If is cos_monitor, try to empty the mbuf. * - * If is cli->flush, then when all buffers are emptied out, clears itself and - * the out_active flag. + * If cos_active, try to empty the obuf. Move to more_wait if required. + * + * If is not "in_progress", then when all buffers are emptied out, clears + * itself and the cos_active state. * * Returns: * @@ -833,10 +932,7 @@ uty_term_write(vio_vf vf) vio_fifo_clear(cli->cbuf, false) ; vio_lc_clear(cli->olc) ; - cli->out_active = false ; - - cli->more_wait = false ; - cli->more_enter = false ; + cli->out_state = cos_idle ; /* stamps on everything */ return utw_error ; } ; @@ -863,13 +959,13 @@ uty_term_write(vio_vf vf) /* Next: if there is monitor output to deal with, deal with it. * - * Note that the mon_active flag is set under VTY_LOCK(), so do not + * Note that the cos_monitor state is set under VTY_LOCK(), so do not * need to LOG_LOCK() to discover whether there is anything to do. * * But the vio->mbuf is filled under LOG_LOCK(), so need to write it * under the same. */ - if (cli->mon_active) + if (cli->out_state & cos_monitor) { LOG_LOCK() ; @@ -886,13 +982,13 @@ uty_term_write(vio_vf vf) return utw_error ; } ; - uty_cli_post_monitor(vf->cli) ; + uty_cli_post_monitor(vf->cli) ; /* clears monitor output state */ } ; - /* If not out_active, or in more_wait, then we are done, waiting for some - * external event to move things on. + /* If not cos_active we are done, waiting for some external event to move + * things on. */ - if ((!cli->out_active) || (cli->more_wait)) + if (cli->out_state != cos_active) return utw_done ; /* can do no more */ /* Push the output fifo and any complete line fragments that may be buffered @@ -903,9 +999,8 @@ uty_term_write(vio_vf vf) * even if the fifo is empty -- this deals with any parts of a complete * line that may be held in the line control due to counter exhaustion. * - * If the fifo is or becomes empty, then if is cli->flush, flush out any - * incomplete line which may be held in the line control -- in effect, - * cli->flush is a phantom '\n' at the end of the output fifo ! + * If the fifo is or becomes empty, then if the command has completed, flush + * out any incomplete line which may be held in the line control. */ vio_fifo_set_hold_mark(vf->obuf) ; /* released in uty_term_write_lc() */ @@ -923,7 +1018,7 @@ uty_term_write(vio_vf vf) break ; } ; - if ((have == 0) && !cli->in_progress) + if ((have == 0) && (cli->state == cst_complete)) vio_lc_flush(cli->olc) ; ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ; @@ -957,25 +1052,23 @@ uty_term_write(vio_vf vf) /* Exciting stuff: there is nothing left to output... * * ...with the sole possible exception of an incomplete line buffered - * in the line control, which can do nothing about until there is more - * to be output, or the output is flushed... + * in the line control -- if not cst_complete. * - * ...if not cli->flush, we are stopped, waiting for something else to - * happen. + * ...if is cst_complete, all output has gone, so can fall back to cos_idle. */ - qassert(!cli->more_wait && !cli->more_enter) ; - - if (cli->in_progress) - return utw_done ; /* can do no more */ + qassert(cli->out_state = cos_active) ; - /* Even more exciting: is !cli->in_progress ! - * - * This means that any incomplete line must have been flushed, above. - * So all buffers MUST be empty. - */ - qassert(vio_fifo_empty(vf->obuf) && vio_lc_is_empty(cli->olc)) ; + if (cli->state == cst_complete) + { + /* Even more exciting: is cst_complete + * + * This means that any incomplete line must have been flushed, above. + * So all buffers MUST be empty. + */ + qassert(vio_fifo_empty(vf->obuf) && vio_lc_is_empty(cli->olc)) ; - cli->out_active = false ; + cli->out_state = cos_idle ; + } ; return utw_done ; /* absolutely all done */ } ; diff --git a/lib/vty_io_term.h b/lib/vty_io_term.h index 78d37b18..ac5620e3 100644 --- a/lib/vty_io_term.h +++ b/lib/vty_io_term.h @@ -56,14 +56,16 @@ extern void uty_term_new(vty_io vio, int sock_fd) ; extern cmd_return_code_t uty_term_fetch_command_line(vio_vf vf, - cmd_action action, cmd_context context) ; + cmd_action action) ; +extern void uty_term_cmd_complete(vio_vf vf, cmd_context context) ; extern cmd_return_code_t uty_term_out_push(vio_vf vf, bool final) ; +extern void uty_term_out_cancelled(vio_vf vf) ; extern uint uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth) ; extern cmd_return_code_t uty_term_read_close(vio_vf vf, bool final) ; extern void uty_term_close_reason(vio_vf vf, const char* reason) ; extern cmd_return_code_t uty_term_write_close(vio_vf vf, bool final); -extern int uty_term_read(vio_vf vf) ; +extern void uty_term_read(vio_vf vf) ; extern void uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) ; extern qtimer_action vty_term_pause_timeout ; |