698 lines
16 KiB
C
698 lines
16 KiB
C
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
/*--------------- Includes --------------*/
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "Cli.h"
|
|
|
|
/*-- Symbolic Constant Macros (defines) --*/
|
|
#define MAX_LINE_HISTORY 2
|
|
#define MAX_MENUS 100
|
|
|
|
/*-------------- Typedefs ----------------*/
|
|
typedef enum _CLI_ESC_CTRL_T
|
|
{
|
|
CLI_ESC_NONE,
|
|
CLI_ESC_PENDING1,
|
|
CLI_ESC_PENDING2,
|
|
CLI_ESC_UP
|
|
} CLI_ESC_CTRL_T;
|
|
|
|
typedef struct _CLI_COMMAND_LIST
|
|
{
|
|
CLI_COMMAND_T Cmd;
|
|
struct _CLI_COMMAND_LIST *Next;
|
|
} CLI_COMMAND_LIST;
|
|
|
|
/*-- Declarations (Statics and globals) --*/
|
|
static char *Prompt = CLI_MAIN_MENU;
|
|
static CLI_COMMAND_LIST *RegisteredCmds = NULL;
|
|
static bool ShellOpen = false;
|
|
|
|
/*--------- Function Prototypes ----------*/
|
|
void Help(int argc, char **argv);
|
|
bool StrHasUpper(const char *pszStr);
|
|
|
|
/*-------------- Functions ---------------*/
|
|
/**
|
|
* Converts a string to a UINT32 value
|
|
*
|
|
* @param str
|
|
* Pointer to the string to conver
|
|
*
|
|
* @return
|
|
* TRUE The data was correctly formated and converted
|
|
* FALSE The data MAY have been converted but it was incorrectly formated
|
|
*
|
|
*/
|
|
bool CliGetStrVal(const char *str, uint32_t *pOut)
|
|
{
|
|
size_t len = strlen(str);
|
|
|
|
// Default to decimal argument.
|
|
*pOut = 0;
|
|
int32_t base = 10;
|
|
uint32_t start = 0;
|
|
|
|
if (len == 0)
|
|
return false;
|
|
|
|
// If find '0x' or '0X', change to hexadecimal argument.
|
|
if (len > 1 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
|
|
{
|
|
start = 2;
|
|
base = 16;
|
|
}
|
|
|
|
*pOut = (uint32_t)strtoul(&str[start], NULL, base);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* function to extract binary data from space separated string
|
|
*
|
|
* @param pSrc Pointer to the string source
|
|
* @param pDst Pointer to where the binary data will be placed
|
|
* @param Size Size of the Destination pointed to by pDst
|
|
*
|
|
* @return
|
|
* UINT8 Numb of bytes converted
|
|
*/
|
|
uint8_t CliGetBin(char *pSrc, uint8_t *pDst, uint32_t Size)
|
|
{
|
|
uint8_t Index = 0;
|
|
uint32_t Value;
|
|
|
|
// Go through the input string and parse to binary data
|
|
while ((*pSrc != 0) && (Index < Size))
|
|
{
|
|
// skip white spaces
|
|
while ((*pSrc < 48) && (*pSrc != 0))
|
|
{
|
|
pSrc++;
|
|
}
|
|
if (*pSrc != 0)
|
|
{
|
|
CliGetStrVal((const char *)pSrc, &Value);
|
|
pDst[Index++] = Value & 0xFF;
|
|
// move pointer beyond value
|
|
while ((*pSrc > 47) && (*pSrc != 0))
|
|
{
|
|
pSrc++;
|
|
}
|
|
}
|
|
}
|
|
return Index;
|
|
}
|
|
|
|
/**
|
|
* This function detects if an escape character
|
|
* is entered.
|
|
*
|
|
* @param Ch Character to include for esc check
|
|
*
|
|
* @return
|
|
* CLI_ESC_NONE No characters detected
|
|
* CLI_ESC_PENDING1 First stage of escape character detected
|
|
* CLI_ESC_PENDING2 Second stage of escape character detected
|
|
* CLI_ESC_UP - Up arrow detected
|
|
*/
|
|
static CLI_ESC_CTRL_T CliEsc(char Ch)
|
|
{
|
|
static CLI_ESC_CTRL_T EscChar = CLI_ESC_NONE;
|
|
|
|
if (Ch == 0x1B)
|
|
{
|
|
EscChar = CLI_ESC_PENDING1;
|
|
}
|
|
|
|
else if ((EscChar == CLI_ESC_PENDING1) && (Ch == '['))
|
|
{
|
|
EscChar = CLI_ESC_PENDING2;
|
|
}
|
|
|
|
else if (EscChar == CLI_ESC_PENDING2 && Ch == 'A')
|
|
{
|
|
EscChar = CLI_ESC_UP;
|
|
}
|
|
else
|
|
{
|
|
EscChar = CLI_ESC_NONE;
|
|
}
|
|
|
|
return EscChar;
|
|
}
|
|
|
|
/**
|
|
* Get Line from serial stream
|
|
*
|
|
* @param pBuffer Pointer to the buffer the will hold the line
|
|
*
|
|
* @param Length The size of the buffer handed in
|
|
*
|
|
* @return
|
|
* uint16_t number of bytes received
|
|
*
|
|
*/
|
|
static uint16_t CliGetLine(char *pBuffer, uint8_t Length)
|
|
{
|
|
char Ch;
|
|
uint16_t NumChar = 0;
|
|
static char Line[MAX_LINE_HISTORY][MAX_LINE_LENGTH];
|
|
static uint8_t LineCount;
|
|
static uint8_t LinePosition;
|
|
CLI_ESC_CTRL_T EscCtrl;
|
|
|
|
while (NumChar < Length)
|
|
{
|
|
Ch = (char)getchar();
|
|
|
|
// Check for LF or CR.
|
|
if (Ch == '\n' || Ch == '\r')
|
|
{
|
|
pBuffer[NumChar] = 0;
|
|
|
|
// Exit loop
|
|
break;
|
|
}
|
|
|
|
// Check for full cmd buffer.
|
|
if (NumChar >= Length - 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check for ESC sequence: <ESC> [ 1 3 ~
|
|
EscCtrl = CliEsc(Ch);
|
|
if (EscCtrl != CLI_ESC_NONE)
|
|
{
|
|
if ((EscCtrl == CLI_ESC_UP) && (LineCount > 0))
|
|
{
|
|
// Last command
|
|
// go back 1 cmd position
|
|
if (LinePosition != 0)
|
|
{
|
|
LinePosition--;
|
|
}
|
|
else
|
|
{
|
|
LinePosition = LineCount - 1;
|
|
}
|
|
|
|
// backspace current number of characters
|
|
while (NumChar > 0)
|
|
{
|
|
NumChar--;
|
|
}
|
|
|
|
// Copy last command to buffer
|
|
NumChar = (uint16_t)strlen(Line[LinePosition]);
|
|
if (NumChar > Length)
|
|
{
|
|
NumChar = Length;
|
|
}
|
|
|
|
memcpy(pBuffer, Line[LinePosition], NumChar);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Check for backspace or del character.
|
|
else if ((Ch == '\b') || (Ch == 0x7F))
|
|
{
|
|
if (NumChar > 0)
|
|
{
|
|
NumChar--;
|
|
}
|
|
}
|
|
|
|
// Check for uppercase character.
|
|
else if (Ch >= 'A' && Ch <= 'Z')
|
|
{
|
|
pBuffer[NumChar++] = Ch - 'A' + 'a';
|
|
}
|
|
|
|
// Check for printable character.
|
|
else
|
|
{
|
|
pBuffer[NumChar++] = Ch;
|
|
}
|
|
}
|
|
|
|
if (NumChar > 0)
|
|
{
|
|
// Copy to buffer history
|
|
memcpy(Line[LinePosition], pBuffer, strlen(pBuffer));
|
|
|
|
LinePosition++;
|
|
if (LinePosition > LineCount)
|
|
{
|
|
LineCount = LinePosition;
|
|
}
|
|
if (LinePosition >= MAX_LINE_HISTORY)
|
|
{
|
|
LinePosition = 0; // loop through available buffer
|
|
}
|
|
}
|
|
|
|
return NumChar;
|
|
}
|
|
|
|
/**
|
|
* This utility function will parse the input line by separating
|
|
* the arguments by NULLs.
|
|
*
|
|
* @param pLine Pointer to the string to parse
|
|
*
|
|
* @param Argv Pointer to the pointer array for arguments
|
|
*
|
|
* @param Length Maximum number of arguments to parse
|
|
*
|
|
* @return
|
|
* int Number of arguments found
|
|
*
|
|
*/
|
|
static int ParseLine(char *pLine, char **Argv, uint8_t Length)
|
|
{
|
|
int Argc;
|
|
char Ch;
|
|
bool StringVar = false;
|
|
|
|
for (Argc = 0; Argc < Length;)
|
|
{
|
|
// Skip whitespace.
|
|
while ((Ch = *pLine) != 0)
|
|
{
|
|
if (Ch == '"')
|
|
{
|
|
StringVar = true;
|
|
}
|
|
if (Ch != ' ' && Ch != '\t' && Ch != '"')
|
|
{
|
|
break;
|
|
}
|
|
pLine++;
|
|
}
|
|
if (Ch == 0)
|
|
break;
|
|
|
|
// Record start of current argument.
|
|
Argv[Argc] = pLine;
|
|
Argc++;
|
|
|
|
// Skip up to next whitespace.
|
|
while ((Ch = *pLine) != 0)
|
|
{
|
|
if (StringVar)
|
|
{
|
|
if (Ch == '"')
|
|
{
|
|
StringVar = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Ch == ' ' || Ch == '\t')
|
|
break;
|
|
}
|
|
pLine++;
|
|
}
|
|
|
|
// Terminate argument string.
|
|
if (Ch != 0)
|
|
{
|
|
*pLine++ = 0;
|
|
}
|
|
}
|
|
return Argc;
|
|
}
|
|
|
|
/**
|
|
* Shell loop for processing commands.
|
|
*
|
|
*/
|
|
static void CliShell(void)
|
|
{
|
|
char Buffer[MAX_LINE_LENGTH];
|
|
static int MyArgc;
|
|
static char *MyArgv[MAX_ARGS];
|
|
|
|
ShellOpen = true;
|
|
printf("%s>", Prompt);
|
|
|
|
while (ShellOpen)
|
|
{
|
|
CliGetLine(Buffer, MAX_LINE_LENGTH);
|
|
|
|
MyArgc = ParseLine(Buffer, MyArgv, MAX_ARGS);
|
|
|
|
// Parse and execute command.
|
|
CliExecute(MyArgc, &MyArgv[0]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exit command for shell
|
|
*
|
|
*/
|
|
static void CliExit(void)
|
|
{
|
|
ShellOpen = false;
|
|
}
|
|
|
|
/**
|
|
* This function is execute the input from the user
|
|
*
|
|
* @param Argc Number of variables handed in
|
|
*
|
|
* @param Argv Pointer to variable array
|
|
*
|
|
* @return
|
|
* CLI_SUCCESS If executed properly
|
|
* CLI_ERROR If error was detected
|
|
*
|
|
*/
|
|
int CliExecute(int Argc, char **Argv)
|
|
{
|
|
CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds;
|
|
CLI_COMMAND_T *pCmd;
|
|
|
|
CLI_STATUS Status = CLI_SUCCESS;
|
|
|
|
// Search for command.
|
|
printf("\n");
|
|
|
|
// check for help
|
|
if ((Argc == 0) || (strcmp(Argv[0], "?") == 0))
|
|
{
|
|
// Print help information
|
|
Help(Argc, &Argv[0]);
|
|
printf("%s>", Prompt);
|
|
return (int)Status;
|
|
}
|
|
|
|
// check for shell
|
|
if (strcmp(Argv[0], "shell") == 0)
|
|
{
|
|
// Go into command line shell
|
|
CliShell();
|
|
return (int)Status;
|
|
}
|
|
|
|
// check for exit
|
|
if (strcmp(Argv[0], "exit") == 0)
|
|
{
|
|
// Exit shell if in it
|
|
CliExit();
|
|
return (int)Status;
|
|
}
|
|
|
|
// check for main menu
|
|
if (strcmp(Argv[0], "..") == 0)
|
|
{
|
|
// Go to main menu
|
|
Prompt = CLI_MAIN_MENU;
|
|
printf("%s>", Prompt);
|
|
return (int)Status;
|
|
}
|
|
|
|
// Find command
|
|
while (pCmdEntry != NULL)
|
|
{
|
|
pCmd = &(pCmdEntry->Cmd);
|
|
if ((strcmp(Argv[0], pCmd->Menu) == 0) && (Argc == 1))
|
|
{
|
|
// Change prompts and menu level
|
|
Prompt = pCmd->Menu;
|
|
break;
|
|
}
|
|
|
|
if ((strcmp(Prompt, pCmd->Menu) == 0) && (strcmp(Argv[0], pCmd->Command) == 0))
|
|
{
|
|
Status = pCmd->Routine(Argc, &Argv[0]);
|
|
if (Status == CLI_SUCCESS)
|
|
{
|
|
printf("Ok\n");
|
|
}
|
|
else
|
|
{
|
|
printf("ERROR\n");
|
|
}
|
|
break;
|
|
}
|
|
else if ((Argc > 1) && (strcmp(Argv[0], pCmd->Menu) == 0) && (strcmp(Argv[1], pCmd->Command) == 0))
|
|
{
|
|
Status = pCmd->Routine(Argc - 1, &Argv[1]);
|
|
if (Status == CLI_SUCCESS)
|
|
{
|
|
printf("Ok\n");
|
|
}
|
|
else
|
|
{
|
|
printf("ERROR\n");
|
|
}
|
|
break;
|
|
}
|
|
pCmdEntry = pCmdEntry->Next;
|
|
};
|
|
|
|
if (pCmdEntry == NULL)
|
|
{
|
|
// No commands found to match
|
|
printf("'%s' Invalid command. Type ? for help.\n", Argv[0]);
|
|
Status = CLI_ERROR;
|
|
}
|
|
|
|
printf("%s>", Prompt);
|
|
return (int)Status;
|
|
}
|
|
|
|
/**
|
|
* This function is used to display help information
|
|
*
|
|
* @param Argc Number of variables handed in
|
|
*
|
|
* @param Argv Pointer to variable array
|
|
*
|
|
*/
|
|
void Help(int Argc, char **Argv)
|
|
{
|
|
CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds;
|
|
CLI_COMMAND_T *pCmd;
|
|
uint32_t y;
|
|
|
|
char *Menus[MAX_MENUS] = { NULL };
|
|
|
|
// Display menu or usage information
|
|
while (pCmdEntry != NULL)
|
|
{
|
|
pCmd = &(pCmdEntry->Cmd);
|
|
// Extract list of menus
|
|
for (y = 0; y < MAX_MENUS; y++)
|
|
{
|
|
if (Menus[y] == NULL)
|
|
{
|
|
Menus[y] = pCmd->Menu;
|
|
break;
|
|
}
|
|
else if (strcmp(Menus[y], pCmd->Menu) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (Argc == 1)
|
|
{
|
|
// List all command at current level
|
|
if (strcmp(Prompt, pCmd->Menu) == 0)
|
|
{
|
|
printf("%s", pCmd->Command);
|
|
printf("%s", &" "[strlen(pCmd->Command) & 0xF]);
|
|
printf("%s\n", pCmd->Description);
|
|
}
|
|
}
|
|
else if (Argc == 2)
|
|
{
|
|
// Display help on item at current level
|
|
if ((strcmp(Prompt, pCmd->Menu) == 0) && (strcmp(Argv[1], pCmd->Command) == 0) && (pCmd->Usage != NULL))
|
|
{
|
|
printf("%s\n", pCmd->Usage);
|
|
break;
|
|
}
|
|
}
|
|
else if (Argc == 3)
|
|
{
|
|
// Display sub menu item help
|
|
if ((strcmp(Argv[1], pCmd->Menu) == 0) && (strcmp(Argv[2], pCmd->Command) == 0) && (pCmd->Usage != NULL))
|
|
{
|
|
printf("%s\n", pCmd->Usage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
pCmdEntry = pCmdEntry->Next;
|
|
if (pCmdEntry == NULL)
|
|
{
|
|
// Print out default commands in menu list
|
|
if (strncmp(Prompt, CLI_MAIN_MENU, sizeof(CLI_MAIN_MENU)) != 0)
|
|
{
|
|
printf(".. Goto Main Menu\n");
|
|
}
|
|
else
|
|
{
|
|
// print out available sub-menus
|
|
for (y = 0; y < MAX_MENUS; y++)
|
|
{
|
|
if ((Menus[y] != 0) && (*Menus[y] != 0))
|
|
{
|
|
printf("%s", Menus[y]);
|
|
printf("%s", &" "[strlen(Menus[y]) & 0xF]);
|
|
printf("Menu\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("? Display menu or usage information\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dynamic registration API for CLI commands
|
|
*
|
|
* @param pMenu String pointer for menu syntax
|
|
*
|
|
* @param pSyntax String pointer for command syntax
|
|
*
|
|
* @param Routine Function pointer associated with the command
|
|
*
|
|
* @param pDescription String pointer describing the command
|
|
*
|
|
* @param pUsage Usage string
|
|
*
|
|
*/
|
|
void CliRegister(char *pMenu,
|
|
const char *pSyntax,
|
|
CLI_STATUS (*Routine)(int argc, char **argv),
|
|
const char *pDescription,
|
|
const char *pUsage)
|
|
{
|
|
assert((pSyntax != NULL) && (Routine != NULL) && (pMenu != NULL));
|
|
CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds;
|
|
CLI_COMMAND_LIST *pNewEntry;
|
|
|
|
assert(!StrHasUpper(pMenu));
|
|
assert(!StrHasUpper(pSyntax));
|
|
|
|
pNewEntry = malloc(sizeof(CLI_COMMAND_LIST));
|
|
assert(pNewEntry != NULL);
|
|
pNewEntry->Cmd.Menu = pMenu;
|
|
pNewEntry->Cmd.Command = pSyntax;
|
|
pNewEntry->Cmd.Routine = Routine;
|
|
pNewEntry->Cmd.Description = pDescription;
|
|
pNewEntry->Cmd.Usage = pUsage;
|
|
pNewEntry->Next = NULL;
|
|
|
|
// Fist command?
|
|
if (RegisteredCmds == NULL)
|
|
{
|
|
RegisteredCmds = pNewEntry;
|
|
}
|
|
else
|
|
{
|
|
// Find last command
|
|
while (pCmdEntry->Next != NULL)
|
|
{
|
|
pCmdEntry = pCmdEntry->Next;
|
|
}
|
|
pCmdEntry->Next = pNewEntry;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dynamic registration API for CLI commands
|
|
*
|
|
* @param pTable Pointer to the array of commands to register with
|
|
* the command engine.
|
|
*
|
|
*/
|
|
void CliRegisterTable(CLI_COMMAND_T *pTable)
|
|
{
|
|
while (pTable->Command != NULL)
|
|
{
|
|
CliRegister(pTable->Menu, pTable->Command, pTable->Routine, pTable->Description, pTable->Usage);
|
|
pTable++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method for prompting information from the user
|
|
*
|
|
* @param pPrompt The prompt string that will be displayed to the
|
|
* user when this function is called. If NULL, no
|
|
* prompt will be displayed.
|
|
*
|
|
* @param pBuffer Pointer to the location used to hold
|
|
* the input string
|
|
*
|
|
* @param Length Size of buffer handed in
|
|
*
|
|
* @return
|
|
* uint16_t Number of bytes that were placed in pBuffer
|
|
*/
|
|
uint16_t CliRead(char *pPrompt, char *pBuffer, uint8_t Length)
|
|
{
|
|
if (pPrompt != NULL)
|
|
{
|
|
printf("%s", pPrompt);
|
|
}
|
|
return CliGetLine(pBuffer, Length);
|
|
}
|
|
|
|
/**
|
|
* Method to display the usage string of the function associated with the
|
|
* command syntax
|
|
*
|
|
* @param Routine Function pointer to the function associated
|
|
* with the command syntax.
|
|
*
|
|
*/
|
|
void CliDisplayUsage(CLI_STATUS (*Routine)(int argc, char **argv))
|
|
{
|
|
CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds;
|
|
|
|
while (pCmdEntry != NULL)
|
|
{
|
|
if ((pCmdEntry->Cmd.Routine == Routine) && (pCmdEntry->Cmd.Usage != NULL))
|
|
{
|
|
printf("%s\n", pCmdEntry->Cmd.Usage);
|
|
break;
|
|
}
|
|
pCmdEntry = pCmdEntry->Next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a string has any upper case charater
|
|
*
|
|
* @param pszStr pointer to a NULL terminated string
|
|
*
|
|
* @return
|
|
* true if an upper case character is found in the string
|
|
* false if no upper case character is found in the string
|
|
*/
|
|
bool StrHasUpper(const char *pszStr)
|
|
{
|
|
while (pszStr && *pszStr)
|
|
{
|
|
if (isupper(*pszStr++))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|