diff options
Diffstat (limited to 'lib/command_queue.c')
-rw-r--r-- | lib/command_queue.c | 368 |
1 files changed, 171 insertions, 197 deletions
diff --git a/lib/command_queue.c b/lib/command_queue.c index 0a09d950..f83e3291 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -43,7 +43,7 @@ * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and * VTY_SHELL_SERVER. * - * There are further issues: + * There are further issues: fix to current state TODO * * 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 @@ -53,7 +53,7 @@ * 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. + * 3) while a VTY is in the command loop it is marked vio->cmd_running. TODO * * 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(). @@ -72,7 +72,7 @@ * 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: + * While a vty is vio->cmd_running, it must be in one of these states: TODO * * - on the vty_cli_nexus queue (or the combined queue) * - executing in the vty_cli_nexus (or the single "both" nexus) @@ -111,69 +111,54 @@ * Prototypes */ static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state, - cmd_return_code_t ret) ; + 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) ; +static void cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) ; /*------------------------------------------------------------------------------ - * Enqueue vty for parse and execution of a command. + * Enqueue vty to enter the command loop -- must be exec_null * - * 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. + * 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_dispatch(vty vty, cmd_do_t to_do, qstring line) +cq_loop_enter(vty vty, cmd_return_code_t ret) { 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) ; + assert(vty->exec->state == exec_null) ; + + cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ; } ; /*------------------------------------------------------------------------------ - * 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. + * Continue (resume) at hiatus -- must be exec_hiatus */ extern void -cq_go_fetch(vty vty) +cq_continue(vty vty, cmd_return_code_t ret) { VTY_ASSERT_CLI_THREAD() ; - vty->vio->cmd_running = true ; - cq_enqueue(vty, vty_cli_nexus, exec_fetch, CMD_SUCCESS) ; + assert(vty->exec->state == exec_hiatus) ; + + cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ; } ; /*------------------------------------------------------------------------------ * Enqueue vty for execution in given nexus or issue thread event. * - * Note that preserves the return code state. + * Will execute in the current exec->state, passing in the given return + * code. */ static void cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, - cmd_return_code_t ret) + cmd_return_code_t ret) { cmd_exec exec = vty->exec ; - assert(vty->vio->cmd_running) ; assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ; exec->locus = dst ; @@ -200,7 +185,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, /*------------------------------------------------------------------------------ * Deal with command message -- in the qpthreads world. * - * Note that if the command is revoked.... + * Note that if the command is revoked.... TODO */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) @@ -212,11 +197,12 @@ cq_action(mqueue_block mqb, mqb_flag_t flag) vty = mqb_get_arg0(mqb); assert(vty->exec->cq.mqb == mqb) ; - assert(vty->vio->cmd_running) ; if (flag == mqb_action) - return cq_process(vty) ; /* do not touch vty on way out */ + return cq_process(vty, vty->exec->state, vty->exec->ret) ; + /* do not touch vty on way out */ + /* Revoke action. */ mqb_free(mqb) ; vty->exec->cq.mqb = NULL ; } ; @@ -224,7 +210,7 @@ cq_action(mqueue_block mqb, mqb_flag_t flag) /*------------------------------------------------------------------------------ * Deal with command message -- in the legacy threads world. * - * Note that if the command is revoked.... + * Note that if the command is revoked.... TODO */ static int cq_thread(struct thread* thread) @@ -232,63 +218,64 @@ 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 */ - + cq_process(vty, vty->exec->state, vty->exec->ret) ; + /* do not touch vty on way out */ return 0 ; } ; /*------------------------------------------------------------------------------ * Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER. * - * To get into the process loop, or to get back to it when more input has - * arrived a message is sent: + * This is essentially a coroutine, where the state is in the vty, noting that: + * + * vty->exec->state is the "return address" + * vty->exec->ret is the "return code" + * + * 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: * - * cli -> cli -- exec_parse -- when a command line has been gathered - * from input and is ready to be processed. + * cq_loop_enter() -- called once and once only to start the loop. * - * cli -> cli -- exec_fetch -- when was waiting for more input from an - * in_pipe of some sort. + * cq_continue() -- called when some I/O process has completed and + * the loop is in hiatus, waiting for I/O. * - * 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. + * 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: * - * 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_SUCCESS => can proceed to fetch the next command + * CMD_WAITING => must leave the command loop, waiting for I/O + * CMD_EOF => close the vty and leave command loop + * CMD_IO_ERROR => loop back and deal with the error * - * cmd -> cli -- exec_open_pipes - * cli -> cmd -- exec_execute - * cmd -> cli -- exec_complete + * 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(). * - * Note that this means that in the multi-pthread world, only one sort of - * message is sent to the vty_cmd_nexus. + * 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: 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_process(vty vty) +cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) { - cmd_exec exec = vty->exec ; - cmd_parsed parsed = exec->parsed ; - cmd_return_code_t ret = exec->ret ; + 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_complete, - * with the current ret, in the CLI thread. + * 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 @@ -296,79 +283,44 @@ cq_process(vty vty) */ while (1) { - switch(exec->state) + switch(state) { /*-------------------------------------------------------------------- * Should not get here in exec_null state. */ case exec_null: - zabort("exec->state == 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. + * 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) - { - /* 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 */ - } ; + break ; - if (exec->to_do != cmd_do_command) + if (exec->action->to_do != cmd_do_command) { - exec->state = exec_special ; + state = exec_special ; continue ; } ; - exec->state = exec_parse ; - /* continue by falling through */ + fall_through ; /* with ret == CMD_SUCCESS */ /*-------------------------------------------------------------------- * 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. + * If multi-threaded: may be in either thread. */ - case exec_parse: - cmd_tokenise(parsed, exec->line) ; - ret = cmd_parse_command(parsed, vty->node, - exec->parse_type | cmd_parse_execution) ; + 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) @@ -381,15 +333,20 @@ cq_process(vty vty) */ ret = CMD_SUCCESS ; - exec->state = exec_success ; + state = exec_done_cmd ; continue ; } ; - /* reflection now -- output always succeeds */ - if (exec->reflect_enabled) - vty_cmd_reflect_line(vty) ; + /* reflection now */ + if (exec->reflect) + { + ret = vty_cmd_reflect_line(vty) ; + + if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) + break ; + } ; - /* continue by falling through */ + fall_through ; /*-------------------------------------------------------------------- * Pipe work if any @@ -402,8 +359,6 @@ cq_process(vty vty) 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) ; @@ -414,11 +369,10 @@ cq_process(vty vty) break ; /* quit if open fails */ } ; - exec->state = exec_execute ; - /* continue by falling through */ + fall_through ; /* with ret == CMD_SUCCESS */ /*-------------------------------------------------------------------- - * Execute command in hand + * 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 @@ -428,8 +382,7 @@ cq_process(vty vty) 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))) + if ((exec->locus != vty_cmd_nexus) && (!cmd_is_direct(parsed))) return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ; /* Standard command handling */ @@ -449,59 +402,97 @@ cq_process(vty vty) realtime = thread_consumed_time(&after, &before, &cputime) ; if (realtime > CONSUMED_TIME_CHECK) /* Warn about CPU hog that must be fixed. */ - uzlog(NULL, LOG_WARNING, + zlog(NULL, LOG_WARNING, "SLOW COMMAND: command took %lums (cpu time %lums): %s", realtime/1000, cputime/1000, - qs_make_string(exec->line)) ; + qs_make_string(exec->action->line)) ; } ; #endif /* CONSUMED_TIME_CHECK */ - - if (ret != CMD_SUCCESS) - break ; /* stop */ } ; - exec->state = exec_success ; - /* continue by falling through */ + fall_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. + * Command has completed -- if successful, push output and loop back + * to fetch another command. * - * Output cannot block, so this always succeeds. - * - * Then loop back to fetch another command line, if can. + * 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_success: - assert(ret == CMD_SUCCESS) ; + case exec_done_cmd: + if (ret == CMD_SUCCESS) + ret = vty_cmd_success(vty) ; - vty_cmd_success(vty) ; + if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) + break ; /* stop */ - exec->state = exec_fetch ; + state = exec_fetch ; continue ; /*-------------------------------------------------------------------- - * End of the command loop ! - * - * If multi-threaded: must return to the vty_cli_nexus. + * Deal with the "special" commands + */ + case exec_special: + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_special, ret) ; + + ret = vty_cmd_special(vty) ; + if (ret != CMD_SUCCESS) + break ; + + state = exec_done_cmd ; + continue ; + + /*-------------------------------------------------------------------- + * Hiatus state -- some return code to be dealt with ! */ - case exec_complete: + case exec_hiatus: if (exec->locus != vty_cli_nexus) - break ; /* Will send back to the cli */ + return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ; + + while (1) + { + /* Let vty_cmd_hiatus() deal with the return code, and adjust + * the stack as required. + */ + ret = vty_cmd_hiatus(vty, ret) ; + + if (ret == CMD_SUCCESS) + break ; - /* Now in the vty_cli_nexus */ + if (ret == CMD_WAITING) + { + exec->state = exec_hiatus ; + return ; /* <<< DONE, pro tem */ + } ; - exec->state = exec_null ; /* all done ! */ + if (ret == CMD_EOF) + { + exec->state = exec_stopped ; + vty_cmd_loop_exit(vty) ; - vty_cmd_loop_exit(vty, ret) ; /* clears vio->cmd_running */ + return ; /* <<< DONE, permanently */ + } ; - return ; /* <<<< Finally finished ! */ + if (ret == CMD_IO_ERROR) + continue ; + + 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 ! @@ -511,22 +502,9 @@ cq_process(vty vty) 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 ; - } ; + /* Have broken out of the switch() => exec_hiatus */ + state = exec_hiatus ; + } ; } ; /*------------------------------------------------------------------------------ @@ -537,45 +515,41 @@ cq_process(vty vty) * 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. - * - * 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. + * 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. * - * 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. + * 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. * * Note that the revoke does not affect any vty_cli_nexus messages associated * with the vty_io_basic operations. + * + * Returns: true <=> have revoked a pending message/event */ -extern void +extern bool cq_revoke(vty vty) { int ret ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; if (vty_nexus) { - ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ; + ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ; - if ((ret == 0) && (vty_cli_nexus != vty_cmd_nexus)) - 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 revoked message/event, the command loop is stopped. */ if (ret != 0) - { - vty->exec->state = exec_null ; - vty_cmd_loop_exit(vty, vty->exec->ret) ; - } ; + vty->exec->state = exec_stopped ; + + return (ret != 0) ; } ; |