From 5cae7eea451f2b7d65b5892e2c1dafc70f8b836e Mon Sep 17 00:00:00 2001 From: Chris Hall Date: Sun, 13 Feb 2011 23:11:45 +0000 Subject: Second tranche of updates for pipework branch. modified: bgpd/bgp_connection.c modified: bgpd/bgp_debug.c modified: bgpd/bgp_engine.h modified: bgpd/bgp_main.c modified: bgpd/bgp_packet.c modified: bgpd/bgp_peer.c modified: bgpd/bgp_route.c modified: bgpd/bgp_routemap.c modified: bgpd/bgp_session.c modified: bgpd/bgp_vty.c modified: bgpd/bgpd.c modified: bgpd/bgpd.h modified: configure.ac modified: isisd/dict.h modified: isisd/isis_misc.c modified: isisd/isis_routemap.c modified: isisd/isis_spf.c modified: lib/Makefile.am modified: lib/command.c modified: lib/command.h modified: lib/command_execute.h modified: lib/command_parse.c modified: lib/command_parse.h modified: lib/command_queue.c modified: lib/command_queue.h modified: lib/elstring.h modified: lib/heap.c modified: lib/if.c modified: lib/if.h modified: lib/keychain.c modified: lib/keystroke.c modified: lib/keystroke.h modified: lib/list_util.c modified: lib/list_util.h modified: lib/log.c modified: lib/log.h modified: lib/memory.c modified: lib/memory.h modified: lib/memtypes.c modified: lib/misc.h modified: lib/mqueue.c modified: lib/mqueue.h deleted: lib/node_type.h modified: lib/pthread_safe.c modified: lib/qfstring.c modified: lib/qiovec.c modified: lib/qiovec.h modified: lib/qpath.c modified: lib/qpnexus.c modified: lib/qpnexus.h modified: lib/qpselect.c modified: lib/qpthreads.h modified: lib/qstring.c modified: lib/qstring.h modified: lib/qtime.c modified: lib/qtime.h modified: lib/qtimers.c modified: lib/qtimers.h modified: lib/routemap.c modified: lib/symtab.h modified: lib/thread.h deleted: lib/uty.h modified: lib/vector.c modified: lib/vector.h modified: lib/version.h.in modified: lib/vio_fifo.c modified: lib/vio_fifo.h modified: lib/vio_lines.c modified: lib/vio_lines.h modified: lib/vty.c modified: lib/vty.h modified: lib/vty_cli.c modified: lib/vty_cli.h modified: lib/vty_io.c modified: lib/vty_io.h modified: lib/vty_io_basic.c modified: lib/vty_io_basic.h modified: lib/vty_io_file.c modified: lib/vty_io_file.h modified: lib/vty_io_shell.c modified: lib/vty_io_term.c modified: lib/vty_io_term.h modified: lib/vty_local.h modified: lib/vty_pipe.c modified: lib/workqueue.h modified: lib/zebra.h modified: ospf6d/ospf6_lsa.c modified: ripngd/ripngd.c modified: tests/test-list_util.c modified: tests/test-vector.c modified: vtysh/vtysh.c modified: vtysh/vtysh_config.c --- lib/command_queue.c | 580 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 504 insertions(+), 76 deletions(-) (limited to 'lib/command_queue.c') diff --git a/lib/command_queue.c b/lib/command_queue.c index 5f14abae..0a09d950 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -19,136 +19,564 @@ * Boston, MA 02111-1307, USA. */ -#include +#include "zconfig.h" /* for CONSUMED_TIME_CHECK */ +#include "misc.h" -#include "mqueue.h" #include "qpnexus.h" -#include "memory.h" +#include "mqueue.h" +#include "thread.h" + #include "command_queue.h" #include "command_execute.h" -#include "vty.h" -#include "uty.h" -#include "vector.h" -#include "qstring.h" +#include "vty_command.h" +#include "vty_io.h" +#include "log.h" + +/*============================================================================== + * This command loop processes commands for VTY_TERMINAL and VTY_SHELL_SERVER. + * + * Commands appear from those sources, driven by socket I/O. Once a command + * line is ready it is passed out of the I/O pselect/select driven code + * as a message, and enters the command loop. + * + * If pipes are involved that is all dealt with in the command loop, which + * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and + * VTY_SHELL_SERVER. + * + * There are further issues: + * + * 1) in the qpthreads world, commands are parsed in the CLI thread, but most + * are executed in the Routing thread. So parsed commands are passed, by + * message between threads. + * + * 2) input is expected to be non-blocking -- so the command loop will + * exit if a command line cannot be delivered immediately, and will be + * returned to later. + * + * 3) while a VTY is in the command loop it is marked vio->cmd_running. + * + * While that is true, the vty and the vty->exec are in the hands + * of the command loop. The vty->vio is always accessed under VTY_LOCK(). + * + * 4) opening pipes is done in the CLI thread, in case of any possible + * blocking. + * + * 5) all output is to fifo buffers -- when output is pushed the CLI side + * is kicked to manage all output via pselect/select. + * + * In vty_io_basic() it is possible to set read/write ready and associated + * timeouts while running in the CMD thread, but that too depends on the + * passing of messages to the CLI thread. + * + * The smooth running of the command handling depends on the continued + * running of the CLI thread. + * + * To close a VTY must (eventually) arrange for vio->cmd_running to be cleared. + * While a vty is vio->cmd_running, it must be in one of these states: + * + * - on the vty_cli_nexus queue (or the combined queue) + * - executing in the vty_cli_nexus (or the single "both" nexus) + * - on the vty_cmd_nexus queue (if different) + * - executing in the vty_cmd_nexus (if different) + * + * Or, in the legacy threads world: + * + * - on the event queue + * - executing + * + * Where there is only one pthread (and in the legacy threads world) things are + * easy. + * + * + * This revoke runs in the CLI thread, and will catch the message if it is + * on either queue, and vty_revoke() will deal with it -- still in the CLI + * thread. + * + * The command cannot be running in the CLI thread, since that is where we + * are ! + * + * That leaves the command running in the CMD thread. That will run to + * completion... the VTY may be closed in the meantime, which will shut down + * the reading side, so the command loop will come to a halt quite quickly. + * Note, however, that the smooth running of this process requires the CLI + * thread and its messages to be + * + * + */ + + + /*------------------------------------------------------------------------------ - * Form of message passed with command to be executed + * Prototypes */ +static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state, + cmd_return_code_t ret) ; +static void cq_action(mqueue_block mqb, mqb_flag_t flag); +static int cq_thread(struct thread* thread) ; +static void cq_process(vty vty) ; -struct cq_command_args +/*------------------------------------------------------------------------------ + * Enqueue vty for parse and execution of a command. + * + * Sets the vio->cmd_running flag, which will be cleared only when the + * command is completed (including any nested pipes etc.) or when the vty + * is blocked on input or it is revoked. + * + * Note that from now on, exec->ret reflects the state of the return + * code when the vty was last enqueued. + * + * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main + * part of the VTY or with the vty->exec state. + */ +extern void +cq_dispatch(vty vty, cmd_do_t to_do, qstring line) { - enum cmd_return_code ret ; /* return code from command */ + VTY_ASSERT_CLI_THREAD() ; + + vty->exec->to_do = to_do ; + vty->exec->line = line ; + vty->vio->cmd_running = true ; + cq_enqueue(vty, vty_cli_nexus, + (to_do == cmd_do_command) ? exec_parse + : exec_special, CMD_SUCCESS) ; } ; -MQB_ARGS_SIZE_OK(cq_command_args) ; /*------------------------------------------------------------------------------ - * Prototypes + * Enqueue vty for fetching a command line from an in_pipe which has just + * received more input. + * + * Sets the vio->cmd_running flag, which will be cleared only when the + * command is completed (including any nested pipes etc.) or when the vty + * is blocked on input or it is revoked. + * + * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main + * part of the VTY or with the vty->exec state. */ -static void cq_action(mqueue_block mqb, mqb_flag_t flag); -static void cq_return(mqueue_block mqb, mqb_flag_t flag); +extern void +cq_go_fetch(vty vty) +{ + VTY_ASSERT_CLI_THREAD() ; + + vty->vio->cmd_running = true ; + cq_enqueue(vty, vty_cli_nexus, exec_fetch, CMD_SUCCESS) ; +} ; /*------------------------------------------------------------------------------ - * Enqueue vty and argv[] for execution in given nexus. + * Enqueue vty for execution in given nexus or issue thread event. + * + * Note that preserves the return code state. */ -void -cq_enqueue(struct vty *vty, qpn_nexus dst) +static void +cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, + cmd_return_code_t ret) { - struct cq_command_args* args ; - mqueue_block mqb ; + cmd_exec exec = vty->exec ; + + assert(vty->vio->cmd_running) ; + assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + exec->locus = dst ; + exec->state = state ; + exec->ret = ret ; - mqb = mqb_init_new(NULL, cq_action, vty) ; - args = mqb_get_args(mqb) ; + if (vty_nexus) + { + mqueue_block mqb ; - args->ret = CMD_QUEUED ; + if ((mqb = exec->cq.mqb) == NULL) + mqb = exec->cq.mqb = mqb_init_new(NULL, cq_action, vty) ; - mqueue_enqueue(dst->queue, mqb, 0) ; -} + mqueue_enqueue(dst->queue, mqb, (dst == vty_cmd_nexus) ? mqb_priority + : mqb_ordinary) ; + } + else + { + assert(vty_cli_nexus == vty_cmd_nexus) ; + exec->cq.thread = thread_add_event(vty_master, cq_thread, vty, 0) ; + } ; +} ; /*------------------------------------------------------------------------------ - * Dispatch a command from the message queue block + * Deal with command message -- in the qpthreads world. * - * When done (or revoked/deleted) return the message, so that the sender knows - * that the command has been dealt with (one way or another). - * - * Note that if the command is revoked the return is set to CMD_QUEUED. + * Note that if the command is revoked.... */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) { - struct vty *vty; - struct cq_command_args* args ; + vty vty; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + assert(vty_nexus) ; /* must be running qnexus-wise */ vty = mqb_get_arg0(mqb); - args = mqb_get_args(mqb) ; + + assert(vty->exec->cq.mqb == mqb) ; + assert(vty->vio->cmd_running) ; if (flag == mqb_action) - { - args->ret = cmd_dispatch_call(vty) ; - assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */ - } - else - args->ret = CMD_QUEUED ; + return cq_process(vty) ; /* do not touch vty on way out */ + + mqb_free(mqb) ; + vty->exec->cq.mqb = NULL ; +} ; - mqb_set_action(mqb, cq_return) ; - mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; +/*------------------------------------------------------------------------------ + * Deal with command message -- in the legacy threads world. + * + * Note that if the command is revoked.... + */ +static int +cq_thread(struct thread* thread) +{ + vty vty = THREAD_ARG(thread) ; + + assert(vty->exec->cq.thread == thread) ; + assert(vty->vio->cmd_running) ; + + vty->exec->cq.thread = NULL ; + + cq_process(vty) ; /* do not touch vty on way out */ + + return 0 ; } ; /*------------------------------------------------------------------------------ - * Accept return from command which has completed. + * Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER. * - * The command line processing for the vty may be stalled (with read mode - * disabled) waiting for the return from the command. + * To get into the process loop, or to get back to it when more input has + * arrived a message is sent: * - * Do not care whether the message is being revoked or not... the command - * has completed and that must be signalled to the CLI. Any pending output - * is released. + * cli -> cli -- exec_parse -- when a command line has been gathered + * from input and is ready to be processed. * - * The command itself may have been revoked before it was executed. That - * makes no difference either... the output buffers will simply be empty. - * However, the return code is CMD_QUEUED, to signal the fact that the command - * was never executed. + * cli -> cli -- exec_fetch -- when was waiting for more input from an + * in_pipe of some sort. + * + * In the single-pthread world, where the vty_cli_nexus is the same as the + * vty_cmd_nexus (either because not running qpthreaded, or because are + * running in the legacy threads world), things are reasonably straightforward. + * The process runs to completion or until an in_pipe would block, and the + * above are the only messages in the system. + * + * In the multi-pthread world, things are more complicated... The above + * messages are sent, and in addition the following are sent back and forth to + * transfer between threads in the following states: + * + * cmd -> cli -- exec_open_pipes + * cli -> cmd -- exec_execute + * cmd -> cli -- exec_complete + * + * Note that this means that in the multi-pthread world, only one sort of + * message is sent to the vty_cmd_nexus. + * + * The vty_io_basic stuff allows the multi-pthread vty_cmd_nexus to set read + * and/or write ready state -- which may generate messages to the vty_cli_nexus. + * + * NB: if blocks in exec_fetch, then vty_cmd_fetch_line() will have cleared + * vio->cmd_running -- so on return from cq_process the vty MAY HAVE BEEN + * DELETED. */ static void -cq_return(mqueue_block mqb, mqb_flag_t flag) +cq_process(vty vty) { - struct vty *vty ; - struct cq_command_args* args ; + cmd_exec exec = vty->exec ; + cmd_parsed parsed = exec->parsed ; + cmd_return_code_t ret = exec->ret ; + + /* Have switch wrapped in a while(1) so that can change state by setting + * exec->state and doing "continue". + * + * Breaking out of the switch forces the exec state to exec_complete, + * with the current ret, in the CLI thread. + * + * The exec locus is either vty_cli_nexus or vty_cmd_nexus. If these + * are equal (including both NULL, in the legacy threads world), then is + * running single threaded -- otherwise is running multi-threaded. + */ + while (1) + { + switch(exec->state) + { + /*-------------------------------------------------------------------- + * Should not get here in exec_null state. + */ + case exec_null: + zabort("exec->state == exec_null") ; + break ; + + /*-------------------------------------------------------------------- + * Deal with the "spacial" commands + */ + case exec_special: + ret = vty_cmd_special(vty) ; + if (ret != CMD_SUCCESS) + break ; + + exec->state = exec_fetch ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Need another command to execute => in_pipe ! + * + * Note that at vin_depth == 0 this will return CMD_EOF, and will + * drop out of the loop exec_complete. + * + * Will also receive CMD_EOF if the VTY has been closed. + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_fetch_line() may set read and/or write ready -- so in + * vty_cmd_nexus may generate message to vty_cli_nexus. + */ + case exec_fetch: + ret = vty_cmd_fetch_line(vty) ; + + if (ret != CMD_SUCCESS) + { + /* If is CMD_WAITING, then the vty_cmd_fetch_line() will + * have prepared for command to be re-queued when there is more + * to be read. NB: vio->cmd_running has been cleared, so + * vty MAY HAVE BEEN DELETED ! + * + * If is CMD_EOF then is "closing" or reached EOF on top-most + * pipe. + */ + if (ret == CMD_WAITING) + return ; /* <<<< DONE, pro tem */ + + break ; /* => exec_complete */ + } ; + + if (exec->to_do != cmd_do_command) + { + exec->state = exec_special ; + continue ; + } ; + + exec->state = exec_parse ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Parse command in hand + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_reflect_line() may set read and/or write ready -- so in + * vty_cmd_nexus may generate message to vty_cli_nexus. + */ + case exec_parse: + cmd_tokenise(parsed, exec->line) ; + ret = cmd_parse_command(parsed, vty->node, + exec->parse_type | cmd_parse_execution) ; + 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 ; + + exec->state = exec_success ; + continue ; + } ; + + /* reflection now -- output always succeeds */ + if (exec->reflect_enabled) + vty_cmd_reflect_line(vty) ; + + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * 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) + { + exec->state = exec_open_pipes ; + + /* 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 */ + } ; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + exec->state = exec_execute ; + /* continue by falling through */ - vty = mqb_get_arg0(mqb) ; - args = mqb_get_args(mqb) ; + /*-------------------------------------------------------------------- + * Execute command in hand + * + * 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) ; - /* signal end of command */ - cmd_post_command(vty, args->ret) ; - vty_queued_result(vty, args->ret) ; + /* Standard command handling */ +#ifdef CONSUMED_TIME_CHECK + { + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; -//if (qpthreads_enabled) -// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); + GETRUSAGE(&before); +#endif /* CONSUMED_TIME_CHECK */ - mqb_free(mqb); -} + ret = cmd_execute(vty) ; + +#ifdef CONSUMED_TIME_CHECK + GETRUSAGE(&after); + realtime = thread_consumed_time(&after, &before, &cputime) ; + if (realtime > CONSUMED_TIME_CHECK) + /* Warn about CPU hog that must be fixed. */ + uzlog(NULL, LOG_WARNING, + "SLOW COMMAND: command took %lums (cpu time %lums): %s", + realtime/1000, cputime/1000, + qs_make_string(exec->line)) ; + } ; +#endif /* CONSUMED_TIME_CHECK */ + + if (ret != CMD_SUCCESS) + break ; /* stop */ + } ; + + exec->state = exec_success ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Command has completed successfully -- so push the output. + * + * If the vout_depth > vin_depth, pops the vout's -- deals with single + * command lines with a pipe output. + * + * Output cannot block, so this always succeeds. + * + * Then loop back to fetch another command line, if can. + * + * 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_success: + assert(ret == CMD_SUCCESS) ; + + vty_cmd_success(vty) ; + + exec->state = exec_fetch ; + continue ; + + /*-------------------------------------------------------------------- + * End of the command loop ! + * + * If multi-threaded: must return to the vty_cli_nexus. + */ + case exec_complete: + if (exec->locus != vty_cli_nexus) + break ; /* Will send back to the cli */ + + /* Now in the vty_cli_nexus */ + + exec->state = exec_null ; /* all done ! */ + + vty_cmd_loop_exit(vty, ret) ; /* clears vio->cmd_running */ + + return ; /* <<<< Finally finished ! */ + + /*---------------------------------------------------------------------- + * Unknown exec->state ! + */ + default: + zabort("unknown exec->state") ; + break ; + } ; + + /* Have broken out of the switch(). This means that for good or ill, + * the command is complete. If we are not in the vty_cli_nexus, need to + * send back to the vty_cli_nexus for handling. + * + * At all times we treat CMD_EOF and CMD_SUCCESS. + * + * Otherwise, can continue in exec_complete state. + */ + if (ret == CMD_EOF) + ret = CMD_SUCCESS ; + + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_complete, ret) ; + + exec->state = exec_complete ; + } ; +} ; /*------------------------------------------------------------------------------ - * Revoke any messages related to the given VTY -- if running qnexus-wise. + * Revoke any message associated with the given vty from the command queue. + * + * This is used when a VTY_TERMINAL or a VTY_SHELL_SERVER is being closed. + * + * See cq_process above for discussion of what messages there may be. At any + * time there is at most one message in flight. + * + * If we find a message in flight, then we vty_cmd_loop_exit() to bring things + * to a stop tidily. * - * Revokes in vty_cmd_nexus -- so before command is started - * and in vty_cli_nexus -- so after command has completed + * In the single-threaded world, expect that a command, once started will run + * to conclusion, or until blocked on an in_pipe. So after this revoke the + * vty should not be vio->cmd_running. * - * Can do nothing about any command actually being executed in the - * vty_cmd_nexus. + * In the multi-threaded world, this revoke will catch any vty which is on + * either the vty_cli_nexus or vty_cmd_nexus queues, and force completion. + * After this revoke, vio->cmd_running will be true iff the command is + * currently being executed in the vty_cmd_nexus -- we expect that to run to + * conclusion or block on an in_pipe, shortly, which will be collected when + * the vty_cli_nexus message queue is next processed. + * + * Note that the revoke does not affect any vty_cli_nexus messages associated + * with the vty_io_basic operations. */ -void -cq_revoke(struct vty *vty) +extern void +cq_revoke(vty vty) { - if (vty_cli_nexus) + int ret ; + + VTY_ASSERT_CLI_THREAD() ; + + if (vty_nexus) { - mqueue_revoke(vty_cmd_nexus->queue, vty) ; - mqueue_revoke(vty_cli_nexus->queue, vty) ; + ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ; + + if ((ret == 0) && (vty_cli_nexus != vty_cmd_nexus)) + ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ; + } + else + ret = thread_cancel_event(vty_master, vty) ; + + if (ret != 0) + { + vty->exec->state = exec_null ; + vty_cmd_loop_exit(vty, vty->exec->ret) ; } ; -} +} ; + + -- cgit v1.2.3