1/*
2Copyright (c) 2012, Broadcom Europe Ltd
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the copyright holder nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*/
27
28/*****************************************************************************
29*
30* This file provides a generic command line interface which allows
31* vcos internals to be manipulated and/or displayed.
32*
33*****************************************************************************/
34
35/* ---- Include Files ---------------------------------------------------- */
36
37#include "interface/vcos/vcos.h"
38
39#ifdef HAVE_VCOS_VERSION
40#include "interface/vcos/vcos_build_info.h"
41#endif
42
43#ifdef _VIDEOCORE
44#include "vcfw/logging/logging.h"
45#endif
46
47/* ---- Public Variables ------------------------------------------------- */
48
49/* ---- Private Constants and Types -------------------------------------- */
50
51#define VCOS_LOG_CATEGORY (&vcos_cmd_log_category)
52VCOS_LOG_CAT_T vcos_cmd_log_category;
53
54/* ---- Private Variables ------------------------------------------------ */
55
56static struct VCOS_CMD_GLOBALS_T
57{
58 VCOS_MUTEX_T lock;
59 VCOS_ONCE_T initialized;
60
61 unsigned num_cmd_entries;
62 unsigned num_cmd_alloc;
63 VCOS_CMD_T *cmd_entry;
64
65 VCOS_LOG_CAT_T *log_category;
66} cmd_globals;
67
68#ifdef HAVE_VCOS_VERSION
69/* Keep the static strings in the image from being dropped by
70 * the linker.
71 */
72extern const char *vcos_get_build_strings(unsigned id);
73const char *(*vcos_keep_static_strings)(unsigned);
74#endif
75
76/* ---- Private Function Prototypes -------------------------------------- */
77
78static VCOS_STATUS_T help_cmd( VCOS_CMD_PARAM_T *param );
79
80/* ---- Functions ------------------------------------------------------- */
81
82/*****************************************************************************
83*
84* Walks through the commands looking for a particular command
85*
86*****************************************************************************/
87
88static VCOS_CMD_T *find_cmd( VCOS_CMD_T *cmd_entry, const char *name )
89{
90 VCOS_CMD_T *scan_entry = cmd_entry;
91
92 while ( scan_entry->name != NULL )
93 {
94 if ( vcos_strcmp( scan_entry->name, name ) == 0 )
95 {
96 return scan_entry;
97 }
98 scan_entry++;
99 }
100
101 return NULL;
102}
103
104/*****************************************************************************
105*
106* Saves away
107* each line individually.
108*
109*****************************************************************************/
110
111void vcos_cmd_always_log_output( VCOS_LOG_CAT_T *log_category )
112{
113 cmd_globals.log_category = log_category;
114}
115
116/*****************************************************************************
117*
118* Walks through a buffer containing newline separated lines, and logs
119* each line individually.
120*
121*****************************************************************************/
122
123static void cmd_log_results( VCOS_CMD_PARAM_T *param )
124{
125 char *start;
126 char *end;
127
128 start = end = param->result_buf;
129
130 while ( *start != '\0' )
131 {
132 while (( *end != '\0' ) && ( *end != '\n' ))
133 end++;
134
135 if ( *end == '\n' )
136 {
137 *end++ = '\0';
138 }
139
140 if ( cmd_globals.log_category != NULL )
141 {
142 if ( vcos_is_log_enabled( cmd_globals.log_category, VCOS_LOG_INFO ))
143 {
144 vcos_log_impl( cmd_globals.log_category, VCOS_LOG_INFO, "%s", start );
145 }
146 }
147 else
148 {
149 vcos_log_info( "%s", start );
150 }
151
152 start = end;
153 }
154
155 /* Since we logged the buffer, reset the pointer back to the beginning. */
156
157 param->result_ptr = param->result_buf;
158 param->result_buf[0] = '\0';
159}
160
161/*****************************************************************************
162*
163* Since we may have limited output space, we create a generic routine
164* which tries to use the result space, but will switch over to using
165* logging if the output is too large.
166*
167*****************************************************************************/
168
169void vcos_cmd_vprintf( VCOS_CMD_PARAM_T *param, const char *fmt, va_list args )
170{
171 int bytes_written;
172 int bytes_remaining;
173
174 bytes_remaining = (int)(param->result_size - ( param->result_ptr - param->result_buf ));
175
176 bytes_written = vcos_vsnprintf( param->result_ptr, bytes_remaining, fmt, args );
177
178 if ( cmd_globals.log_category != NULL )
179 {
180 /* We're going to log each line as we encounter it. If the buffer
181 * doesn't end in a newline, then we'll wait for one first.
182 */
183
184 if ( (( bytes_written + 1 ) >= bytes_remaining )
185 || ( param->result_ptr[ bytes_written - 1 ] == '\n' ))
186 {
187 cmd_log_results( param );
188 }
189 else
190 {
191 param->result_ptr += bytes_written;
192 }
193 }
194 else
195 {
196 if (( bytes_written + 1 ) >= bytes_remaining )
197 {
198 /* Output doesn't fit - switch over to logging */
199
200 param->use_log = 1;
201
202 *param->result_ptr = '\0'; /* Zap the partial line that didn't fit above. */
203
204 cmd_log_results( param ); /* resets result_ptr */
205
206 bytes_written = vcos_vsnprintf( param->result_ptr, bytes_remaining, fmt, args );
207 }
208 param->result_ptr += bytes_written;
209 }
210}
211
212/*****************************************************************************
213*
214* Prints the output.
215*
216*****************************************************************************/
217
218void vcos_cmd_printf( VCOS_CMD_PARAM_T *param, const char *fmt, ... )
219{
220 va_list args;
221
222 va_start( args, fmt );
223 vcos_cmd_vprintf( param, fmt, args );
224 va_end( args );
225}
226
227/*****************************************************************************
228*
229* Prints the arguments which were on the command line prior to ours.
230*
231*****************************************************************************/
232
233static void print_argument_prefix( VCOS_CMD_PARAM_T *param )
234{
235 int arg_idx;
236
237 for ( arg_idx = 0; &param->argv_orig[arg_idx] != param->argv; arg_idx++ )
238 {
239 vcos_cmd_printf( param, "%s ", param->argv_orig[arg_idx] );
240 }
241}
242
243/*****************************************************************************
244*
245* Prints an error message, prefixed by the command chain required to get
246* to where we're at.
247*
248*****************************************************************************/
249
250void vcos_cmd_error( VCOS_CMD_PARAM_T *param, const char *fmt, ... )
251{
252 va_list args;
253
254 print_argument_prefix( param );
255
256 va_start( args, fmt );
257 vcos_cmd_vprintf( param, fmt, args );
258 va_end( args );
259 vcos_cmd_printf( param, "\n" );
260}
261
262/****************************************************************************
263*
264* usage - prints command usage for an array of commands.
265*
266***************************************************************************/
267
268static void usage( VCOS_CMD_PARAM_T *param, VCOS_CMD_T *cmd_entry )
269{
270 int cmd_idx;
271 int nameWidth = 0;
272 int argsWidth = 0;
273 VCOS_CMD_T *scan_entry;
274
275 vcos_cmd_printf( param, "Usage: " );
276 print_argument_prefix( param );
277 vcos_cmd_printf( param, "command [args ...]\n" );
278 vcos_cmd_printf( param, "\n" );
279 vcos_cmd_printf( param, "Where command is one of the following:\n" );
280
281 for ( cmd_idx = 0; cmd_entry[cmd_idx].name != NULL; cmd_idx++ )
282 {
283 int aw;
284 int nw;
285
286 scan_entry = &cmd_entry[cmd_idx];
287
288 nw = vcos_strlen( scan_entry->name );
289 aw = vcos_strlen( scan_entry->args );
290
291 if ( nw > nameWidth )
292 {
293 nameWidth = nw;
294 }
295 if ( aw > argsWidth )
296 {
297 argsWidth = aw;
298 }
299 }
300
301 for ( cmd_idx = 0; cmd_entry[cmd_idx].name != NULL; cmd_idx++ )
302 {
303 scan_entry = &cmd_entry[cmd_idx];
304
305 vcos_cmd_printf( param, " %-*s %-*s - %s\n",
306 nameWidth, scan_entry->name,
307 argsWidth, scan_entry->args,
308 scan_entry->descr );
309 }
310}
311
312/****************************************************************************
313*
314* Prints the usage for the current command.
315*
316***************************************************************************/
317
318void vcos_cmd_usage( VCOS_CMD_PARAM_T *param )
319{
320 VCOS_CMD_T *cmd_entry;
321
322 cmd_entry = param->cmd_entry;
323
324 if ( cmd_entry->sub_cmd_entry != NULL )
325 {
326 /* This command is command with sub-commands */
327
328 usage( param, param->cmd_entry->sub_cmd_entry );
329 }
330 else
331 {
332 vcos_cmd_printf( param, "Usage: " );
333 print_argument_prefix( param );
334 vcos_cmd_printf( param, "%s %s - %s\n",
335 param->argv[0],
336 param->cmd_entry->args,
337 param->cmd_entry->descr );
338 }
339}
340
341/*****************************************************************************
342*
343* Command to print out the help
344*
345* This help command is only called from the main menu.
346*
347*****************************************************************************/
348
349static VCOS_STATUS_T help_cmd( VCOS_CMD_PARAM_T *param )
350{
351 VCOS_CMD_T *found_entry;
352
353#if 0
354 {
355 int arg_idx;
356
357 vcos_log_trace( "%s: argc = %d", __func__, param->argc );
358 for ( arg_idx = 0; arg_idx < param->argc; arg_idx++ )
359 {
360 vcos_log_trace( "%s: argv[%d] = '%s'", __func__, arg_idx, param->argv[arg_idx] );
361 }
362 }
363#endif
364
365 /* If there is an argument after the word help, then we want to print
366 * help for that command.
367 */
368
369 if ( param->argc == 1 )
370 {
371 if ( param->cmd_parent_entry == cmd_globals.cmd_entry )
372 {
373 /* Bare help - print the command usage for the root */
374
375 usage( param, cmd_globals.cmd_entry );
376 return VCOS_SUCCESS;
377 }
378
379 /* For all other cases help requires an argument */
380
381 vcos_cmd_error( param, "%s requires an argument", param->argv[0] );
382 return VCOS_EINVAL;
383 }
384
385 /* We were given an argument. */
386
387 if (( found_entry = find_cmd( param->cmd_parent_entry, param->argv[1] )) != NULL )
388 {
389 /* Make it look like the command that was specified is the one that's
390 * currently running
391 */
392
393 vcos_cmd_printf( param, "Usage: " );
394 print_argument_prefix( param );
395 vcos_cmd_printf( param, "%s %s - %s\n",
396 param->argv[1],
397 found_entry->args,
398 found_entry->descr );
399
400 return VCOS_SUCCESS;
401 }
402
403 vcos_cmd_error( param, "- unrecognized command: '%s'", param->argv[1] );
404 return VCOS_ENOENT;
405}
406
407/*****************************************************************************
408*
409* Command to print out the version/build information.
410*
411*****************************************************************************/
412
413#ifdef HAVE_VCOS_VERSION
414
415static VCOS_STATUS_T version_cmd( VCOS_CMD_PARAM_T *param )
416{
417 static const char* copyright = "Copyright (c) 2011 Broadcom";
418
419 vcos_cmd_printf( param, "%s %s\n%s\nversion %s\nhost %s",
420 vcos_get_build_date(),
421 vcos_get_build_time(),
422 copyright,
423 vcos_get_build_version(),
424 vcos_get_build_hostname() );
425
426 return VCOS_SUCCESS;
427}
428
429#endif
430
431/*****************************************************************************
432*
433* Internal commands
434*
435*****************************************************************************/
436
437static VCOS_CMD_T cmd_help = { "help", "[command]", help_cmd, NULL, "Prints command help information" };
438
439#ifdef HAVE_VCOS_VERSION
440static VCOS_CMD_T cmd_version = { "version", "", version_cmd, NULL, "Prints build/version information" };
441#endif
442
443/*****************************************************************************
444*
445* Walks the command table and executes the commands
446*
447*****************************************************************************/
448
449static VCOS_STATUS_T execute_cmd( VCOS_CMD_PARAM_T *param, VCOS_CMD_T *cmd_entry )
450{
451 const char *cmdStr;
452 VCOS_CMD_T *found_entry;
453
454#if 0
455 {
456 int arg_idx;
457
458 vcos_cmd_printf( param, "%s: argc = %d", __func__, param->argc );
459 for ( arg_idx = 0; arg_idx < param->argc; arg_idx++ )
460 {
461 vcos_cmd_printf( param, " argv[%d] = '%s'", arg_idx, param->argv[arg_idx] );
462 }
463 vcos_cmd_printf( param, "\n" );
464 }
465#endif
466
467 if ( param->argc <= 1 )
468 {
469 /* No command specified */
470
471 vcos_cmd_error( param, "%s - no command specified", param->argv[0] );
472 return VCOS_EINVAL;
473 }
474
475 /* argv[0] is the command/program that caused us to get invoked, so we strip
476 * it off.
477 */
478
479 param->argc--;
480 param->argv++;
481 param->cmd_parent_entry = cmd_entry;
482
483 /* Not the help command, scan for the command and execute it. */
484
485 cmdStr = param->argv[0];
486
487 if (( found_entry = find_cmd( cmd_entry, cmdStr )) != NULL )
488 {
489 if ( found_entry->sub_cmd_entry != NULL )
490 {
491 return execute_cmd( param, found_entry->sub_cmd_entry );
492 }
493
494 param->cmd_entry = found_entry;
495 return found_entry->cmd_fn( param );
496 }
497
498 /* Unrecognized command - check to see if it was the help command */
499
500 if ( vcos_strcmp( cmdStr, cmd_help.name ) == 0 )
501 {
502 return help_cmd( param );
503 }
504
505 vcos_cmd_error( param, "- unrecognized command: '%s'", cmdStr );
506 return VCOS_ENOENT;
507}
508
509/*****************************************************************************
510*
511* Initializes the command line parser.
512*
513*****************************************************************************/
514
515static void vcos_cmd_init( void )
516{
517 vcos_mutex_create( &cmd_globals.lock, "vcos_cmd" );
518
519 cmd_globals.num_cmd_entries = 0;
520 cmd_globals.num_cmd_alloc = 0;
521 cmd_globals.cmd_entry = NULL;
522
523#ifdef HAVE_VCOS_VERSION
524 vcos_keep_static_strings = vcos_get_build_strings;
525#endif
526}
527
528/*****************************************************************************
529*
530* Shuts down the command line parser.
531*
532*****************************************************************************/
533
534void vcos_cmd_shutdown( void )
535{
536 vcos_mutex_delete( &cmd_globals.lock );
537
538 vcos_free( cmd_globals.cmd_entry );
539 cmd_globals.cmd_entry = NULL;
540}
541
542/*****************************************************************************
543*
544* Command line processor.
545*
546*****************************************************************************/
547
548VCOS_STATUS_T vcos_cmd_execute( int argc, char **argv, size_t result_size, char *result_buf )
549{
550 VCOS_STATUS_T rc = VCOS_EINVAL;
551 VCOS_CMD_PARAM_T param;
552
553 vcos_once( &cmd_globals.initialized, vcos_cmd_init );
554
555 param.argc = argc;
556 param.argv = param.argv_orig = argv;
557
558 param.use_log = 0;
559 param.result_size = result_size;
560 param.result_ptr = result_buf;
561 param.result_buf = result_buf;
562
563 result_buf[0] = '\0';
564
565 vcos_mutex_lock( &cmd_globals.lock );
566
567 rc = execute_cmd( &param, cmd_globals.cmd_entry );
568
569 if ( param.use_log )
570 {
571 cmd_log_results( &param );
572 vcos_snprintf( result_buf, result_size, "results logged" );
573 }
574 else
575 if ( cmd_globals.log_category != NULL )
576 {
577 if ( result_buf[0] != '\0' )
578 {
579 /* There is a partial line still buffered. */
580
581 vcos_cmd_printf( &param, "\n" );
582 }
583 }
584
585 vcos_mutex_unlock( &cmd_globals.lock );
586
587 return rc;
588}
589
590/*****************************************************************************
591*
592* Registers a command entry with the command line processor
593*
594*****************************************************************************/
595
596VCOS_STATUS_T vcos_cmd_register( VCOS_CMD_T *cmd_entry )
597{
598 VCOS_STATUS_T rc;
599 VCOS_UNSIGNED new_num_cmd_alloc;
600 VCOS_CMD_T *new_cmd_entry;
601 VCOS_CMD_T *old_cmd_entry;
602 VCOS_CMD_T *scan_entry;
603
604 vcos_once( &cmd_globals.initialized, vcos_cmd_init );
605
606 vcos_assert( cmd_entry != NULL );
607 vcos_assert( cmd_entry->name != NULL );
608
609 vcos_log_trace( "%s: cmd '%s'", __FUNCTION__, cmd_entry->name );
610
611 vcos_assert( cmd_entry->args != NULL );
612 vcos_assert(( cmd_entry->cmd_fn != NULL ) || ( cmd_entry->sub_cmd_entry != NULL ));
613 vcos_assert( cmd_entry->descr != NULL );
614
615 /* We expect vcos_cmd_init to be called before vcos_logging_init, so we
616 * need to defer registering our logging category until someplace
617 * like right here.
618 */
619
620 if ( vcos_cmd_log_category.name == NULL )
621 {
622 /*
623 * If you're using the command interface, you pretty much always want
624 * log messages from this file to show up. So we change the default
625 * from ERROR to be the more reasonable INFO level.
626 */
627
628 vcos_log_set_level(&vcos_cmd_log_category, VCOS_LOG_INFO);
629 vcos_log_register("vcos_cmd", &vcos_cmd_log_category);
630
631 /* We register a help command so that it shows up in the usage. */
632
633 vcos_cmd_register( &cmd_help );
634#ifdef HAVE_VCOS_VERSION
635 vcos_cmd_register( &cmd_version );
636#endif
637 }
638
639 vcos_mutex_lock( &cmd_globals.lock );
640
641 if ( cmd_globals.num_cmd_entries >= cmd_globals.num_cmd_alloc )
642 {
643 if ( cmd_globals.num_cmd_alloc == 0 )
644 {
645 /* We haven't allocated a table yet */
646 }
647
648 /* The number 8 is rather arbitrary. */
649
650 new_num_cmd_alloc = cmd_globals.num_cmd_alloc + 8;
651
652 /* The + 1 is to ensure that we always have a NULL entry at the end. */
653
654 new_cmd_entry = (VCOS_CMD_T *)vcos_calloc( new_num_cmd_alloc + 1, sizeof( *cmd_entry ), "vcos_cmd_entries" );
655 if ( new_cmd_entry == NULL )
656 {
657 rc = VCOS_ENOMEM;
658 goto out;
659 }
660 memcpy( new_cmd_entry, cmd_globals.cmd_entry, cmd_globals.num_cmd_entries * sizeof( *cmd_entry ));
661 cmd_globals.num_cmd_alloc = new_num_cmd_alloc;
662 old_cmd_entry = cmd_globals.cmd_entry;
663 cmd_globals.cmd_entry = new_cmd_entry;
664 vcos_free( old_cmd_entry );
665 }
666
667 if ( cmd_globals.num_cmd_entries == 0 )
668 {
669 /* This is the first command being registered */
670
671 cmd_globals.cmd_entry[0] = *cmd_entry;
672 }
673 else
674 {
675 /* Keep the list in alphabetical order. We start at the end and work backwards
676 * shuffling entries up one until we find an insertion point.
677 */
678
679 for ( scan_entry = &cmd_globals.cmd_entry[cmd_globals.num_cmd_entries - 1];
680 scan_entry >= cmd_globals.cmd_entry; scan_entry-- )
681 {
682 if ( vcos_strcmp( cmd_entry->name, scan_entry->name ) > 0 )
683 {
684 /* We found an insertion point. */
685
686 break;
687 }
688
689 scan_entry[1] = scan_entry[0];
690 }
691 scan_entry[1] = *cmd_entry;
692 }
693 cmd_globals.num_cmd_entries++;
694
695 rc = VCOS_SUCCESS;
696
697out:
698
699 vcos_mutex_unlock( &cmd_globals.lock );
700 return rc;
701}
702
703/*****************************************************************************
704*
705* Registers multiple commands.
706*
707*****************************************************************************/
708
709VCOS_STATUS_T vcos_cmd_register_multiple( VCOS_CMD_T *cmd_entry )
710{
711 VCOS_STATUS_T status;
712
713 while ( cmd_entry->name != NULL )
714 {
715 if (( status = vcos_cmd_register( cmd_entry )) != VCOS_SUCCESS )
716 {
717 return status;
718 }
719 cmd_entry++;
720 }
721 return VCOS_SUCCESS;
722}
723
724