Monday, December 21, 2009

Command-Line Redirection










































Command-Line Redirection



MicroMonitor also includes support for command-line
redirection. Command-line redirection is handy, for example, to log the
output of a command to a file. My primary motivation for adding
command-line redirection is to support the ability to dump a stack
trace output to a file.



I discussed exception handling earlier. In the
next few chapters, I will describe a flash file system and show how to
dump an application stack. Consider the case where your system is in
the field and taking a strange exception every other blue moon. How do
you find the cause for the strange exception without camping out at the
customer site? If the exception handler has the optional ability to
execute a specified script (from the file system), then that script,
among other things, can issue a stack trace command (strace)
and redirect the output to a file. If this script has been activated,
when the customer calls saying that something appears to have crashed
and restarted, you can query the file system for the trace output file
and see what caused the exception.



By default all printf output passes through putchar(), which sends each character to the console port. If putchar
has some additional smarts and can place its incoming character into a
buffer of some known size, then the output will be logged to RAM. To
convert this internal logging to output redirection, I need only
provide a mechanism to tell MicroMonitor to transfer the buffer to a
file. Traditionally, CLI redirection syntax is


echo hey you >filename

This syntax isn’t good for MicroMonitor’s command-line
redirection because I want the output to go to RAM first. So I deviate
slightly with two different forms:





  1. echo hey you > buffer, buffer_size[, filename]



    This format consists of single right arrow with a
    one- or two-comma delimited string containing a buffer address followed
    by the size of the buffer and an optional filename. If the filename is
    specified, the output of the command is copied to the buffer (truncated
    at buffer_size if necessary) and then transferred to TFS as filename. The running buffer pointer is reset back to the base address of buffer.
    If a filename is omitted, the output of the command is copied to the
    buffer, and the pointer into the buffer is left at the position
    following the data (assuming buffer_size is not reached).






  2. echo hey you again >>[filename]



    This syntax is used to append the output of the command to the buffer that was created by the >
    syntax described above. If a filename is included, the content of the
    buffer is transferred to TFS under the specified name (in this
    example), and the running buffer pointer is reset to the base address
    of the buffer. If a filename is not specified, there is no transfer to
    a file, and the running pointer is incremented by one to the position
    just after the data copied (once again, assuming buffer_size is not reached).





To implement this feature, the function putchar() needs an additional call at the top (see Listing 5.7).



Listing 5.7: Adding Redirection to putchar().







int
putchar(uchar c)
{
RedirectCharacter(c);
if (c == '\n')
rputchar('\r');
rputchar(c);
return((int)c);
}













The RedirectCharacter() function (see Listing 5.8) looks at a state to determine if it does anything at all. The RedirectCharacter()
function either does nothing or, after checking to see that the running
buffer pointer has not reached the end of the buffer, copies the
character to the buffer space and increments the pointer by one.



Listing 5.8: RedirectCharacter().







void
RedirectCharacter(char c)
{
if (RedirectState == REDIRECT_ACTIVE) {
if (RedirectPtr < RedirectEnd)
*RedirectPtr++ = c;
}
}














I must also modify docommand() so it can recognize the redirection syntax (set RedirectState to REDIRECT_ACTIVE) and, when appropriate, notify the redirection code that the command has completed (set RedirectState back to REDIRECT_IDLE). So I must add a few lines to docommand().


if (RedirectionCheck(cmdcpy) == -1)
return(CMD_LINE_ERROR);

I insert the preceding code into docommand() just after the shell variable processing. Placing the modification after shell variable processing allows the user to do something like


echo hi >$APPRAMBASE,100

and know that when RedirectionCheck() is called, the content of $APPRAMBASE has already been converted to a physical address by the CLI shell-variable processor described previously. At the bottom of docommand(), I add another call to indicate command completion. This call is simply a call to RedirectionDone() prior to returning from docommand().


The RedirectionCheck() function (see Listing 5.9) starts by looking through the line for the redirection symbol. If, at the end of the line, RedirectionCheck() hasn’t found a right arrow (>), it simply returns. If it finds a second arrow, then RedirectState should already be REDIRECT_ACTIVE; any other state indicates an error. If RedirectState is correct, then RedirectionCheck() looks for a filename and stores it away for use later. Finally, when RedirectionCheck()
does not find a second arrow, it processes the comma-delimited string
after the arrow and establishes pointers and counters appropriately.




