windows-nt/Source/XPSP1/NT/base/efiutil/sdk/shell/shellenv/batch.c
2020-09-26 16:20:57 +08:00

1004 lines
24 KiB
C

/*++
Copyright (c) 1999 Intel Corporation
Module Name:
batch.c
Abstract:
Functions implementing batch scripting in the shell.
Revision History
--*/
#include "shelle.h"
/*
* Constants
*/
#define ASCII_LF ((CHAR8)0x0a)
#define ASCII_CR ((CHAR8)0x0d)
#define UNICODE_LF ((CHAR16)0x000a)
#define UNICODE_CR ((CHAR16)0x000d)
/* Can hold 64-bit hex error numbers + null char */
#define LASTERROR_BUFSIZ (17)
/*
* Statics
* (needed to maintain state across multiple calls or for callbacks)
*/
STATIC UINTN NestLevel;
STATIC UINTN LastError;
STATIC CHAR16 LastErrorBuf[LASTERROR_BUFSIZ];
STATIC BOOLEAN Condition;
STATIC BOOLEAN GotoIsActive;
STATIC UINT64 GotoFilePos;
STATIC BOOLEAN BatchIsActive;
STATIC BOOLEAN EchoIsOn;
STATIC BOOLEAN BatchAbort;
STATIC SIMPLE_INPUT_INTERFACE *OrigConIn;
STATIC SIMPLE_TEXT_OUTPUT_INTERFACE *OrigConOut;
STATIC EFI_FILE_HANDLE CurrentBatchFile;
/*
* Definitions for the argument list stack
*
* In order to support nested scripts (script calling script calling script...)
* there is an argument list stack "BatchInfoStack". BatchInfoStack is a
* list of argument lists. Each argument list contains Argv[0] - Argv[n]
* for the corresponding script file. The head of BatchInfoStack corresponds
* to the currently active script file.
*
* This allows positional argument substitution to be done when each line
* is read and scanned, and calls to other script files can overwrite the
* shell interface's argument list.
*/
#define EFI_BATCH_INFO_SIGNATURE EFI_SIGNATURE_32('b','i','r','g')
typedef struct {
UINTN Signature;
LIST_ENTRY Link;
CHAR16 *ArgValue;
} EFI_SHELL_BATCH_INFO;
#define EFI_BATCH_INFOLIST_SIGNATURE EFI_SIGNATURE_32('b','l','s','t')
typedef struct {
UINTN Signature;
LIST_ENTRY Link;
LIST_ENTRY ArgListHead; /* Head of this argument list */
UINT64 FilePosition; /* Current file position */
} EFI_SHELL_BATCH_INFOLIST;
STATIC LIST_ENTRY BatchInfoStack;
/*
* Prototypes
*/
STATIC EFI_STATUS
BatchIsAscii(
IN EFI_FILE_HANDLE File,
OUT BOOLEAN *IsAscii
);
STATIC EFI_STATUS
BatchGetLine(
IN EFI_FILE_HANDLE File,
IN BOOLEAN Ascii,
IN OUT UINT64 *FilePosition,
IN OUT UINTN *BufSize,
OUT CHAR16 *CommandLine
);
VOID
SEnvInitBatch(
VOID
)
/*++
Function Name:
SEnvInitBatch
Description:
Initializes global variables used for batch file processing.
--*/
{
NestLevel = 0;
LastError = EFI_SUCCESS;
ZeroMem( LastErrorBuf, LASTERROR_BUFSIZ );
Condition = TRUE;
GotoIsActive = FALSE;
GotoFilePos = (UINT64)0x00;
BatchIsActive = FALSE;
EchoIsOn = TRUE;
BatchAbort = FALSE;
OrigConIn = ST->ConIn;
OrigConOut = ST->ConOut;
InitializeListHead( &BatchInfoStack );
SEnvInitForLoopInfo();
}
BOOLEAN
SEnvBatchIsActive(
VOID
)
/*++
Function Name:
SEnvBatchIsActive
Description:
Returns whether any batch files are currently being processed.
--*/
{
/*
* BUGBUG should be able to return IsListEmpty( &BatchInfoStack );
* instead of using this variable
*/
return BatchIsActive;
}
VOID
SEnvSetBatchAbort(
VOID
)
/*++
Function Name:
SEnvSetBatchAbort
Description:
Sets a flag to notify the main batch processing loop to exit.
--*/
{
BatchAbort = TRUE;
return;
}
VOID
SEnvBatchGetConsole(
OUT SIMPLE_INPUT_INTERFACE **ConIn,
OUT SIMPLE_TEXT_OUTPUT_INTERFACE **ConOut
)
/*++
Function Name:
SEnvBatchGetConsole
Description:
Returns the Console I/O interface pointers.
--*/
{
*ConIn = OrigConIn;
*ConOut = OrigConOut;
return;
}
EFI_STATUS
SEnvBatchEchoCommand(
IN ENV_SHELL_INTERFACE *Shell
)
/*++
Function Name:
SEnvBatchEchoCommand
Description:
Echoes the given command to stdout.
--*/
{
UINTN i;
CHAR16 *BatchFileName;
EFI_STATUS Status;
/*
* Echo the parsed-and-expanded command to the console
*/
if ( SEnvBatchIsActive() && EchoIsOn ) {
BatchFileName = NULL;
Status = SEnvBatchGetArg( 0, &BatchFileName );
if ( EFI_ERROR(Status) ) {
goto Done;
}
Print( L"%E" );
for ( i=0; i<NestLevel; i++ ) {
Print( L"+" );
}
Print( L"%s> ", BatchFileName );
for ( i=0; i<Shell->ShellInt.Argc; i++ ) {
Print( L"%s ", Shell->ShellInt.Argv[i] );
}
for ( i=0; i<Shell->ShellInt.RedirArgc; i++ ) {
Print( L"%s ", Shell->ShellInt.RedirArgv[i] );
}
Print( L"\n" );
}
Done:
/*
* Switch output attribute to normal
*/
Print (L"%N");
return Status;
}
VOID
SEnvBatchSetEcho(
IN BOOLEAN Val
)
/*++
Function Name:
SEnvBatchSetEcho
Description:
Sets the echo flag to the specified value.
--*/
{
EchoIsOn = Val;
return;
}
BOOLEAN
SEnvBatchGetEcho(
VOID
)
/*++
Function Name:
SEnvBatchGetEcho
Description:
Returns the echo flag.
--*/
{
return EchoIsOn;
}
EFI_STATUS
SEnvBatchSetFilePos(
IN UINT64 NewPos
)
/*++
Function Name:
SEnvBatchSetFilePos
Description:
Sets the current script file position to the specified value.
--*/
{
EFI_STATUS Status = EFI_SUCCESS;
EFI_SHELL_BATCH_INFOLIST *BatchInfo = NULL;
Status = CurrentBatchFile->SetPosition( CurrentBatchFile, NewPos );
if ( EFI_ERROR(Status) ) {
goto Done;
}
if ( !IsListEmpty( &BatchInfoStack ) ) {
BatchInfo = CR( BatchInfoStack.Flink,
EFI_SHELL_BATCH_INFOLIST,
Link,
EFI_BATCH_INFOLIST_SIGNATURE );
}
if ( BatchInfo ) {
BatchInfo->FilePosition = NewPos;
} else {
Status = EFI_NOT_FOUND;
goto Done;
}
Done:
return Status;
}
EFI_STATUS
SEnvBatchGetFilePos(
UINT64 *FilePos
)
/*++
Function Name:
SEnvBatchGetFilePos
Description:
Returns the current script file position.
--*/
{
EFI_SHELL_BATCH_INFOLIST *BatchInfo = NULL;
EFI_STATUS Status = EFI_SUCCESS;
if ( !FilePos ) {
Status = EFI_INVALID_PARAMETER;
goto Done;
}
if ( !IsListEmpty( &BatchInfoStack ) ) {
BatchInfo = CR( BatchInfoStack.Flink,
EFI_SHELL_BATCH_INFOLIST,
Link,
EFI_BATCH_INFOLIST_SIGNATURE );
}
if ( BatchInfo ) {
*FilePos = BatchInfo->FilePosition;
} else {
Status = EFI_NOT_FOUND;
goto Done;
}
Done:
return Status;
}
VOID
SEnvBatchSetCondition(
IN BOOLEAN Val
)
/*++
Function Name:
SEnvBatchSetCondition
Description:
Sets the condition flag to the specified value.
--*/
{
Condition = Val;
return;
}
VOID
SEnvBatchSetGotoActive(
VOID
)
/*++
Function Name:
SEnvBatchSetGotoActive
Description:
Sets the goto-is-active to TRUE and saves the current position
of the active script file.
--*/
{
GotoIsActive = TRUE;
SEnvBatchGetFilePos( &GotoFilePos );
return;
}
BOOLEAN
SEnvBatchVarIsLastError(
IN CHAR16 *Name
)
/*++
Function Name:
SEnvBatchVarIsLastError
Description:
Checks to see if variable's name is "lasterror".
--*/
{
return (StriCmp( L"lasterror", Name ) == 0);
}
VOID
SEnvBatchSetLastError(
IN UINTN NewLastError
)
/*++
Function Name:
SEnvBatchSetLastError
Description:
Sets the lasterror variable's value to the given value.
--*/
{
LastError = NewLastError;
return;
}
CHAR16*
SEnvBatchGetLastError( VOID
)
/*++
Function Name:
SEnvBatchGetLastError
Description:
Returns a pointer to a string representation of the error value
returned by the last shell command.
--*/
{
ValueToHex( LastErrorBuf, (UINT64)LastError );
return LastErrorBuf;
}
STATIC EFI_STATUS
BatchIsAscii(
IN EFI_FILE_HANDLE File,
OUT BOOLEAN *IsAscii
)
/*++
Function Name:
BatchIsAscii
Description:
Checks to see if the specified batch file is ASCII.
--*/
{
EFI_STATUS Status=EFI_SUCCESS;
CHAR8 Buffer8[2]; /* UNICODE byte-order-mark is two bytes */
UINTN BufSize;
/*
* Read the first two bytes to check for byte order mark
*/
BufSize = sizeof(Buffer8);
Status = File->Read( File, &BufSize, Buffer8 );
if ( EFI_ERROR(Status) ) {
goto Done;
}
Status = File->SetPosition( File, (UINT64)0 );
if ( EFI_ERROR(Status) ) {
goto Done;
}
/*
* If we find a UNICODE byte order mark assume it is UNICODE,
* otherwise assume it is ASCII. UNICODE byte order mark on
* IA little endian is first byte 0xff and second byte 0xfe
*/
if ( (Buffer8[0] | (Buffer8[1] << 8)) == UNICODE_BYTE_ORDER_MARK ) {
*IsAscii = FALSE;
} else {
*IsAscii = TRUE;
}
Done:
return Status;
}
STATIC EFI_STATUS
BatchGetLine(
IN EFI_FILE_HANDLE File,
IN BOOLEAN Ascii,
IN OUT UINT64 *FilePosition,
IN OUT UINTN *BufSize,
OUT CHAR16 *CommandLine
)
/*++
Function Name:
BatchGetLine
Description:
Reads the next line from the batch file, converting it from
ASCII to UNICODE if necessary. If end of file is encountered
then it returns 0 in the BufSize parameter.
--*/
{
EFI_STATUS Status;
CHAR8 Buffer8[MAX_CMDLINE];
CHAR16 Buffer16[MAX_CMDLINE];
UINTN i = 0;
UINTN CmdLenInChars = 0;
UINTN CmdLenInBytes = 0;
UINTN CharSize = 0;
/*
* Check params
*/
if ( !CommandLine || !BufSize || !FilePosition ) {
Status = EFI_INVALID_PARAMETER;
goto Done;
}
/*
* Initialize OUT param
*/
ZeroMem( CommandLine, MAX_CMDLINE );
/*
* If beginning of UNICODE file, move past the Byte-Order-Mark (2 bytes)
*/
if ( !Ascii && *FilePosition == (UINT64)0 ) {
*FilePosition = (UINT64)2;
Status = File->SetPosition( File, *FilePosition );
if ( EFI_ERROR(Status) ) {
goto Done;
}
}
/*
* (1) Read a buffer-full from the file
* (2) Locate the end of the 1st line in the buffer
* ASCII version and UNICODE version
*/
if ( Ascii ) {
CharSize = sizeof(CHAR8);
Status = File->Read( File, BufSize, Buffer8 );
if ( EFI_ERROR(Status) || *BufSize == 0 ) {
goto Done;
}
for ( i=0; i<*BufSize; i++ ) {
if ( Buffer8[i] == ASCII_LF ) {
CmdLenInChars = i;
CmdLenInBytes = CmdLenInChars;
break;
}
}
} else { /* UNICODE */
CharSize = sizeof(CHAR16);
Status = File->Read( File, BufSize, Buffer16 );
if ( EFI_ERROR(Status) || *BufSize == 0 ) {
goto Done;
}
for ( i=0; i < *BufSize/CharSize; i++ ) {
if ( Buffer16[i] == UNICODE_LF ) {
CmdLenInChars = i;
CmdLenInBytes = CmdLenInChars * CharSize;
break;
}
}
}
/*
* Reset the file position to just after the command line
*/
*FilePosition += (UINT64)(CmdLenInBytes + CharSize);
Status = File->SetPosition( File, *FilePosition );
/*
* Copy, converting chars to UNICODE if necessary
*/
if ( Ascii ) {
for ( i=0; i<CmdLenInChars; i++ ) {
CommandLine[i] = (CHAR16)Buffer8[i];
}
} else {
CopyMem( CommandLine, Buffer16, CmdLenInBytes );
}
CmdLenInChars = i;
Done:
*BufSize = CmdLenInChars * CharSize;
return Status;
}
EFI_STATUS
SEnvBatchGetArg(
IN UINTN Argno,
OUT CHAR16 **Argval
)
/*++
Function Name:
BatchGetArg
Description:
Extract the specified element from the arglist at the top of the arglist
stack. Return a pointer to the value field of the "Argno"th element of
the list.
--*/
{
EFI_SHELL_BATCH_INFOLIST *BatchInfo = NULL;
LIST_ENTRY *Link = NULL;
EFI_SHELL_BATCH_INFO *ArgEntry = NULL;
UINTN i = 0;
if ( !IsListEmpty( &BatchInfoStack ) ) {
BatchInfo = CR( BatchInfoStack.Flink,
EFI_SHELL_BATCH_INFOLIST,
Link,
EFI_BATCH_INFOLIST_SIGNATURE );
}
if ( !IsListEmpty( &BatchInfo->ArgListHead ) ) {
for ( Link=BatchInfo->ArgListHead.Flink;
Link!=&BatchInfo->ArgListHead;
Link=Link->Flink) {
ArgEntry = CR( Link,
EFI_SHELL_BATCH_INFO,
Link,
EFI_BATCH_INFO_SIGNATURE);
if ( i++ == Argno ) {
*Argval = ArgEntry->ArgValue;
return EFI_SUCCESS;
}
}
}
*Argval = NULL;
return EFI_NOT_FOUND;
}
EFI_STATUS
SEnvExecuteScript(
IN ENV_SHELL_INTERFACE *Shell,
IN EFI_FILE_HANDLE File
)
/*++
Function Name:
SEnvExecuteScript
Description:
Execute the commands in the script file specified by the
file parameter.
Arguments:
Shell: shell interface of the caller
File: file handle to open script file
Returns:
EFI_STATUS
--*/
{
EFI_FILE_INFO *FileInfo;
UINTN FileNameLen = 0;
BOOLEAN EndOfFile = FALSE;
EFI_STATUS Status = EFI_SUCCESS;
UINTN BufSize = 0;
UINTN FileInfoSize = 0;
CHAR16 CommandLine[MAX_CMDLINE];
EFI_SHELL_BATCH_INFOLIST *BatchInfo = NULL;
EFI_SHELL_BATCH_INFO *ArgEntry = NULL;
UINTN i = 0;
BOOLEAN Output = TRUE;
ENV_SHELL_INTERFACE NewShell;
UINTN GotoTargetStatus;
UINTN SkippedIfCount;
/*
* Initialize
*/
BatchIsActive = TRUE;
Status = EFI_SUCCESS;
NestLevel++;
SEnvInitTargetLabel();
/*
* Check params
*/
if ( !File ) {
Status = EFI_INVALID_PARAMETER;
goto Done;
}
/*
* Figure out if the file is ASCII or UNICODE.
*/
Status = BatchIsAscii( File, &Shell->StdIn.Ascii );
if ( EFI_ERROR( Status ) ) {
goto Done;
}
/*
* Get the filename from the file handle.
*/
/*
* Allocate buffer for file info (including file name)
* BUGBUG 1024 arbitrary space for filename, as elsewhere in shell
*/
FileInfoSize = SIZE_OF_EFI_FILE_INFO + 1024;
FileInfo = AllocatePool(FileInfoSize);
if (!FileInfo) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
/* Get file info */
Status = File->GetInfo( File,
&GenericFileInfo,
&FileInfoSize,
FileInfo );
if ( EFI_ERROR(Status) ) {
return Status;
}
/*
* Save the handle
*/
CurrentBatchFile = File;
/*
* Initialize argument list for this script
* This list is needed since nested batch files would overwrite the
* argument list in Shell->ShellInt.Argv[]. Here we maintain the args in
* a local list on the stack.
*/
BatchInfo = AllocateZeroPool( sizeof( EFI_SHELL_BATCH_INFOLIST ) );
if ( !BatchInfo ) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
BatchInfo->Signature = EFI_BATCH_INFOLIST_SIGNATURE;
BatchInfo->FilePosition = (UINT64)0x00;
InitializeListHead( &BatchInfo->ArgListHead );
for ( i=0; i<Shell->ShellInt.Argc; i++ ) {
/* Allocate the new element of the argument list */
ArgEntry = AllocateZeroPool( sizeof( EFI_SHELL_BATCH_INFO ) );
if ( !ArgEntry ) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
/* Allocate space for the argument string in the arglist element */
ArgEntry->ArgValue = AllocateZeroPool(StrSize(Shell->ShellInt.Argv[i]));
if ( !ArgEntry->ArgValue ) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
/* Copy in the argument string */
StrCpy( ArgEntry->ArgValue, Shell->ShellInt.Argv[i] );
ArgEntry->Signature = EFI_BATCH_INFO_SIGNATURE;
/* Add the arglist element to the end of the list */
InsertTailList( &BatchInfo->ArgListHead, &ArgEntry->Link );
}
/* Push the arglist onto the arglist stack */
InsertHeadList( &BatchInfoStack, &BatchInfo->Link );
/*
* Iterate through the file, reading a line at a time and executing each
* line as a shell command. Nested shell scripts will come through
* this code path recursively.
*/
EndOfFile = FALSE;
SkippedIfCount = 0;
while (1) {
/*
* Read a command line from the file
*/
BufSize = MAX_CMDLINE;
Status = BatchGetLine( File,
Shell->StdIn.Ascii,
&BatchInfo->FilePosition,
&BufSize,
CommandLine );
if ( EFI_ERROR( Status ) ) {
goto Done;
}
/*
* No error and no chars means EOF
* If we are in the middle of a GOTO then rewind to search for the
* label from the beginning of the file, otherwise we are done
* with this script.
*/
if ( BufSize == 0 ) {
if ( GotoIsActive ) {
BatchInfo->FilePosition = (UINT64)(0x00);
Status = File->SetPosition( File, BatchInfo->FilePosition );
if ( EFI_ERROR( Status ) ) {
goto Done;
} else {
continue;
}
} else {
goto Done;
}
}
/*
* Convert the command line to an arg list
*/
ZeroMem( &NewShell, sizeof(NewShell ) );
Status = SEnvStringToArg(
CommandLine,
TRUE,
&NewShell.ShellInt.Argv,
&NewShell.ShellInt.Argc
);
if (EFI_ERROR(Status)) {
goto Done;
}
/*
* Skip comments and blank lines
*/
if ( NewShell.ShellInt.Argc == 0 ) {
continue;
}
/*
* If a GOTO command is active, skip everything until we find
* the target label or until we determine it doesn't exist.
*/
if ( GotoIsActive ) {
/*
* Check if we have the right label or if we've searched
* the whole file
*/
Status = SEnvCheckForGotoTarget( NewShell.ShellInt.Argv[0],
GotoFilePos,
BatchInfo->FilePosition,
&GotoTargetStatus );
if ( EFI_ERROR( Status ) ) {
goto Done;
}
switch ( GotoTargetStatus ) {
case GOTO_TARGET_FOUND:
GotoIsActive = FALSE;
SEnvFreeTargetLabel();
continue;
case GOTO_TARGET_NOT_FOUND:
continue;
case GOTO_TARGET_DOESNT_EXIST:
GotoIsActive = FALSE;
Status = EFI_INVALID_PARAMETER;
LastError = Status;
SEnvPrintLabelNotFound();
SEnvFreeTargetLabel();
continue;
default:
Status = EFI_INVALID_PARAMETER;
SEnvFreeTargetLabel();
Print( L"Internal error: invalid GotoTargetStatus\n" );
break;
}
} else if ( NewShell.ShellInt.Argv[0][0] == L':' ) {
/*
* Skip labels when no GOTO is active
*/
continue;
}
/*
* Skip everything between an 'if' whose condition was false and its
* matching 'endif'. Note that 'endif' doesn't do anything if
* Condition is TRUE, so we only track endif matching when it is false.
*/
if ( !Condition ) {
if ( StriCmp( NewShell.ShellInt.Argv[0], L"if") == 0 ) {
/*
* Keep track of how many endifs we have to skip before we are
* done with the FALSE Condition
*/
SkippedIfCount += 1;
continue;
} else if ( StriCmp( NewShell.ShellInt.Argv[0], L"endif") == 0 ) {
if ( SkippedIfCount > 0 ) {
SkippedIfCount -= 1;
continue;
}
/*
* When SkippedIfCount goes to zero (as here), we have the
* endif that matches the if with the FALSE condition that
* we are dealing with, so we want to fall through and have
* the endif command reset the Condition flag.
*/
} else {
/*
* Condition FALSE, not an if or an endif, so skip
*/
continue;
}
}
/*
* Execute the command
*/
LastError = SEnvDoExecute(
Shell->ShellInt.ImageHandle,
CommandLine,
&NewShell,
TRUE
);
/*
* Save the current file handle
*/
CurrentBatchFile = File;
if ( BatchAbort ) {
goto Done;
}
}
Done:
/*
* Clean up
*/
/* Decrement the count of open script files */
NestLevel--;
/* Free any potential remaining GOTO target label */
SEnvFreeTargetLabel();
/* Reset the IF condition to TRUE, even if no ENDIF was found */
SEnvBatchSetCondition( TRUE );
/* Close the script file */
if ( File ) {
File->Close( File );
}
/* Free the file info structure used to get the file name from the handle */
if ( FileInfo ) {
FreePool( FileInfo );
FileInfo = NULL;
}
/* Pop the argument list for this script off the stack */
if ( !IsListEmpty( &BatchInfoStack ) ) {
BatchInfo = CR( BatchInfoStack.Flink,
EFI_SHELL_BATCH_INFOLIST,
Link,
EFI_BATCH_INFOLIST_SIGNATURE );
RemoveEntryList( &BatchInfo->Link );
}
/* Free the argument list for this script file */
while ( !IsListEmpty( &BatchInfo->ArgListHead ) ) {
ArgEntry = CR( BatchInfo->ArgListHead.Flink,
EFI_SHELL_BATCH_INFO,
Link,
EFI_BATCH_INFO_SIGNATURE );
if ( ArgEntry ) {
RemoveEntryList( &ArgEntry->Link );
if ( ArgEntry->ArgValue ) {
FreePool( ArgEntry->ArgValue );
ArgEntry->ArgValue = NULL;
}
FreePool( ArgEntry );
ArgEntry = NULL;
}
}
FreePool( BatchInfo );
BatchInfo = NULL;
/*
* If we are returning to the interactive shell, then reset
* the batch-is-active flag
*/
if ( IsListEmpty( &BatchInfoStack ) ) {
BatchIsActive = FALSE;
BatchAbort = FALSE;
}
return Status;
}