1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
|
/* Command Message Queue
* Copyright (C) 2009 Chris Hall (GMCH), Highwayman
*
* This file is part of GNU Zebra.
*
* GNU Zebra is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2, or (at your
* option) any later version.
*
* GNU Zebra is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Zebra; see the file COPYING. If not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "zconfig.h" /* for CONSUMED_TIME_CHECK */
#include "misc.h"
#include "qpnexus.h"
#include "mqueue.h"
#include "thread.h"
#include "command_queue.h"
#include "command_execute.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
*
*
*/
/*------------------------------------------------------------------------------
* 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) ;
/*------------------------------------------------------------------------------
* 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)
{
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) ;
} ;
/*------------------------------------------------------------------------------
* 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.
*/
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 for execution in given nexus or issue thread event.
*
* Note that preserves the return code state.
*/
static void
cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
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 ;
exec->state = state ;
exec->ret = ret ;
if (vty_nexus)
{
mqueue_block mqb ;
if ((mqb = exec->cq.mqb) == NULL)
mqb = exec->cq.mqb = mqb_init_new(NULL, cq_action, vty) ;
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) ;
} ;
} ;
/*------------------------------------------------------------------------------
* Deal with command message -- in the qpthreads world.
*
* Note that if the command is revoked....
*/
static void
cq_action(mqueue_block mqb, mqb_flag_t flag)
{
vty vty;
assert(vty_nexus) ; /* must be running qnexus-wise */
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 */
mqb_free(mqb) ;
vty->exec->cq.mqb = NULL ;
} ;
/*------------------------------------------------------------------------------
* 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 ;
} ;
/*------------------------------------------------------------------------------
* 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:
*
* cli -> cli -- exec_parse -- when a command line has been gathered
* from input and is ready to be processed.
*
* 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_process(vty vty)
{
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 */
} ;
exec->state = exec_execute ;
/* continue by falling through */
/*--------------------------------------------------------------------
* 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) ;
/* 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. */
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 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.
*
* 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 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.
*/
extern void
cq_revoke(vty vty)
{
int ret ;
VTY_ASSERT_CLI_THREAD() ;
if (vty_nexus)
{
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) ;
} ;
} ;
|