summaryrefslogtreecommitdiffstats
path: root/lib/command_queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/command_queue.c')
-rw-r--r--lib/command_queue.c586
1 files changed, 510 insertions, 76 deletions
diff --git a/lib/command_queue.c b/lib/command_queue.c
index 5f14abae..b80b2c9f 100644
--- a/lib/command_queue.c
+++ b/lib/command_queue.c
@@ -18,137 +18,571 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
-#include <zebra.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.
+ *
+ * The command loop is a form of co-routine, which is "called" by sending a
+ * message (or by legacy threads events). In the multi-pthread world, the
+ * command loop may run in either the vty_cli_nexus or the vty_cmd_nexus, and
+ * may be passed from one to the other by sending a message.
+ *
+ * The command loop is usually in one of three states:
+ *
+ * vc_running -- the command loop is running, doing things.
+ *
+ * In this state the vty and the vty->exec are in the hands
+ * of the command loop.
+ *
+ * The vty->vio is always accessed under VTY_LOCK().
+ *
+ * Note that one of the things the command loop may be
+ * doing is waiting for a message to be picked up (or
+ * a legacy thread event to be dispatched).
+ *
+ * vc_waiting -- the command loop is waiting for something to happen
+ * (typically some I/O), and will stay there until a
+ * message is sent (or a legacy threads event).
+ *
+ * To be waiting the command loop must have entered "hiatus"
+ * and then exited.
+ *
+ * When a command loop is or has been stopped, to goes to vc_stopped state.
+ *
+ * There are further issues:
+ *
+ * * In the mult-pthreaded world, commands may be parsed in either pthread,
+ * but most are executed in the Routing thread. So may switch pthreads
+ * after parsing a command -- by sending a message.
+ *
+ * * opening pipes is done in the vty_cli_nexus, in case of any possible
+ * blocking and because the pselect() stuff is all done in the
+ * vty_cli_nexus.
+ *
+ * * all output is via fifo buffers -- when output is pushed, as much as
+ * possible is done immediately, but if necessary the pselect() process
+ * (in the vty_cli_nexus) is left to keep things moving.
+ *
+ * In vty_io_basic() it is possible to set read/write ready and associated
+ * timeouts while running in the vty_cmd_nexus, but note that this is done
+ * by sending messages to the CLI thread (if multi-pthreaded).
+ *
+ * The smooth running of the command handling depends on the continued
+ * running of the CLI thread.
+ *
+ * To close a VTY must (eventually) arrange for the state to not be vc_running.
+ * While a vty is vc_running, the command loop may be in either nexus, and
+ * may be:
+ *
+ * - on the vty_cli_nexus queue (or the combined queue)
+ * - executing cq_process() in the vty_cli_nexus (or the single "both" nexus)
+ * - on the vty_cmd_nexus queue (if different)
+ * - executing cq_process() in the vty_cmd_nexus (if different)
+ *
+ * where being on the queue means that there is a message waiting to be
+ * picked up on that queue, which will end up calling cq_process().
+ *
+ * Or, in the legacy threads world:
+ *
+ * - on the event queue -- waiting for event handler to call cq_process()
+ * - executing cq_process()
+ *
+ * To bring the command loop to a stop will call cq_revoke to revoke any
+ * pending message or pending legacy event. If that succeeds, then the
+ * command loop can be stopped immediately. If not, then vc_running implies
+ * it is executing in cq_process() in one of the nexuses.
+ */
/*------------------------------------------------------------------------------
- * 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, cmd_exec_state_t state, cmd_return_code_t ret) ;
-struct cq_command_args
+/*------------------------------------------------------------------------------
+ * Enqueue vty to enter the command loop -- must be exec_null
+ *
+ * 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).
+ */
+extern void
+cq_loop_enter(vty vty, cmd_return_code_t ret)
{
- enum cmd_return_code ret ; /* return code from command */
+ VTY_ASSERT_CLI_THREAD() ;
+
+ qassert(vty->exec->state == exec_null) ;
+
+ cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ;
} ;
-MQB_ARGS_SIZE_OK(cq_command_args) ;
/*------------------------------------------------------------------------------
- * Prototypes
+ * Continue (resume) at hiatus -- must be exec_hiatus
*/
-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_continue(vty vty, cmd_return_code_t ret)
+{
+ VTY_ASSERT_CLI_THREAD() ;
+
+ qassert(vty->exec->state == exec_hiatus) ;
+
+ cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+} ;
/*------------------------------------------------------------------------------
- * Enqueue vty and argv[] for execution in given nexus.
+ * Enqueue vty for execution in given nexus or issue thread event.
+ *
+ * Will execute in the current exec->state, passing in the given return
+ * code.
+ *
+ * Note that the vio->state *must* be vc_running.
*/
-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 ;
+
+ qassert((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
+ {
+ qassert(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 message is revoked, then the state is set vc_stopped.
+ * We revoke when stopping a command loop -- see cq_revoke, below. If the
+ * message is revoked at any other time then the command loop is, indeed,
+ * stopped -- the I/O side may plow on until buffers empty, but the
+ * command loop will not cq_continue(). At
*/
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) ;
+
+ qassert(vty->exec->cq.mqb == mqb) ;
if (flag == mqb_action)
- {
- args->ret = cmd_dispatch_call(vty) ;
- assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */
- }
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ /* do not touch vty on way out */
else
- args->ret = CMD_QUEUED ;
+ {
+ /* Revoke action. */
+ mqb_free(mqb) ;
+ vty->exec->cq.mqb = NULL ;
+
+ vty_cmd_set_stopped(vty) ; /* enforced stop of loop */
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Deal with command message -- in the legacy threads world.
+ */
+static int
+cq_thread(struct thread* thread)
+{
+ vty vty = THREAD_ARG(thread) ;
+
+ qassert(vty->exec->cq.thread == thread) ;
+
+ vty->exec->cq.thread = NULL ;
- mqb_set_action(mqb, cq_return) ;
- mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ;
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ /* 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.
+ * This is essentially a coroutine, where the state is in the vty, noting that:
*
- * 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.
+ * vty->exec->state is the "return address"
+ * vty->exec->ret is the "return code"
*
- * 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.
+ * The command loop runs at the mqueue level in the qpnexus world, or is
+ * driven by "events" in the legacy threads world. The two ways into the
+ * loop are:
+ *
+ * cq_loop_enter() -- called once and once only to start the loop.
+ *
+ * cq_continue() -- called when some I/O process has completed and
+ * the loop is in hiatus, waiting for I/O.
+ *
+ * The one way out of the loop is when it hits a hiatus, which is triggered
+ * by any operation not returning CMD_SUCCESS. In hiatus, vty_cmd_hiatus()
+ * is called and given the current return code to deal with. It will
+ * return:
+ *
+ * CMD_SUCCESS => can proceed to fetch the next command
+ * CMD_WAITING => must leave the command loop, waiting for I/O
+ * CMD_STOP => close the vty and leave command loop
+ * CMD_IO_ERROR => loop back and deal with the error
+ *
+ * The cq_loop_enter() and cq_continue() operations send a message (to the
+ * cli thread, in the mult-pthreaded world), whose action routine is to call
+ * cq_process().
+ *
+ * In the multi-pthreaded world, opening and closing files MUST be done in the
+ * cli thread. Most commands MUST be executed in the cmd thread. So the
+ * command loop is passed between the threads in a number of places. (To keep
+ * life simple, switches back to the cli thread if ends up waiting for I/O.)
+ *
+ * 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: on exit from cq_process the VTY may have been closed down and released,
+ * so do NOT depend on its existence.
*/
static void
-cq_return(mqueue_block mqb, mqb_flag_t flag)
+cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
{
- struct vty *vty ;
- struct cq_command_args* args ;
+ cmd_exec exec = vty->exec ;
+ cmd_parsed parsed = exec->parsed ;
+
+ /* 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_hiatus,
+ * with the current ret (which will switch to 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(state)
+ {
+ /*--------------------------------------------------------------------
+ * Should not get here in exec_null state.
+ */
+ case exec_null:
+ zabort("exec state == exec_null") ;
+ break ;
+
+ /*--------------------------------------------------------------------
+ * 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 ;
+
+ 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) ;
+
+ 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 ;
+ } ;
+
+ /* reflection now */
+ if (exec->reflect)
+ {
+ ret = vty_cmd_reflect_line(vty) ;
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
+ break ; /* CMD_IO_ERROR or CMD_HIATUS */
+ } ;
+
+ fall_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)
+ {
+ /* 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 */
+ } ;
+
+ fall_through ; /* with ret == CMD_SUCCESS */
+
+ /*--------------------------------------------------------------------
+ * 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) ;
+
+ /* Standard command handling */
+#ifdef CONSUMED_TIME_CHECK
+ {
+ RUSAGE_T before;
+ RUSAGE_T after;
+ unsigned long realtime, cputime;
+
+ GETRUSAGE(&before);
+#endif /* CONSUMED_TIME_CHECK */
+
+ 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. */
+ zlog(NULL, LOG_WARNING,
+ "SLOW COMMAND: command took %lums (cpu time %lums): %s",
+ realtime/1000, cputime/1000,
+ qs_make_string(exec->action->line)) ;
+ } ;
+#endif /* CONSUMED_TIME_CHECK */
+ } ;
+
+ fall_through ;
+
+ /*--------------------------------------------------------------------
+ * Command has completed -- if successful, 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) ;
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
+ break ; /* stop */
- assert(vty_cli_nexus) ; /* must be running qnexus-wise */
+ state = exec_fetch ;
+ continue ;
- vty = mqb_get_arg0(mqb) ;
- args = mqb_get_args(mqb) ;
+ /*--------------------------------------------------------------------
+ * Deal with the "special" commands
+ */
+ case exec_special:
+ if (exec->locus != vty_cli_nexus)
+ return cq_enqueue(vty, vty_cli_nexus, exec_special, ret) ;
- /* signal end of command */
- cmd_post_command(vty, args->ret) ;
- vty_queued_result(vty, args->ret) ;
+ ret = vty_cmd_special(vty) ;
+ if (ret != CMD_SUCCESS)
+ break ;
-//if (qpthreads_enabled)
-// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE);
+ state = exec_done_cmd ;
+ continue ;
- mqb_free(mqb);
-}
+ /*--------------------------------------------------------------------
+ * 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 ;
+
+ /*----------------------------------------------------------------------
+ * Unknown exec->state !
+ */
+ default:
+ zabort("unknown exec->state") ;
+ break ;
+ } ;
+
+ /* Have broken out of the switch() => exec_hiatus */
+ state = exec_hiatus ;
+ } ;
+} ;
/*------------------------------------------------------------------------------
- * 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.
+ *
+ * In the single-threaded world (including legacy threads), messages or events
+ * are used to enter or resume the command loop. If a message or event can
+ * be revoked, then the command loop is effectively stopped.
*
- * Revokes in vty_cmd_nexus -- so before command is started
- * and in vty_cli_nexus -- so after command has completed
+ * In the multi-threaded world, messages are used to enter or resume the
+ * command loop, and to transfer it to and from the cli and cmd threads. If
+ * a message can be revoked, the command loop is effectively stopped.
*
- * Can do nothing about any command actually being executed in the
- * vty_cmd_nexus.
+ * Note that the revoke does not affect any vty_cli_nexus messages associated
+ * with the vty_io_basic operations.
+ *
+ * If succeeds in revoking, then will have set the state to vc_stopped, and
+ * will have released the message block or legacy thread object.
+ *
+ * Returns: true <=> have revoked a pending message/event
+ *
+ * NB: if no command loop has ever been started for this vty, then will not
+ * find anything to revoke.
*/
-void
-cq_revoke(struct vty *vty)
+extern bool
+cq_revoke(vty vty)
{
- if (vty_cli_nexus)
+ int ret ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ if (vty_nexus)
{
- mqueue_revoke(vty_cmd_nexus->queue, vty) ;
- mqueue_revoke(vty_cli_nexus->queue, vty) ;
+ ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ;
+
+ if ((ret == 0) && vty_multi_nexus)
+ ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ;
+ }
+ else
+ {
+ ret = thread_cancel_event(vty_master, vty) ;
+
+ if (ret != 0)
+ {
+ /* Make sure in same state as after mqueue_revoke. */
+ vty_cmd_set_stopped(vty) ;
+ vty->exec->cq.thread = NULL ;
+ } ;
} ;
-}
+
+ /* If revoked message/event, the command loop is stopped. */
+ if (ret != 0)
+ vty->exec->state = exec_stopped ;
+
+ return (ret != 0) ;
+} ;
+
+