Files
Sebastian Huber 3b061c5c44 cpukit/libmisc: Write the Doxygen briefs
The briefs of the libmisc files named a topic, not the content of the
file.  Four shell briefs named the wrong command.  The file
main_blksync.c said "RM Shell Command Implementation".  The files
main_id.c, main_time.c and shellconfig.c named ls and debugrfs.  Four
string conversion sources named the conversion, for example "Convert
String to Unsigned Long".

Write every brief in the style of the tree.  A source brief starts with
"This source file contains".  A command brief names the command the file
provides.  A header brief starts with "This header file provides".  Put
@file, @ingroup and @brief in this order in every block.

Updates #5717.

Assisted-by: Claude:claude-opus-5 claude-code
Signed-off-by: Sebastian Huber <sebastian.huber@embedded-brains.de>
2026-09-01 20:17:20 +00:00

649 lines
17 KiB
C

/**
* @file
*
* @ingroup RTEMSImplMonitor
*
* @brief This source file contains the command line editor of the monitor.
*/
/*
* COPYRIGHT (c) 2000 Chris Johns <chrisj@rtems.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <rtems.h>
#include <rtems/monitor.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <termios.h>
#include <unistd.h>
#ifndef MONITOR_PROMPT
#define MONITOR_PROMPT "rtems" /* will have '> ' appended */
#endif
/*
* Some key labels to define special keys.
*/
#define KEYS_EXTENDED ( 0x8000 )
#define KEYS_NORMAL_MASK ( 0x00ff )
#define KEYS_INS ( 0 )
#define KEYS_DEL ( 1 )
#define KEYS_UARROW ( 2 )
#define KEYS_DARROW ( 3 )
#define KEYS_LARROW ( 4 )
#define KEYS_RARROW ( 5 )
#define KEYS_HOME ( 6 )
#define KEYS_END ( 7 )
#define KEYS_F1 ( 8 )
#define KEYS_F2 ( 9 )
#define KEYS_F3 ( 10 )
#define KEYS_F4 ( 11 )
#define KEYS_F5 ( 12 )
#define KEYS_F6 ( 13 )
#define KEYS_F7 ( 14 )
#define KEYS_F8 ( 15 )
#define KEYS_F9 ( 16 )
#define KEYS_F10 ( 17 )
#define RTEMS_COMMAND_BUFFER_SIZE ( 75 )
static char monitor_prompt[ 32 ];
static char buffer[ RTEMS_COMMAND_BUFFER_SIZE ];
static int pos;
static int logged_in;
/*
* History data.
*/
#define RTEMS_COMMAND_HISTORIES ( 20 )
static char history_buffer[ RTEMS_COMMAND_HISTORIES ]
[ RTEMS_COMMAND_BUFFER_SIZE ];
static int history_pos[ RTEMS_COMMAND_HISTORIES ];
static int history;
static int history_next;
/*
* Translation tables. Not sure if this is the best way to
* handle this, how-ever I wish to avoid the overhead of
* including a more complete and standard environment such
* as ncurses.
*/
struct translation_table {
char expecting;
const struct translation_table *branch;
unsigned int key;
};
static const struct translation_table trans_two[] = {
{ '~', 0, KEYS_INS },
{ 0, 0, 0 }
};
static const struct translation_table trans_three[] = {
{ '~', 0, KEYS_DEL },
{ 0, 0, 0 }
};
static const struct translation_table trans_tab_csi[] = {
{ '2', trans_two, 0 },
{ '3', trans_three, 0 },
{ 'A', 0, KEYS_UARROW },
{ 'B', 0, KEYS_DARROW },
{ 'D', 0, KEYS_LARROW },
{ 'C', 0, KEYS_RARROW },
{ 'F', 0, KEYS_END },
{ 'H', 0, KEYS_HOME },
{ 0, 0, 0 }
};
static const struct translation_table trans_tab_O[] = {
{ '1', 0, KEYS_F1 },
{ '2', 0, KEYS_F2 },
{ '3', 0, KEYS_F3 },
{ '4', 0, KEYS_F4 },
{ '5', 0, KEYS_F5 },
{ '6', 0, KEYS_F6 },
{ '7', 0, KEYS_F7 },
{ '8', 0, KEYS_F8 },
{ '9', 0, KEYS_F9 },
{ ':', 0, KEYS_F10 },
{ 'P', 0, KEYS_F1 },
{ 'Q', 0, KEYS_F2 },
{ 'R', 0, KEYS_F3 },
{ 'S', 0, KEYS_F4 },
{ 'T', 0, KEYS_F5 },
{ 'U', 0, KEYS_F6 },
{ 'V', 0, KEYS_F7 },
{ 'W', 0, KEYS_F8 },
{ 'X', 0, KEYS_F9 },
{ 'Y', 0, KEYS_F10 },
{ 0, 0, 0 }
};
static const struct translation_table trans_tab[] = {
{ '[', trans_tab_csi, 0 }, /* CSI command sequences */
{ 'O', trans_tab_O, 0 }, /* O are the fuction keys */
{ 0, 0, 0 }
};
/*
* Perform a basic translation for some ANSI/VT100 key codes.
* This code could do with a timeout on the ESC as it is
* now lost from the input stream. It is not* used by the
* line editor below so considiered not worth the effort.
*/
static unsigned int rtems_monitor_getchar( void )
{
const struct translation_table *translation = 0;
for ( ;; ) {
char c = getchar();
if ( c == 27 ) {
translation = trans_tab;
} else {
/*
* If no translation happing just pass through
* and return the key.
*/
if ( translation ) {
/*
* Scan the current table for the key, and if found
* see if this key is a fork. If so follow it and
* wait else return the extended key.
*/
int index = 0;
int branched = 0;
while (
( translation[ index ].expecting != '\0' ) ||
( translation[ index ].key != '\0' )
) {
if ( translation[ index ].expecting == c ) {
/*
* A branch is take if more keys are to come.
*/
if ( translation[ index ].branch == 0 ) {
return KEYS_EXTENDED | translation[ index ].key;
} else {
translation = translation[ index ].branch;
branched = 1;
break;
}
}
index++;
}
/*
* Who knows what these keys are, just drop them.
*/
if ( !branched ) {
translation = 0;
}
} else {
return c;
}
}
}
}
/*
* The line editor with history.
*/
static int rtems_monitor_line_editor( char *command )
{
int repeating = 0;
memset( buffer, 0, RTEMS_COMMAND_BUFFER_SIZE );
history = history_next;
pos = 0;
if ( !logged_in ) {
fprintf( stdout, "\nMonitor ready, press enter to login.\n\n" );
} else {
fprintf( stdout, "%s $ ", monitor_prompt );
}
while ( 1 ) {
unsigned int extended_key;
char c;
fflush( stdout );
extended_key = rtems_monitor_getchar();
c = extended_key & KEYS_NORMAL_MASK;
/*
* Make the extended_key usable as a boolean.
*/
extended_key &= ~KEYS_NORMAL_MASK;
if ( !extended_key && !logged_in ) {
if ( c == '\n' ) {
logged_in = 1;
/*
* The prompt has changed from `>' to `$' to help know
* which version of the monitor code people are using.
*/
fprintf( stdout, "%s $ ", monitor_prompt );
}
} else {
if ( extended_key ) {
switch ( c ) {
case KEYS_END:
fprintf( stdout, "%s", buffer + pos );
pos = (int) strlen( buffer );
break;
case KEYS_HOME:
fprintf( stdout, "\r%s $ ", monitor_prompt );
pos = 0;
break;
case KEYS_LARROW:
if ( pos > 0 ) {
pos--;
putchar( '\b' );
}
break;
case KEYS_RARROW:
if (
( pos < RTEMS_COMMAND_BUFFER_SIZE ) && ( buffer[ pos ] != '\0' )
) {
putchar( buffer[ pos ] );
pos++;
}
break;
case KEYS_UARROW:
/*
* If we are moving up the histories then we need to save the working
* buffer.
*/
if ( history ) {
int end;
int bs;
if ( history == history_next ) {
memcpy(
history_buffer[ history_next ],
buffer,
RTEMS_COMMAND_BUFFER_SIZE
);
history_pos[ history_next ] = pos;
}
history--;
memcpy(
buffer,
history_buffer[ history ],
RTEMS_COMMAND_BUFFER_SIZE
);
pos = history_pos[ history ];
fprintf( stdout, "\r%*c", RTEMS_COMMAND_BUFFER_SIZE, ' ' );
fprintf( stdout, "\r%s $ %s", monitor_prompt, buffer );
end = (int) strlen( buffer );
for ( bs = 0; bs < ( end - pos ); bs++ ) {
putchar( '\b' );
}
}
break;
case KEYS_DARROW:
if ( history < history_next ) {
int end;
int bs;
history++;
memcpy(
buffer,
history_buffer[ history ],
RTEMS_COMMAND_BUFFER_SIZE
);
pos = history_pos[ history ];
fprintf( stdout, "\r%*c", RTEMS_COMMAND_BUFFER_SIZE, ' ' );
fprintf( stdout, "\r%s $ %s", monitor_prompt, buffer );
end = (int) strlen( buffer );
for ( bs = 0; bs < ( end - pos ); bs++ ) {
putchar( '\b' );
}
}
break;
case KEYS_DEL:
if ( buffer[ pos ] != '\0' ) {
int end;
int bs;
memmove(
&buffer[ pos ],
&buffer[ pos + 1 ],
strlen( &buffer[ pos + 1 ] ) + 1
);
fprintf( stdout, "\r%s $ %s", monitor_prompt, buffer );
end = (int) strlen( buffer );
for ( bs = 0; bs < ( end - pos ); bs++ ) {
putchar( '\b' );
}
}
break;
}
} else {
switch ( c ) {
case '\b':
case '\x7e':
case '\x7f':
if ( pos > 0 ) {
int bs;
pos--;
/*
* Memory operation used here instead of string
* method due the src and dest of buffer overlapping.
*/
memmove(
buffer + pos,
buffer + pos + 1,
RTEMS_COMMAND_BUFFER_SIZE - pos - 1
);
buffer[ RTEMS_COMMAND_BUFFER_SIZE - 1 ] = '\0';
fprintf( stdout, "\b%s \b", buffer + pos );
for ( bs = 0; bs < ( (int) strlen( buffer ) - pos ); bs++ ) {
putchar( '\b' );
}
}
break;
case '\n':
/*
* Process the command.
*/
fprintf( stdout, "\n" );
repeating = 1;
/*
* Only process the history if we have a command and
*a history.
*/
if ( strlen( buffer ) ) {
if ( history_next && ( history == history_next ) ) {
/*
* Do not place the last command into the history
*if the same.
*/
if ( strcmp( history_buffer[ history_next - 1 ], buffer ) ) {
repeating = 0;
}
} else {
repeating = 0;
}
}
if ( !repeating ) {
memcpy(
history_buffer[ history_next ],
buffer,
RTEMS_COMMAND_BUFFER_SIZE
);
history_pos[ history_next ] = pos;
if ( history_next < ( RTEMS_COMMAND_HISTORIES - 1 ) ) {
history_next++;
} else {
memmove(
history_buffer[ 0 ],
history_buffer[ 1 ],
RTEMS_COMMAND_BUFFER_SIZE * ( RTEMS_COMMAND_HISTORIES - 1 )
);
memmove(
&history_pos[ 0 ],
&history_pos[ 1 ],
sizeof( history_pos[ 0 ] ) * ( RTEMS_COMMAND_HISTORIES - 1 )
);
}
} else {
#ifdef ENABLE_ENTER_REPEATS
if ( history_next ) {
memcpy(
buffer,
history_buffer[ history_next - 1 ],
RTEMS_COMMAND_BUFFER_SIZE
);
}
#endif
}
memmove( command, buffer, RTEMS_COMMAND_BUFFER_SIZE );
return repeating;
break;
default:
if (
( pos < ( RTEMS_COMMAND_BUFFER_SIZE - 1 ) ) && ( c >= ' ' ) &&
( c <= 'z' )
) {
int end;
end = strlen( buffer );
if ( ( pos < end ) && ( end < RTEMS_COMMAND_BUFFER_SIZE ) ) {
int ch, bs;
for ( ch = end; ch > pos; ch-- ) {
buffer[ ch ] = buffer[ ch - 1 ];
}
fprintf( stdout, "%s", buffer + pos );
for ( bs = 0; bs < ( end - pos + 1 ); bs++ ) {
putchar( '\b' );
}
}
buffer[ pos++ ] = c;
if ( pos > end ) {
buffer[ pos ] = '\0';
}
putchar( c );
}
break;
}
}
}
}
}
/*
* make_argv(cp): token-count
* Break up the command line in 'cp' into global argv[] and argc (return
* value).
*/
int rtems_monitor_make_argv( char *cp, int *argc_p, char **argv )
{
int argc = 0;
while (( cp = strtok( cp, " \t\n\r" ) )) {
argv[ argc++ ] = cp;
cp = (char *) NULL;
}
argv[ argc ] = (char *) NULL; /* end of argv */
return *argc_p = argc;
}
/*
* Read and break up a monitor command
*
* We have to loop on the gets call, since it will return NULL under UNIX
* RTEMS when we get a signal (eg: SIGALRM).
*/
int rtems_monitor_command_read( char *command, int *argc, char **argv )
{
char *env_prompt;
env_prompt = getenv( "RTEMS_MONITOR_PROMPT" );
/*
* put node number in the prompt if we are multiprocessing
*/
if ( rtems_monitor_default_node != rtems_monitor_node ) {
snprintf(
monitor_prompt,
sizeof( monitor_prompt ),
"%" PRId32 "-%s-%" PRId32 "",
rtems_monitor_node,
( env_prompt == NULL ) ? MONITOR_PROMPT : env_prompt,
rtems_monitor_default_node
);
} else {
snprintf(
monitor_prompt,
sizeof( monitor_prompt ),
"%" PRId32 "-%s",
rtems_monitor_node,
( env_prompt == NULL ) ? MONITOR_PROMPT : env_prompt
);
}
rtems_monitor_line_editor( command );
return rtems_monitor_make_argv( command, argc, argv );
}
/*
* Main monitor command loop
*/
void rtems_monitor_task( rtems_task_argument monitor_flags )
{
char command_buffer[ 513 ];
int argc;
char *argv[ 64 ];
bool verbose = false;
struct termios term;
/*
* Make the stdin stream characte not line based.
*/
if ( tcgetattr( STDIN_FILENO, &term ) < 0 ) {
fprintf( stdout, "rtems-monitor: cannot get terminal attributes.\n" );
} else {
/*
* No echo, no canonical processing.
*/
term.c_lflag &= ~( ECHO | ICANON | IEXTEN );
/*
* No sigint on BREAK, CR-to-NL off, input parity off,
* don't strip 8th bit on input, output flow control off
*/
term.c_lflag &= ~( INPCK | ISTRIP | IXON );
term.c_cc[ VMIN ] = 1;
term.c_cc[ VTIME ] = 0;
if ( tcsetattr( STDIN_FILENO, TCSANOW, &term ) < 0 ) {
fprintf( stdout, "cannot set terminal attributes\n" );
}
}
if ( !( monitor_flags & RTEMS_MONITOR_NOSYMLOAD ) ) {
rtems_monitor_symbols_loadup();
}
if ( monitor_flags & RTEMS_MONITOR_SUSPEND ) {
(void) rtems_monitor_suspend( RTEMS_NO_TIMEOUT );
}
for ( ;; ) {
const rtems_monitor_command_entry_t *command;
if ( 0 == rtems_monitor_command_read( command_buffer, &argc, argv ) ) {
continue;
}
if (
argc < 1 || ( command = rtems_monitor_command_lookup( argv[ 0 ] ) ) == 0
) {
/* no command */
fprintf( stdout, "Unrecognised command; try 'help'\n" );
continue;
}
command->command_function( argc, argv, &command->command_arg, verbose );
fflush( stdout );
}
}
void rtems_monitor_kill( void )
{
if ( rtems_monitor_task_id ) {
rtems_task_delete( rtems_monitor_task_id );
}
rtems_monitor_task_id = 0;
rtems_monitor_server_kill();
}
void rtems_monitor_init( uint32_t monitor_flags )
{
rtems_status_code status;
rtems_monitor_kill();
status = rtems_task_create(
RTEMS_MONITOR_NAME,
1,
RTEMS_MINIMUM_STACK_SIZE * 2,
RTEMS_INTERRUPT_LEVEL( 0 ),
RTEMS_DEFAULT_ATTRIBUTES,
&rtems_monitor_task_id
);
if ( status != RTEMS_SUCCESSFUL ) {
rtems_error( status, "could not create monitor task" );
return;
}
rtems_monitor_node = rtems_object_id_get_node( rtems_monitor_task_id );
rtems_monitor_default_node = rtems_monitor_node;
rtems_monitor_server_init( monitor_flags );
if ( !( monitor_flags & RTEMS_MONITOR_NOTASK ) ) {
/*
* Start the monitor task itself
*/
status = rtems_task_start(
rtems_monitor_task_id,
rtems_monitor_task,
monitor_flags
);
if ( status != RTEMS_SUCCESSFUL ) {
rtems_error( status, "could not start monitor" );
return;
}
}
}