Listing 5.9: Parsing Redirection Commands.






#define REDIRECT_UNINITIALIZED  0
#define REDIRECT_ACTIVE 0x12345678
#define REDIRECT_IDLE 0x87654321

static int RedirectSize, RedirectState;
static char *RedirectBase, *RedirectPtr, *RedirectEnd;
static char RedirectFile[TFSNAMESIZE];

int
RedirectionCheck(char *cmdcpy)
{
int inquote;
char *arrow, *base, *comma, *space;

base = cmdcpy;
arrow = (char *)0;

/* Parse the incoming command line looking for a right arrow.
* This parsing assumes that there will be no negated arrows
* (preceding backslash) after a non-negated arrow is detected.
* Note that a redirection arrow within a double-quote set is
* ignored. This allows a shell variable that contains a right arrow
* to be printed properly if put in double quotes.
* For example...
* set PROMPT "maint> "
* echo $PROMPT # This will generate a redirection syntax error
* echo "$PROMPT" # This won't.
*/
inquote = 0;

while(*cmdcpy) {
if ((*cmdcpy == '"') && ((cmdcpy == base) || (*(cmdcpy-1) != '\\'))) {
inquote = inquote == 1 ? 0 : 1;
cmdcpy++;
continue;
}
if (inquote == 1) {
cmdcpy++;
continue;
}
if (*cmdcpy == '>') {
arrow = cmdcpy;
if (*(arrow-1) == '\\') {
strcpy(arrow-1,arrow);
cmdcpy = arrow+1;
arrow = (char *)0;
continue;
}
break;
}
cmdcpy++;
}

if (arrow == (char *)0)
return(0);

/* Remove the remaining text from the command line because it is to
* be used only by the redirection mechanism.
*/
*arrow = 0;

/* Now parse the text after the first non-negated arrow. */
if (*(arrow+1) == '>') {
if (RedirectState == REDIRECT_UNINITIALIZED) {
printf("Redirection not initialized\n");
return(-1);
}
arrow += 2;
while(isspace(*arrow))
arrow++;
if (*arrow != 0)
strncpy(RedirectFile,arrow,TFSNAMESIZE);
}
else {
RedirectPtr = RedirectBase = (char *)strtoul(arrow+1,&comma,0);
if (*comma == ',') {
RedirectSize = (int)strtol(comma+1,&comma,0);
if (RedirectSize <= 0) {
printf("Redirection size error: %d\n",RedirectSize);
return(-1);
}
RedirectEnd = RedirectBase + RedirectSize;
if (*comma == ',') {
space = strpbrk(comma," \t\r\n");
if (space)
*space = 0;
strncpy(RedirectFile,comma+1,TFSNAMESIZE);
}
else
RedirectFile[0] = 0;
}
else {
printf("Redirection syntax error\n");
return(-1);
}
}
RedirectState = REDIRECT_ACTIVE;
return(0);
}















Finally, docommand() informs the redirection code that the command completed by calling RedirectionCmdDone() (see Listing 5.10).



Listing 5.10: RedirectionCmdDone().







void
RedirectionCmdDone(void)
{
if (RedirectState != REDIRECT_UNINITIALIZED) {
RedirectState = REDIRECT_IDLE;
if (RedirectFile[0]) {
tfsadd(RedirectFile,0,0,(uchar *)RedirectBase,
(int)(RedirectPtr-RedirectBase));
RedirectFile[0] = 0;
RedirectPtr = RedirectBase;
}
}
}













If the state has been previously initialized (by RedirectionCheck())
this function checks for the presence of the redirection filename. The
redirection filename is only present if the filename was specified as
part of the command. Assuming the redirection filename is set, the
buffer is transferred to a file using the flash file system call tfsadd(), which simply transfers a block of memory to a file in the flash file system.


Although in some implementations redirection can
be a complicated add-on, here the whole facility is really trivial to
incorporate into the CLI. Notice that the only complexity added to docommand() are calls to RedirectionCheck() and RedirectionCmdDone().
This implementation of command-line redirection is a good example of
modular functionality. Complexity for the feature is limited to the
function responsible for the complexity; the complexity is not
distributed throughout other components.



































No comments: