forked from Imagelibrary/rtems
This code did not have any copyrights or licenses and a bit of archeology was needed to determine authorship. This code was in the initial import into the RTEMS CVS repository when it was established in May 1995. There was very little, if any, code not written by OAR Corporation in that initial import. After discussion with Chris Johns, it was determined that this code was from OAR Corporation and that he had added a few features later. Both Chris Johns and OAR Corporation have given permission to relicense. Updates #3053.
657 lines
17 KiB
C
657 lines
17 KiB
C
/**
|
|
* @file
|
|
*
|
|
* @brief Command line editor for RTEMS 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 defined(RTEMS_MULTIPROCESSING)
|
|
if (!rtems_configuration_get_user_multiprocessing_table ())
|
|
snprintf (monitor_prompt, sizeof(monitor_prompt), "%s",
|
|
(env_prompt == NULL) ? MONITOR_PROMPT: env_prompt);
|
|
else /* .... */
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
}
|