/** @file | |
This is THE shell (application) | |
Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.<BR> | |
(C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P.<BR> | |
Copyright 2015-2018 Dell Technologies.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "Shell.h" | |
// | |
// Initialize the global structure | |
// | |
SHELL_INFO ShellInfoObject = { | |
NULL, | |
NULL, | |
FALSE, | |
FALSE, | |
{ | |
{ | |
{ | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0 | |
} | |
}, | |
0, | |
NULL, | |
NULL | |
}, | |
{ | |
{ NULL,NULL }, NULL | |
}, | |
{ | |
{ | |
{ NULL,NULL }, NULL | |
}, | |
0, | |
0, | |
TRUE | |
}, | |
NULL, | |
0, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
{ | |
{ NULL,NULL }, NULL, NULL | |
}, | |
{ | |
{ NULL,NULL }, NULL, NULL | |
}, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
FALSE | |
}; | |
STATIC CONST CHAR16 mScriptExtension[] = L".NSH"; | |
STATIC CONST CHAR16 mExecutableExtensions[] = L".NSH;.EFI"; | |
STATIC CONST CHAR16 mStartupScript[] = L"startup.nsh"; | |
CONST CHAR16 mNoNestingEnvVarName[] = L"nonesting"; | |
CONST CHAR16 mNoNestingTrue[] = L"True"; | |
CONST CHAR16 mNoNestingFalse[] = L"False"; | |
/** | |
Cleans off leading and trailing spaces and tabs. | |
@param[in] String pointer to the string to trim them off. | |
**/ | |
EFI_STATUS | |
TrimSpaces ( | |
IN CHAR16 **String | |
) | |
{ | |
ASSERT (String != NULL); | |
ASSERT (*String != NULL); | |
// | |
// Remove any spaces and tabs at the beginning of the (*String). | |
// | |
while (((*String)[0] == L' ') || ((*String)[0] == L'\t')) { | |
CopyMem ((*String), (*String)+1, StrSize ((*String)) - sizeof ((*String)[0])); | |
} | |
// | |
// Remove any spaces and tabs at the end of the (*String). | |
// | |
while ((StrLen (*String) > 0) && (((*String)[StrLen ((*String))-1] == L' ') || ((*String)[StrLen ((*String))-1] == L'\t'))) { | |
(*String)[StrLen ((*String))-1] = CHAR_NULL; | |
} | |
return (EFI_SUCCESS); | |
} | |
/** | |
Parse for the next instance of one string within another string. Can optionally make sure that | |
the string was not escaped (^ character) per the shell specification. | |
@param[in] SourceString The string to search within | |
@param[in] FindString The string to look for | |
@param[in] CheckForEscapeCharacter TRUE to skip escaped instances of FinfString, otherwise will return even escaped instances | |
**/ | |
CHAR16 * | |
FindNextInstance ( | |
IN CONST CHAR16 *SourceString, | |
IN CONST CHAR16 *FindString, | |
IN CONST BOOLEAN CheckForEscapeCharacter | |
) | |
{ | |
CHAR16 *Temp; | |
if (SourceString == NULL) { | |
return (NULL); | |
} | |
Temp = StrStr (SourceString, FindString); | |
// | |
// If nothing found, or we don't care about escape characters | |
// | |
if ((Temp == NULL) || !CheckForEscapeCharacter) { | |
return (Temp); | |
} | |
// | |
// If we found an escaped character, try again on the remainder of the string | |
// | |
if ((Temp > (SourceString)) && (*(Temp-1) == L'^')) { | |
return FindNextInstance (Temp+1, FindString, CheckForEscapeCharacter); | |
} | |
// | |
// we found the right character | |
// | |
return (Temp); | |
} | |
/** | |
Check whether the string between a pair of % is a valid environment variable name. | |
@param[in] BeginPercent pointer to the first percent. | |
@param[in] EndPercent pointer to the last percent. | |
@retval TRUE is a valid environment variable name. | |
@retval FALSE is NOT a valid environment variable name. | |
**/ | |
BOOLEAN | |
IsValidEnvironmentVariableName ( | |
IN CONST CHAR16 *BeginPercent, | |
IN CONST CHAR16 *EndPercent | |
) | |
{ | |
CONST CHAR16 *Walker; | |
Walker = NULL; | |
ASSERT (BeginPercent != NULL); | |
ASSERT (EndPercent != NULL); | |
ASSERT (BeginPercent < EndPercent); | |
if ((BeginPercent + 1) == EndPercent) { | |
return FALSE; | |
} | |
for (Walker = BeginPercent + 1; Walker < EndPercent; Walker++) { | |
if ( | |
((*Walker >= L'0') && (*Walker <= L'9')) || | |
((*Walker >= L'A') && (*Walker <= L'Z')) || | |
((*Walker >= L'a') && (*Walker <= L'z')) || | |
(*Walker == L'_') | |
) | |
{ | |
if ((Walker == BeginPercent + 1) && ((*Walker >= L'0') && (*Walker <= L'9'))) { | |
return FALSE; | |
} else { | |
continue; | |
} | |
} else { | |
return FALSE; | |
} | |
} | |
return TRUE; | |
} | |
/** | |
Determine if a command line contains a split operation | |
@param[in] CmdLine The command line to parse. | |
@retval TRUE CmdLine has a valid split. | |
@retval FALSE CmdLine does not have a valid split. | |
**/ | |
BOOLEAN | |
ContainsSplit ( | |
IN CONST CHAR16 *CmdLine | |
) | |
{ | |
CONST CHAR16 *TempSpot; | |
CONST CHAR16 *FirstQuote; | |
CONST CHAR16 *SecondQuote; | |
FirstQuote = FindNextInstance (CmdLine, L"\"", TRUE); | |
SecondQuote = NULL; | |
TempSpot = FindFirstCharacter (CmdLine, L"|", L'^'); | |
if ((FirstQuote == NULL) || | |
(TempSpot == NULL) || | |
(TempSpot == CHAR_NULL) || | |
(FirstQuote > TempSpot) | |
) | |
{ | |
return (BOOLEAN)((TempSpot != NULL) && (*TempSpot != CHAR_NULL)); | |
} | |
while ((TempSpot != NULL) && (*TempSpot != CHAR_NULL)) { | |
if ((FirstQuote == NULL) || (FirstQuote > TempSpot)) { | |
break; | |
} | |
SecondQuote = FindNextInstance (FirstQuote + 1, L"\"", TRUE); | |
if (SecondQuote == NULL) { | |
break; | |
} | |
if (SecondQuote < TempSpot) { | |
FirstQuote = FindNextInstance (SecondQuote + 1, L"\"", TRUE); | |
continue; | |
} else { | |
FirstQuote = FindNextInstance (SecondQuote + 1, L"\"", TRUE); | |
TempSpot = FindFirstCharacter (TempSpot + 1, L"|", L'^'); | |
continue; | |
} | |
} | |
return (BOOLEAN)((TempSpot != NULL) && (*TempSpot != CHAR_NULL)); | |
} | |
/** | |
Function to start monitoring for CTRL-S using SimpleTextInputEx. This | |
feature's enabled state was not known when the shell initially launched. | |
@retval EFI_SUCCESS The feature is enabled. | |
@retval EFI_OUT_OF_RESOURCES There is not enough memory available. | |
**/ | |
EFI_STATUS | |
InternalEfiShellStartCtrlSMonitor ( | |
VOID | |
) | |
{ | |
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleEx; | |
EFI_KEY_DATA KeyData; | |
EFI_STATUS Status; | |
Status = gBS->OpenProtocol ( | |
gST->ConsoleInHandle, | |
&gEfiSimpleTextInputExProtocolGuid, | |
(VOID **)&SimpleEx, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_SHELL_NO_IN_EX), | |
ShellInfoObject.HiiHandle | |
); | |
return (EFI_SUCCESS); | |
} | |
KeyData.KeyState.KeyToggleState = 0; | |
KeyData.Key.ScanCode = 0; | |
KeyData.KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID|EFI_LEFT_CONTROL_PRESSED; | |
KeyData.Key.UnicodeChar = L's'; | |
Status = SimpleEx->RegisterKeyNotify ( | |
SimpleEx, | |
&KeyData, | |
NotificationFunction, | |
&ShellInfoObject.CtrlSNotifyHandle1 | |
); | |
KeyData.KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID|EFI_RIGHT_CONTROL_PRESSED; | |
if (!EFI_ERROR (Status)) { | |
Status = SimpleEx->RegisterKeyNotify ( | |
SimpleEx, | |
&KeyData, | |
NotificationFunction, | |
&ShellInfoObject.CtrlSNotifyHandle2 | |
); | |
} | |
KeyData.KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID|EFI_LEFT_CONTROL_PRESSED; | |
KeyData.Key.UnicodeChar = 19; | |
if (!EFI_ERROR (Status)) { | |
Status = SimpleEx->RegisterKeyNotify ( | |
SimpleEx, | |
&KeyData, | |
NotificationFunction, | |
&ShellInfoObject.CtrlSNotifyHandle3 | |
); | |
} | |
KeyData.KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID|EFI_RIGHT_CONTROL_PRESSED; | |
if (!EFI_ERROR (Status)) { | |
Status = SimpleEx->RegisterKeyNotify ( | |
SimpleEx, | |
&KeyData, | |
NotificationFunction, | |
&ShellInfoObject.CtrlSNotifyHandle4 | |
); | |
} | |
return (Status); | |
} | |
/** | |
The entry point for the application. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval EFI_SUCCESS The entry point is executed successfully. | |
@retval other Some error occurs when executing this entry point. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
UefiMain ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
CHAR16 *TempString; | |
UINTN Size; | |
EFI_HANDLE ConInHandle; | |
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *OldConIn; | |
SPLIT_LIST *Split; | |
if (PcdGet8 (PcdShellSupportLevel) > 3) { | |
return (EFI_UNSUPPORTED); | |
} | |
// | |
// Clear the screen | |
// | |
Status = gST->ConOut->ClearScreen (gST->ConOut); | |
if (EFI_ERROR (Status)) { | |
return (Status); | |
} | |
// | |
// Populate the global structure from PCDs | |
// | |
ShellInfoObject.ImageDevPath = NULL; | |
ShellInfoObject.FileDevPath = NULL; | |
ShellInfoObject.PageBreakEnabled = PcdGetBool (PcdShellPageBreakDefault); | |
ShellInfoObject.ViewingSettings.InsertMode = PcdGetBool (PcdShellInsertModeDefault); | |
ShellInfoObject.LogScreenCount = PcdGet8 (PcdShellScreenLogCount); | |
// | |
// verify we dont allow for spec violation | |
// | |
ASSERT (ShellInfoObject.LogScreenCount >= 3); | |
// | |
// Initialize the LIST ENTRY objects... | |
// | |
InitializeListHead (&ShellInfoObject.BufferToFreeList.Link); | |
InitializeListHead (&ShellInfoObject.ViewingSettings.CommandHistory.Link); | |
InitializeListHead (&ShellInfoObject.SplitList.Link); | |
// | |
// Check PCDs for optional features that are not implemented yet. | |
// | |
if ( PcdGetBool (PcdShellSupportOldProtocols) | |
|| !FeaturePcdGet (PcdShellRequireHiiPlatform) | |
|| FeaturePcdGet (PcdShellSupportFrameworkHii) | |
) | |
{ | |
return (EFI_UNSUPPORTED); | |
} | |
// | |
// turn off the watchdog timer | |
// | |
gBS->SetWatchdogTimer (0, 0, 0, NULL); | |
// | |
// install our console logger. This will keep a log of the output for back-browsing | |
// | |
Status = ConsoleLoggerInstall (ShellInfoObject.LogScreenCount, &ShellInfoObject.ConsoleInfo); | |
if (!EFI_ERROR (Status)) { | |
// | |
// Enable the cursor to be visible | |
// | |
gST->ConOut->EnableCursor (gST->ConOut, TRUE); | |
// | |
// If supporting EFI 1.1 we need to install HII protocol | |
// only do this if PcdShellRequireHiiPlatform == FALSE | |
// | |
// remove EFI_UNSUPPORTED check above when complete. | |
/// @todo add support for Framework HII | |
// | |
// install our (solitary) HII package | |
// | |
ShellInfoObject.HiiHandle = HiiAddPackages (&gEfiCallerIdGuid, gImageHandle, ShellStrings, NULL); | |
if (ShellInfoObject.HiiHandle == NULL) { | |
if (PcdGetBool (PcdShellSupportFrameworkHii)) { | |
/// @todo Add our package into Framework HII | |
} | |
if (ShellInfoObject.HiiHandle == NULL) { | |
Status = EFI_NOT_STARTED; | |
goto FreeResources; | |
} | |
} | |
// | |
// create and install the EfiShellParametersProtocol | |
// | |
Status = CreatePopulateInstallShellParametersProtocol (&ShellInfoObject.NewShellParametersProtocol, &ShellInfoObject.RootShellInstance); | |
ASSERT_EFI_ERROR (Status); | |
ASSERT (ShellInfoObject.NewShellParametersProtocol != NULL); | |
// | |
// create and install the EfiShellProtocol | |
// | |
Status = CreatePopulateInstallShellProtocol (&ShellInfoObject.NewEfiShellProtocol); | |
ASSERT_EFI_ERROR (Status); | |
ASSERT (ShellInfoObject.NewEfiShellProtocol != NULL); | |
// | |
// Now initialize the shell library (it requires Shell Parameters protocol) | |
// | |
Status = ShellInitialize (); | |
ASSERT_EFI_ERROR (Status); | |
Status = CommandInit (); | |
ASSERT_EFI_ERROR (Status); | |
Status = ShellInitEnvVarList (); | |
// | |
// Check the command line | |
// | |
Status = ProcessCommandLine (); | |
if (EFI_ERROR (Status)) { | |
goto FreeResources; | |
} | |
// | |
// If shell support level is >= 1 create the mappings and paths | |
// | |
if (PcdGet8 (PcdShellSupportLevel) >= 1) { | |
Status = ShellCommandCreateInitialMappingsAndPaths (); | |
} | |
// | |
// Set the environment variable for nesting support | |
// | |
Size = 0; | |
TempString = NULL; | |
if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoNest) { | |
// | |
// No change. require nesting in Shell Protocol Execute() | |
// | |
StrnCatGrow ( | |
&TempString, | |
&Size, | |
L"False", | |
0 | |
); | |
} else { | |
StrnCatGrow ( | |
&TempString, | |
&Size, | |
mNoNestingTrue, | |
0 | |
); | |
} | |
Status = InternalEfiShellSetEnv (mNoNestingEnvVarName, TempString, TRUE); | |
SHELL_FREE_NON_NULL (TempString); | |
Size = 0; | |
// | |
// save the device path for the loaded image and the device path for the filepath (under loaded image) | |
// These are where to look for the startup.nsh file | |
// | |
Status = GetDevicePathsForImageAndFile (&ShellInfoObject.ImageDevPath, &ShellInfoObject.FileDevPath); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Display the version | |
// | |
if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion) { | |
ShellPrintHiiEx ( | |
0, | |
gST->ConOut->Mode->CursorRow, | |
NULL, | |
STRING_TOKEN (STR_VER_OUTPUT_MAIN_SHELL), | |
ShellInfoObject.HiiHandle, | |
SupportLevel[PcdGet8 (PcdShellSupportLevel)], | |
gEfiShellProtocol->MajorVersion, | |
gEfiShellProtocol->MinorVersion | |
); | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_VER_OUTPUT_MAIN_SUPPLIER), | |
ShellInfoObject.HiiHandle, | |
(CHAR16 *)PcdGetPtr (PcdShellSupplier) | |
); | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_VER_OUTPUT_MAIN_UEFI), | |
ShellInfoObject.HiiHandle, | |
(gST->Hdr.Revision&0xffff0000)>>16, | |
(gST->Hdr.Revision&0x0000ffff), | |
gST->FirmwareVendor, | |
gST->FirmwareRevision | |
); | |
} | |
// | |
// Display the mapping | |
// | |
if ((PcdGet8 (PcdShellSupportLevel) >= 2) && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap) { | |
Status = RunCommand (L"map"); | |
ASSERT_EFI_ERROR (Status); | |
} | |
// | |
// init all the built in alias' | |
// | |
Status = SetBuiltInAlias (); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Initialize environment variables | |
// | |
if (ShellCommandGetProfileList () != NULL) { | |
Status = InternalEfiShellSetEnv (L"profiles", ShellCommandGetProfileList (), TRUE); | |
ASSERT_EFI_ERROR (Status); | |
} | |
Size = 100; | |
TempString = AllocateZeroPool (Size); | |
UnicodeSPrint (TempString, Size, L"%d", PcdGet8 (PcdShellSupportLevel)); | |
Status = InternalEfiShellSetEnv (L"uefishellsupport", TempString, TRUE); | |
ASSERT_EFI_ERROR (Status); | |
UnicodeSPrint (TempString, Size, L"%d.%d", ShellInfoObject.NewEfiShellProtocol->MajorVersion, ShellInfoObject.NewEfiShellProtocol->MinorVersion); | |
Status = InternalEfiShellSetEnv (L"uefishellversion", TempString, TRUE); | |
ASSERT_EFI_ERROR (Status); | |
UnicodeSPrint (TempString, Size, L"%d.%d", (gST->Hdr.Revision & 0xFFFF0000) >> 16, gST->Hdr.Revision & 0x0000FFFF); | |
Status = InternalEfiShellSetEnv (L"uefiversion", TempString, TRUE); | |
ASSERT_EFI_ERROR (Status); | |
FreePool (TempString); | |
if (!EFI_ERROR (Status)) { | |
if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt) { | |
// | |
// Set up the event for CTRL-C monitoring... | |
// | |
Status = InernalEfiShellStartMonitor (); | |
} | |
if (!EFI_ERROR (Status) && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) { | |
// | |
// Set up the event for CTRL-S monitoring... | |
// | |
Status = InternalEfiShellStartCtrlSMonitor (); | |
} | |
if (!EFI_ERROR (Status) && ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) { | |
// | |
// close off the gST->ConIn | |
// | |
OldConIn = gST->ConIn; | |
ConInHandle = gST->ConsoleInHandle; | |
gST->ConIn = CreateSimpleTextInOnFile ((SHELL_FILE_HANDLE)&FileInterfaceNulFile, &gST->ConsoleInHandle); | |
} else { | |
OldConIn = NULL; | |
ConInHandle = NULL; | |
} | |
if (!EFI_ERROR (Status) && (PcdGet8 (PcdShellSupportLevel) >= 1)) { | |
// | |
// process the startup script or launch the called app. | |
// | |
Status = DoStartupScript (ShellInfoObject.ImageDevPath, ShellInfoObject.FileDevPath); | |
} | |
if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit && !ShellCommandGetExit () && ((PcdGet8 (PcdShellSupportLevel) >= 3) || PcdGetBool (PcdShellForceConsole)) && !EFI_ERROR (Status) && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) { | |
// | |
// begin the UI waiting loop | |
// | |
do { | |
// | |
// clean out all the memory allocated for CONST <something> * return values | |
// between each shell prompt presentation | |
// | |
if (!IsListEmpty (&ShellInfoObject.BufferToFreeList.Link)) { | |
FreeBufferList (&ShellInfoObject.BufferToFreeList); | |
} | |
// | |
// Reset page break back to default. | |
// | |
ShellInfoObject.PageBreakEnabled = PcdGetBool (PcdShellPageBreakDefault); | |
ASSERT (ShellInfoObject.ConsoleInfo != NULL); | |
ShellInfoObject.ConsoleInfo->Enabled = TRUE; | |
ShellInfoObject.ConsoleInfo->RowCounter = 0; | |
// | |
// Display Prompt | |
// | |
Status = DoShellPrompt (); | |
} while (!ShellCommandGetExit ()); | |
} | |
if ((OldConIn != NULL) && (ConInHandle != NULL)) { | |
CloseSimpleTextInOnFile (gST->ConIn); | |
gST->ConIn = OldConIn; | |
gST->ConsoleInHandle = ConInHandle; | |
} | |
} | |
} | |
FreeResources: | |
// | |
// uninstall protocols / free memory / etc... | |
// | |
if (ShellInfoObject.UserBreakTimer != NULL) { | |
gBS->CloseEvent (ShellInfoObject.UserBreakTimer); | |
DEBUG_CODE ( | |
ShellInfoObject.UserBreakTimer = NULL; | |
); | |
} | |
if (ShellInfoObject.ImageDevPath != NULL) { | |
FreePool (ShellInfoObject.ImageDevPath); | |
DEBUG_CODE ( | |
ShellInfoObject.ImageDevPath = NULL; | |
); | |
} | |
if (ShellInfoObject.FileDevPath != NULL) { | |
FreePool (ShellInfoObject.FileDevPath); | |
DEBUG_CODE ( | |
ShellInfoObject.FileDevPath = NULL; | |
); | |
} | |
if (ShellInfoObject.NewShellParametersProtocol != NULL) { | |
CleanUpShellParametersProtocol (ShellInfoObject.NewShellParametersProtocol); | |
DEBUG_CODE ( | |
ShellInfoObject.NewShellParametersProtocol = NULL; | |
); | |
} | |
if (ShellInfoObject.NewEfiShellProtocol != NULL) { | |
if (ShellInfoObject.NewEfiShellProtocol->IsRootShell ()) { | |
InternalEfiShellSetEnv (L"cwd", NULL, TRUE); | |
} | |
CleanUpShellEnvironment (ShellInfoObject.NewEfiShellProtocol); | |
DEBUG_CODE ( | |
ShellInfoObject.NewEfiShellProtocol = NULL; | |
); | |
} | |
if (!IsListEmpty (&ShellInfoObject.BufferToFreeList.Link)) { | |
FreeBufferList (&ShellInfoObject.BufferToFreeList); | |
} | |
if (!IsListEmpty (&ShellInfoObject.SplitList.Link)) { | |
ASSERT (FALSE); /// @todo finish this de-allocation (free SplitStdIn/Out when needed). | |
for ( Split = (SPLIT_LIST *)GetFirstNode (&ShellInfoObject.SplitList.Link) | |
; !IsNull (&ShellInfoObject.SplitList.Link, &Split->Link) | |
; Split = (SPLIT_LIST *)GetNextNode (&ShellInfoObject.SplitList.Link, &Split->Link) | |
) | |
{ | |
RemoveEntryList (&Split->Link); | |
FreePool (Split); | |
} | |
DEBUG_CODE ( | |
InitializeListHead (&ShellInfoObject.SplitList.Link); | |
); | |
} | |
if (ShellInfoObject.ShellInitSettings.FileName != NULL) { | |
FreePool (ShellInfoObject.ShellInitSettings.FileName); | |
DEBUG_CODE ( | |
ShellInfoObject.ShellInitSettings.FileName = NULL; | |
); | |
} | |
if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) { | |
FreePool (ShellInfoObject.ShellInitSettings.FileOptions); | |
DEBUG_CODE ( | |
ShellInfoObject.ShellInitSettings.FileOptions = NULL; | |
); | |
} | |
if (ShellInfoObject.HiiHandle != NULL) { | |
HiiRemovePackages (ShellInfoObject.HiiHandle); | |
DEBUG_CODE ( | |
ShellInfoObject.HiiHandle = NULL; | |
); | |
} | |
if (!IsListEmpty (&ShellInfoObject.ViewingSettings.CommandHistory.Link)) { | |
FreeBufferList (&ShellInfoObject.ViewingSettings.CommandHistory); | |
} | |
ASSERT (ShellInfoObject.ConsoleInfo != NULL); | |
if (ShellInfoObject.ConsoleInfo != NULL) { | |
ConsoleLoggerUninstall (ShellInfoObject.ConsoleInfo); | |
FreePool (ShellInfoObject.ConsoleInfo); | |
DEBUG_CODE ( | |
ShellInfoObject.ConsoleInfo = NULL; | |
); | |
} | |
ShellFreeEnvVarList (); | |
if (ShellCommandGetExit ()) { | |
return ((EFI_STATUS)ShellCommandGetExitCode ()); | |
} | |
return (Status); | |
} | |
/** | |
Sets all the alias' that were registered with the ShellCommandLib library. | |
@retval EFI_SUCCESS all init commands were run successfully. | |
**/ | |
EFI_STATUS | |
SetBuiltInAlias ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
CONST ALIAS_LIST *List; | |
ALIAS_LIST *Node; | |
// | |
// Get all the commands we want to run | |
// | |
List = ShellCommandGetInitAliasList (); | |
// | |
// for each command in the List | |
// | |
for ( Node = (ALIAS_LIST *)GetFirstNode (&List->Link) | |
; !IsNull (&List->Link, &Node->Link) | |
; Node = (ALIAS_LIST *)GetNextNode (&List->Link, &Node->Link) | |
) | |
{ | |
// | |
// install the alias' | |
// | |
Status = InternalSetAlias (Node->CommandString, Node->Alias, TRUE); | |
ASSERT_EFI_ERROR (Status); | |
} | |
return (EFI_SUCCESS); | |
} | |
/** | |
Internal function to determine if 2 command names are really the same. | |
@param[in] Command1 The pointer to the first command name. | |
@param[in] Command2 The pointer to the second command name. | |
@retval TRUE The 2 command names are the same. | |
@retval FALSE The 2 command names are not the same. | |
**/ | |
BOOLEAN | |
IsCommand ( | |
IN CONST CHAR16 *Command1, | |
IN CONST CHAR16 *Command2 | |
) | |
{ | |
if (StringNoCaseCompare (&Command1, &Command2) == 0) { | |
return (TRUE); | |
} | |
return (FALSE); | |
} | |
/** | |
Internal function to determine if a command is a script only command. | |
@param[in] CommandName The pointer to the command name. | |
@retval TRUE The command is a script only command. | |
@retval FALSE The command is not a script only command. | |
**/ | |
BOOLEAN | |
IsScriptOnlyCommand ( | |
IN CONST CHAR16 *CommandName | |
) | |
{ | |
if ( IsCommand (CommandName, L"for") | |
|| IsCommand (CommandName, L"endfor") | |
|| IsCommand (CommandName, L"if") | |
|| IsCommand (CommandName, L"else") | |
|| IsCommand (CommandName, L"endif") | |
|| IsCommand (CommandName, L"goto")) | |
{ | |
return (TRUE); | |
} | |
return (FALSE); | |
} | |
/** | |
This function will populate the 2 device path protocol parameters based on the | |
global gImageHandle. The DevPath will point to the device path for the handle that has | |
loaded image protocol installed on it. The FilePath will point to the device path | |
for the file that was loaded. | |
@param[in, out] DevPath On a successful return the device path to the loaded image. | |
@param[in, out] FilePath On a successful return the device path to the file. | |
@retval EFI_SUCCESS The 2 device paths were successfully returned. | |
@retval other A error from gBS->HandleProtocol. | |
@sa HandleProtocol | |
**/ | |
EFI_STATUS | |
GetDevicePathsForImageAndFile ( | |
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevPath, | |
IN OUT EFI_DEVICE_PATH_PROTOCOL **FilePath | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; | |
EFI_DEVICE_PATH_PROTOCOL *ImageDevicePath; | |
ASSERT (DevPath != NULL); | |
ASSERT (FilePath != NULL); | |
Status = gBS->OpenProtocol ( | |
gImageHandle, | |
&gEfiLoadedImageProtocolGuid, | |
(VOID **)&LoadedImage, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (!EFI_ERROR (Status)) { | |
Status = gBS->OpenProtocol ( | |
LoadedImage->DeviceHandle, | |
&gEfiDevicePathProtocolGuid, | |
(VOID **)&ImageDevicePath, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (!EFI_ERROR (Status)) { | |
*DevPath = DuplicateDevicePath (ImageDevicePath); | |
*FilePath = DuplicateDevicePath (LoadedImage->FilePath); | |
gBS->CloseProtocol ( | |
LoadedImage->DeviceHandle, | |
&gEfiDevicePathProtocolGuid, | |
gImageHandle, | |
NULL | |
); | |
} | |
gBS->CloseProtocol ( | |
gImageHandle, | |
&gEfiLoadedImageProtocolGuid, | |
gImageHandle, | |
NULL | |
); | |
} | |
return (Status); | |
} | |
/** | |
Process all Uefi Shell 2.0 command line options. | |
see Uefi Shell 2.0 section 3.2 for full details. | |
the command line must resemble the following: | |
shell.efi [ShellOpt-options] [options] [file-name [file-name-options]] | |
ShellOpt-options Options which control the initialization behavior of the shell. | |
These options are read from the EFI global variable "ShellOpt" | |
and are processed before options or file-name. | |
options Options which control the initialization behavior of the shell. | |
file-name The name of a UEFI shell application or script to be executed | |
after initialization is complete. By default, if file-name is | |
specified, then -nostartup is implied. Scripts are not supported | |
by level 0. | |
file-name-options The command-line options that are passed to file-name when it | |
is invoked. | |
This will initialize the ShellInfoObject.ShellInitSettings global variable. | |
@retval EFI_SUCCESS The variable is initialized. | |
**/ | |
EFI_STATUS | |
ProcessCommandLine ( | |
VOID | |
) | |
{ | |
UINTN Size; | |
UINTN LoopVar; | |
CHAR16 *CurrentArg; | |
CHAR16 *DelayValueStr; | |
UINT64 DelayValue; | |
EFI_STATUS Status; | |
EFI_UNICODE_COLLATION_PROTOCOL *UnicodeCollation; | |
// `file-name-options` will contain arguments to `file-name` that we don't | |
// know about. This would cause ShellCommandLineParse to error, so we parse | |
// arguments manually, ignoring those after the first thing that doesn't look | |
// like a shell option (which is assumed to be `file-name`). | |
Status = gBS->LocateProtocol ( | |
&gEfiUnicodeCollation2ProtocolGuid, | |
NULL, | |
(VOID **)&UnicodeCollation | |
); | |
if (EFI_ERROR (Status)) { | |
Status = gBS->LocateProtocol ( | |
&gEfiUnicodeCollationProtocolGuid, | |
NULL, | |
(VOID **)&UnicodeCollation | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
// Set default options | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleOut = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit = FALSE; | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoNest = FALSE; | |
ShellInfoObject.ShellInitSettings.Delay = PcdGet32 (PcdShellDefaultDelay); | |
// | |
// Start LoopVar at 0 to parse only optional arguments at Argv[0] | |
// and parse other parameters from Argv[1]. This is for use case that | |
// UEFI Shell boot option is created, and OptionalData is provided | |
// that starts with shell command-line options. | |
// | |
for (LoopVar = 0; LoopVar < gEfiShellParametersProtocol->Argc; LoopVar++) { | |
CurrentArg = gEfiShellParametersProtocol->Argv[LoopVar]; | |
if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-startup", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-nostartup", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-noconsoleout", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleOut = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-noconsolein", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-nointerrupt", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-nomap", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-noversion", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-nonest", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoNest = TRUE; | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-delay", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay = TRUE; | |
// Check for optional delay value following "-delay" | |
if ((LoopVar + 1) >= gEfiShellParametersProtocol->Argc) { | |
DelayValueStr = NULL; | |
} else { | |
DelayValueStr = gEfiShellParametersProtocol->Argv[LoopVar + 1]; | |
} | |
if (DelayValueStr != NULL) { | |
if (*DelayValueStr == L':') { | |
DelayValueStr++; | |
} | |
if (!EFI_ERROR ( | |
ShellConvertStringToUint64 ( | |
DelayValueStr, | |
&DelayValue, | |
FALSE, | |
FALSE | |
) | |
)) | |
{ | |
ShellInfoObject.ShellInitSettings.Delay = (UINTN)DelayValue; | |
LoopVar++; | |
} | |
} | |
} else if (UnicodeCollation->StriColl ( | |
UnicodeCollation, | |
L"-exit", | |
CurrentArg | |
) == 0) | |
{ | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit = TRUE; | |
} else if (StrnCmp (L"-", CurrentArg, 1) == 0) { | |
// Unrecognized option | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_PROBLEM), | |
ShellInfoObject.HiiHandle, | |
CurrentArg | |
); | |
return EFI_INVALID_PARAMETER; | |
} else { | |
// | |
// First argument should be Shell.efi image name | |
// | |
if (LoopVar == 0) { | |
continue; | |
} | |
ShellInfoObject.ShellInitSettings.FileName = NULL; | |
Size = 0; | |
// | |
// If first argument contains a space, then add double quotes before the argument | |
// | |
if (StrStr (CurrentArg, L" ") != NULL) { | |
StrnCatGrow (&ShellInfoObject.ShellInitSettings.FileName, &Size, L"\"", 0); | |
if (ShellInfoObject.ShellInitSettings.FileName == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} | |
StrnCatGrow (&ShellInfoObject.ShellInitSettings.FileName, &Size, CurrentArg, 0); | |
if (ShellInfoObject.ShellInitSettings.FileName == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
// | |
// If first argument contains a space, then add double quotes after the argument | |
// | |
if (StrStr (CurrentArg, L" ") != NULL) { | |
StrnCatGrow (&ShellInfoObject.ShellInitSettings.FileName, &Size, L"\"", 0); | |
if (ShellInfoObject.ShellInitSettings.FileName == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} | |
// | |
// We found `file-name`. | |
// | |
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = 1; | |
LoopVar++; | |
// Add `file-name-options` | |
for (Size = 0; LoopVar < gEfiShellParametersProtocol->Argc; LoopVar++) { | |
ASSERT ((ShellInfoObject.ShellInitSettings.FileOptions == NULL && Size == 0) || (ShellInfoObject.ShellInitSettings.FileOptions != NULL)); | |
// | |
// Add a space between arguments | |
// | |
if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) { | |
StrnCatGrow (&ShellInfoObject.ShellInitSettings.FileOptions, &Size, L" ", 0); | |
if (ShellInfoObject.ShellInitSettings.FileOptions == NULL) { | |
SHELL_FREE_NON_NULL (ShellInfoObject.ShellInitSettings.FileName); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} | |
// | |
// If an argument contains a space, then add double quotes before the argument | |
// | |
if (StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L" ") != NULL) { | |
StrnCatGrow ( | |
&ShellInfoObject.ShellInitSettings.FileOptions, | |
&Size, | |
L"\"", | |
0 | |
); | |
if (ShellInfoObject.ShellInitSettings.FileOptions == NULL) { | |
SHELL_FREE_NON_NULL (ShellInfoObject.ShellInitSettings.FileName); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} | |
StrnCatGrow ( | |
&ShellInfoObject.ShellInitSettings.FileOptions, | |
&Size, | |
gEfiShellParametersProtocol->Argv[LoopVar], | |
0 | |
); | |
if (ShellInfoObject.ShellInitSettings.FileOptions == NULL) { | |
SHELL_FREE_NON_NULL (ShellInfoObject.ShellInitSettings.FileName); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
// | |
// If an argument contains a space, then add double quotes after the argument | |
// | |
if (StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L" ") != NULL) { | |
StrnCatGrow ( | |
&ShellInfoObject.ShellInitSettings.FileOptions, | |
&Size, | |
L"\"", | |
0 | |
); | |
if (ShellInfoObject.ShellInitSettings.FileOptions == NULL) { | |
SHELL_FREE_NON_NULL (ShellInfoObject.ShellInitSettings.FileName); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} | |
} | |
} | |
} | |
// "-nointerrupt" overrides "-delay" | |
if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt) { | |
ShellInfoObject.ShellInitSettings.Delay = 0; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Function try to find location of the Startup.nsh file. | |
The buffer is callee allocated and should be freed by the caller. | |
@param ImageDevicePath The path to the image for shell. first place to look for the startup script | |
@param FileDevicePath The path to the file for shell. second place to look for the startup script. | |
@retval NULL No Startup.nsh file was found. | |
@return !=NULL Pointer to NULL-terminated path. | |
**/ | |
CHAR16 * | |
LocateStartupScript ( | |
IN EFI_DEVICE_PATH_PROTOCOL *ImageDevicePath, | |
IN EFI_DEVICE_PATH_PROTOCOL *FileDevicePath | |
) | |
{ | |
CHAR16 *StartupScriptPath; | |
CHAR16 *TempSpot; | |
CONST CHAR16 *MapName; | |
UINTN Size; | |
StartupScriptPath = NULL; | |
Size = 0; | |
// | |
// Try to find 'Startup.nsh' in the directory where the shell itself was launched. | |
// | |
MapName = ShellInfoObject.NewEfiShellProtocol->GetMapFromDevicePath (&ImageDevicePath); | |
if (MapName != NULL) { | |
StartupScriptPath = StrnCatGrow (&StartupScriptPath, &Size, MapName, 0); | |
if (StartupScriptPath == NULL) { | |
// | |
// Do not locate the startup script in sys path when out of resource. | |
// | |
return NULL; | |
} | |
TempSpot = StrStr (StartupScriptPath, L";"); | |
if (TempSpot != NULL) { | |
*TempSpot = CHAR_NULL; | |
} | |
InternalEfiShellSetEnv (L"homefilesystem", StartupScriptPath, TRUE); | |
StartupScriptPath = StrnCatGrow (&StartupScriptPath, &Size, ((FILEPATH_DEVICE_PATH *)FileDevicePath)->PathName, 0); | |
PathRemoveLastItem (StartupScriptPath); | |
StartupScriptPath = StrnCatGrow (&StartupScriptPath, &Size, mStartupScript, 0); | |
} | |
// | |
// Try to find 'Startup.nsh' in the execution path defined by the environment variable PATH. | |
// | |
if ((StartupScriptPath == NULL) || EFI_ERROR (ShellIsFile (StartupScriptPath))) { | |
SHELL_FREE_NON_NULL (StartupScriptPath); | |
StartupScriptPath = ShellFindFilePath (mStartupScript); | |
} | |
return StartupScriptPath; | |
} | |
/** | |
Handles all interaction with the default startup script. | |
this will check that the correct command line parameters were passed, handle the delay, and then start running the script. | |
@param ImagePath the path to the image for shell. first place to look for the startup script | |
@param FilePath the path to the file for shell. second place to look for the startup script. | |
@retval EFI_SUCCESS the variable is initialized. | |
**/ | |
EFI_STATUS | |
DoStartupScript ( | |
IN EFI_DEVICE_PATH_PROTOCOL *ImagePath, | |
IN EFI_DEVICE_PATH_PROTOCOL *FilePath | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_STATUS CalleeStatus; | |
UINTN Delay; | |
EFI_INPUT_KEY Key; | |
CHAR16 *FileStringPath; | |
CHAR16 *FullFileStringPath; | |
UINTN NewSize; | |
CalleeStatus = EFI_SUCCESS; | |
Key.UnicodeChar = CHAR_NULL; | |
Key.ScanCode = 0; | |
if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup && (ShellInfoObject.ShellInitSettings.FileName != NULL)) { | |
// | |
// launch something else instead | |
// | |
NewSize = StrSize (ShellInfoObject.ShellInitSettings.FileName); | |
if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) { | |
NewSize += StrSize (ShellInfoObject.ShellInitSettings.FileOptions) + sizeof (CHAR16); | |
} | |
FileStringPath = AllocateZeroPool (NewSize); | |
if (FileStringPath == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
StrCpyS (FileStringPath, NewSize/sizeof (CHAR16), ShellInfoObject.ShellInitSettings.FileName); | |
if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) { | |
StrnCatS (FileStringPath, NewSize/sizeof (CHAR16), L" ", NewSize/sizeof (CHAR16) - StrLen (FileStringPath) -1); | |
StrnCatS (FileStringPath, NewSize/sizeof (CHAR16), ShellInfoObject.ShellInitSettings.FileOptions, NewSize/sizeof (CHAR16) - StrLen (FileStringPath) -1); | |
} | |
Status = RunShellCommand (FileStringPath, &CalleeStatus); | |
if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit == TRUE) { | |
ShellCommandRegisterExit (gEfiShellProtocol->BatchIsActive (), (UINT64)CalleeStatus); | |
} | |
FreePool (FileStringPath); | |
return (Status); | |
} | |
// | |
// for shell level 0 we do no scripts | |
// Without the Startup bit overriding we allow for nostartup to prevent scripts | |
// | |
if ( (PcdGet8 (PcdShellSupportLevel) < 1) | |
|| (ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup) | |
) | |
{ | |
return (EFI_SUCCESS); | |
} | |
gST->ConOut->EnableCursor (gST->ConOut, FALSE); | |
// | |
// print out our warning and see if they press a key | |
// | |
for ( Status = EFI_UNSUPPORTED, Delay = ShellInfoObject.ShellInitSettings.Delay | |
; Delay != 0 && EFI_ERROR (Status) | |
; Delay-- | |
) | |
{ | |
ShellPrintHiiEx (0, gST->ConOut->Mode->CursorRow, NULL, STRING_TOKEN (STR_SHELL_STARTUP_QUESTION), ShellInfoObject.HiiHandle, Delay); | |
gBS->Stall (1000000); | |
if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) { | |
Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key); | |
} | |
} | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_CRLF), ShellInfoObject.HiiHandle); | |
gST->ConOut->EnableCursor (gST->ConOut, TRUE); | |
// | |
// ESC was pressed | |
// | |
if ((Status == EFI_SUCCESS) && (Key.UnicodeChar == 0) && (Key.ScanCode == SCAN_ESC)) { | |
return (EFI_SUCCESS); | |
} | |
FileStringPath = LocateStartupScript (ImagePath, FilePath); | |
if (FileStringPath != NULL) { | |
FullFileStringPath = FullyQualifyPath (FileStringPath); | |
if (FullFileStringPath == NULL) { | |
Status = RunScriptFile (FileStringPath, NULL, FileStringPath, ShellInfoObject.NewShellParametersProtocol); | |
} else { | |
Status = RunScriptFile (FullFileStringPath, NULL, FullFileStringPath, ShellInfoObject.NewShellParametersProtocol); | |
FreePool (FullFileStringPath); | |
} | |
FreePool (FileStringPath); | |
} else { | |
// | |
// we return success since startup script is not mandatory. | |
// | |
Status = EFI_SUCCESS; | |
} | |
return (Status); | |
} | |
/** | |
Function to perform the shell prompt looping. It will do a single prompt, | |
dispatch the result, and then return. It is expected that the caller will | |
call this function in a loop many times. | |
@retval EFI_SUCCESS | |
@retval RETURN_ABORTED | |
**/ | |
EFI_STATUS | |
DoShellPrompt ( | |
VOID | |
) | |
{ | |
UINTN Column; | |
UINTN Row; | |
CHAR16 *CmdLine; | |
CONST CHAR16 *CurDir; | |
UINTN BufferSize; | |
EFI_STATUS Status; | |
LIST_ENTRY OldBufferList; | |
CurDir = NULL; | |
// | |
// Get screen setting to decide size of the command line buffer | |
// | |
gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &Column, &Row); | |
BufferSize = Column * Row * sizeof (CHAR16); | |
CmdLine = AllocateZeroPool (BufferSize); | |
if (CmdLine == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
SaveBufferList (&OldBufferList); | |
CurDir = ShellInfoObject.NewEfiShellProtocol->GetEnv (L"cwd"); | |
// | |
// Prompt for input | |
// | |
gST->ConOut->SetCursorPosition (gST->ConOut, 0, gST->ConOut->Mode->CursorRow); | |
if ((CurDir != NULL) && (StrLen (CurDir) > 1)) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_CURDIR), ShellInfoObject.HiiHandle, CurDir); | |
} else { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_SHELL), ShellInfoObject.HiiHandle); | |
} | |
// | |
// Read a line from the console | |
// | |
Status = ShellInfoObject.NewEfiShellProtocol->ReadFile (ShellInfoObject.NewShellParametersProtocol->StdIn, &BufferSize, CmdLine); | |
// | |
// Null terminate the string and parse it | |
// | |
if (!EFI_ERROR (Status)) { | |
// | |
// Reset the CTRL-C event just before running the command (yes we ignore the return values) | |
// | |
Status = gBS->CheckEvent (ShellInfoObject.NewEfiShellProtocol->ExecutionBreak); | |
CmdLine[BufferSize / sizeof (CHAR16)] = CHAR_NULL; | |
Status = RunCommand (CmdLine); | |
} | |
// | |
// Done with this command | |
// | |
RestoreBufferList (&OldBufferList); | |
FreePool (CmdLine); | |
return Status; | |
} | |
/** | |
Add a buffer to the Buffer To Free List for safely returning buffers to other | |
places without risking letting them modify internal shell information. | |
@param Buffer Something to pass to FreePool when the shell is exiting. | |
**/ | |
VOID * | |
AddBufferToFreeList ( | |
VOID *Buffer | |
) | |
{ | |
BUFFER_LIST *BufferListEntry; | |
if (Buffer == NULL) { | |
return (NULL); | |
} | |
BufferListEntry = AllocateZeroPool (sizeof (BUFFER_LIST)); | |
if (BufferListEntry == NULL) { | |
return NULL; | |
} | |
BufferListEntry->Buffer = Buffer; | |
InsertTailList (&ShellInfoObject.BufferToFreeList.Link, &BufferListEntry->Link); | |
return (Buffer); | |
} | |
/** | |
Create a new buffer list and stores the old one to OldBufferList | |
@param OldBufferList The temporary list head used to store the nodes in BufferToFreeList. | |
**/ | |
VOID | |
SaveBufferList ( | |
OUT LIST_ENTRY *OldBufferList | |
) | |
{ | |
CopyMem (OldBufferList, &ShellInfoObject.BufferToFreeList.Link, sizeof (LIST_ENTRY)); | |
InitializeListHead (&ShellInfoObject.BufferToFreeList.Link); | |
} | |
/** | |
Restore previous nodes into BufferToFreeList . | |
@param OldBufferList The temporary list head used to store the nodes in BufferToFreeList. | |
**/ | |
VOID | |
RestoreBufferList ( | |
IN OUT LIST_ENTRY *OldBufferList | |
) | |
{ | |
FreeBufferList (&ShellInfoObject.BufferToFreeList); | |
CopyMem (&ShellInfoObject.BufferToFreeList.Link, OldBufferList, sizeof (LIST_ENTRY)); | |
} | |
/** | |
Add a buffer to the Line History List | |
@param Buffer The line buffer to add. | |
**/ | |
VOID | |
AddLineToCommandHistory ( | |
IN CONST CHAR16 *Buffer | |
) | |
{ | |
BUFFER_LIST *Node; | |
BUFFER_LIST *Walker; | |
UINT16 MaxHistoryCmdCount; | |
UINT16 Count; | |
Count = 0; | |
MaxHistoryCmdCount = PcdGet16 (PcdShellMaxHistoryCommandCount); | |
if (MaxHistoryCmdCount == 0) { | |
return; | |
} | |
Node = AllocateZeroPool (sizeof (BUFFER_LIST)); | |
if (Node == NULL) { | |
return; | |
} | |
Node->Buffer = AllocateCopyPool (StrSize (Buffer), Buffer); | |
if (Node->Buffer == NULL) { | |
FreePool (Node); | |
return; | |
} | |
for ( Walker = (BUFFER_LIST *)GetFirstNode (&ShellInfoObject.ViewingSettings.CommandHistory.Link) | |
; !IsNull (&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Walker->Link) | |
; Walker = (BUFFER_LIST *)GetNextNode (&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Walker->Link) | |
) | |
{ | |
Count++; | |
} | |
if (Count < MaxHistoryCmdCount) { | |
InsertTailList (&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Node->Link); | |
} else { | |
Walker = (BUFFER_LIST *)GetFirstNode (&ShellInfoObject.ViewingSettings.CommandHistory.Link); | |
RemoveEntryList (&Walker->Link); | |
if (Walker->Buffer != NULL) { | |
FreePool (Walker->Buffer); | |
} | |
FreePool (Walker); | |
InsertTailList (&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Node->Link); | |
} | |
} | |
/** | |
Checks if a string is an alias for another command. If yes, then it replaces the alias name | |
with the correct command name. | |
@param[in, out] CommandString Upon entry the potential alias. Upon return the | |
command name if it was an alias. If it was not | |
an alias it will be unchanged. This function may | |
change the buffer to fit the command name. | |
@retval EFI_SUCCESS The name was changed. | |
@retval EFI_SUCCESS The name was not an alias. | |
@retval EFI_OUT_OF_RESOURCES A memory allocation failed. | |
**/ | |
EFI_STATUS | |
ShellConvertAlias ( | |
IN OUT CHAR16 **CommandString | |
) | |
{ | |
CONST CHAR16 *NewString; | |
NewString = ShellInfoObject.NewEfiShellProtocol->GetAlias (*CommandString, NULL); | |
if (NewString == NULL) { | |
return (EFI_SUCCESS); | |
} | |
FreePool (*CommandString); | |
*CommandString = AllocateCopyPool (StrSize (NewString), NewString); | |
if (*CommandString == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
return (EFI_SUCCESS); | |
} | |
/** | |
This function will eliminate unreplaced (and therefore non-found) environment variables. | |
@param[in,out] CmdLine The command line to update. | |
**/ | |
EFI_STATUS | |
StripUnreplacedEnvironmentVariables ( | |
IN OUT CHAR16 *CmdLine | |
) | |
{ | |
CHAR16 *FirstPercent; | |
CHAR16 *FirstQuote; | |
CHAR16 *SecondPercent; | |
CHAR16 *SecondQuote; | |
CHAR16 *CurrentLocator; | |
for (CurrentLocator = CmdLine; CurrentLocator != NULL; ) { | |
FirstQuote = FindNextInstance (CurrentLocator, L"\"", TRUE); | |
FirstPercent = FindNextInstance (CurrentLocator, L"%", TRUE); | |
SecondPercent = FirstPercent != NULL ? FindNextInstance (FirstPercent+1, L"%", TRUE) : NULL; | |
if ((FirstPercent == NULL) || (SecondPercent == NULL)) { | |
// | |
// If we ever don't have 2 % we are done. | |
// | |
break; | |
} | |
if ((FirstQuote != NULL) && (FirstQuote < FirstPercent)) { | |
SecondQuote = FindNextInstance (FirstQuote+1, L"\"", TRUE); | |
// | |
// Quote is first found | |
// | |
if (SecondQuote < FirstPercent) { | |
// | |
// restart after the pair of " | |
// | |
CurrentLocator = SecondQuote + 1; | |
} else { | |
/* FirstPercent < SecondQuote */ | |
// | |
// Restart on the first percent | |
// | |
CurrentLocator = FirstPercent; | |
} | |
continue; | |
} | |
if ((FirstQuote == NULL) || (SecondPercent < FirstQuote)) { | |
if (IsValidEnvironmentVariableName (FirstPercent, SecondPercent)) { | |
// | |
// We need to remove from FirstPercent to SecondPercent | |
// | |
CopyMem (FirstPercent, SecondPercent + 1, StrSize (SecondPercent + 1)); | |
// | |
// don't need to update the locator. both % characters are gone. | |
// | |
} else { | |
CurrentLocator = SecondPercent + 1; | |
} | |
continue; | |
} | |
CurrentLocator = FirstQuote; | |
} | |
return (EFI_SUCCESS); | |
} | |
/** | |
Function allocates a new command line and replaces all instances of environment | |
variable names that are correctly preset to their values. | |
If the return value is not NULL the memory must be caller freed. | |
@param[in] OriginalCommandLine The original command line | |
@retval NULL An error occurred. | |
@return The new command line with no environment variables present. | |
**/ | |
CHAR16 * | |
ShellConvertVariables ( | |
IN CONST CHAR16 *OriginalCommandLine | |
) | |
{ | |
CONST CHAR16 *MasterEnvList; | |
UINTN NewSize; | |
CHAR16 *NewCommandLine1; | |
CHAR16 *NewCommandLine2; | |
CHAR16 *Temp; | |
UINTN ItemSize; | |
CHAR16 *ItemTemp; | |
SCRIPT_FILE *CurrentScriptFile; | |
ALIAS_LIST *AliasListNode; | |
ASSERT (OriginalCommandLine != NULL); | |
ItemSize = 0; | |
NewSize = StrSize (OriginalCommandLine); | |
CurrentScriptFile = ShellCommandGetCurrentScriptFile (); | |
Temp = NULL; | |
/// @todo update this to handle the %0 - %9 for scripting only (borrow from line 1256 area) ? ? ? | |
// | |
// calculate the size required for the post-conversion string... | |
// | |
if (CurrentScriptFile != NULL) { | |
for (AliasListNode = (ALIAS_LIST *)GetFirstNode (&CurrentScriptFile->SubstList) | |
; !IsNull (&CurrentScriptFile->SubstList, &AliasListNode->Link) | |
; AliasListNode = (ALIAS_LIST *)GetNextNode (&CurrentScriptFile->SubstList, &AliasListNode->Link) | |
) | |
{ | |
for (Temp = StrStr (OriginalCommandLine, AliasListNode->Alias) | |
; Temp != NULL | |
; Temp = StrStr (Temp+1, AliasListNode->Alias) | |
) | |
{ | |
// | |
// we need a preceding and if there is space no ^ preceding (if no space ignore) | |
// | |
if ((((Temp-OriginalCommandLine) > 2) && (*(Temp-2) != L'^')) || ((Temp-OriginalCommandLine) <= 2)) { | |
NewSize += StrSize (AliasListNode->CommandString); | |
} | |
} | |
} | |
} | |
for (MasterEnvList = EfiShellGetEnv (NULL) | |
; MasterEnvList != NULL && *MasterEnvList != CHAR_NULL // && *(MasterEnvList+1) != CHAR_NULL | |
; MasterEnvList += StrLen (MasterEnvList) + 1 | |
) | |
{ | |
if (StrSize (MasterEnvList) > ItemSize) { | |
ItemSize = StrSize (MasterEnvList); | |
} | |
for (Temp = StrStr (OriginalCommandLine, MasterEnvList) | |
; Temp != NULL | |
; Temp = StrStr (Temp+1, MasterEnvList) | |
) | |
{ | |
// | |
// we need a preceding and following % and if there is space no ^ preceding (if no space ignore) | |
// | |
if ((*(Temp-1) == L'%') && (*(Temp+StrLen (MasterEnvList)) == L'%') && | |
((((Temp-OriginalCommandLine) > 2) && (*(Temp-2) != L'^')) || ((Temp-OriginalCommandLine) <= 2))) | |
{ | |
NewSize += StrSize (EfiShellGetEnv (MasterEnvList)); | |
} | |
} | |
} | |
// | |
// now do the replacements... | |
// | |
NewCommandLine1 = AllocateZeroPool (NewSize); | |
NewCommandLine2 = AllocateZeroPool (NewSize); | |
ItemTemp = AllocateZeroPool (ItemSize+(2*sizeof (CHAR16))); | |
if ((NewCommandLine1 == NULL) || (NewCommandLine2 == NULL) || (ItemTemp == NULL)) { | |
SHELL_FREE_NON_NULL (NewCommandLine1); | |
SHELL_FREE_NON_NULL (NewCommandLine2); | |
SHELL_FREE_NON_NULL (ItemTemp); | |
return (NULL); | |
} | |
CopyMem (NewCommandLine1, OriginalCommandLine, StrSize (OriginalCommandLine)); | |
for (MasterEnvList = EfiShellGetEnv (NULL) | |
; MasterEnvList != NULL && *MasterEnvList != CHAR_NULL | |
; MasterEnvList += StrLen (MasterEnvList) + 1 | |
) | |
{ | |
StrCpyS ( | |
ItemTemp, | |
((ItemSize+(2*sizeof (CHAR16)))/sizeof (CHAR16)), | |
L"%" | |
); | |
StrCatS ( | |
ItemTemp, | |
((ItemSize+(2*sizeof (CHAR16)))/sizeof (CHAR16)), | |
MasterEnvList | |
); | |
StrCatS ( | |
ItemTemp, | |
((ItemSize+(2*sizeof (CHAR16)))/sizeof (CHAR16)), | |
L"%" | |
); | |
ShellCopySearchAndReplace (NewCommandLine1, NewCommandLine2, NewSize, ItemTemp, EfiShellGetEnv (MasterEnvList), TRUE, FALSE); | |
StrCpyS (NewCommandLine1, NewSize/sizeof (CHAR16), NewCommandLine2); | |
} | |
if (CurrentScriptFile != NULL) { | |
for (AliasListNode = (ALIAS_LIST *)GetFirstNode (&CurrentScriptFile->SubstList) | |
; !IsNull (&CurrentScriptFile->SubstList, &AliasListNode->Link) | |
; AliasListNode = (ALIAS_LIST *)GetNextNode (&CurrentScriptFile->SubstList, &AliasListNode->Link) | |
) | |
{ | |
ShellCopySearchAndReplace (NewCommandLine1, NewCommandLine2, NewSize, AliasListNode->Alias, AliasListNode->CommandString, TRUE, FALSE); | |
StrCpyS (NewCommandLine1, NewSize/sizeof (CHAR16), NewCommandLine2); | |
} | |
} | |
// | |
// Remove non-existent environment variables | |
// | |
StripUnreplacedEnvironmentVariables (NewCommandLine1); | |
// | |
// Now cleanup any straggler intentionally ignored "%" characters | |
// | |
ShellCopySearchAndReplace (NewCommandLine1, NewCommandLine2, NewSize, L"^%", L"%", TRUE, FALSE); | |
StrCpyS (NewCommandLine1, NewSize/sizeof (CHAR16), NewCommandLine2); | |
FreePool (NewCommandLine2); | |
FreePool (ItemTemp); | |
return (NewCommandLine1); | |
} | |
/** | |
Internal function to run a command line with pipe usage. | |
@param[in] CmdLine The pointer to the command line. | |
@param[in] StdIn The pointer to the Standard input. | |
@param[in] StdOut The pointer to the Standard output. | |
@retval EFI_SUCCESS The split command is executed successfully. | |
@retval other Some error occurs when executing the split command. | |
**/ | |
EFI_STATUS | |
RunSplitCommand ( | |
IN CONST CHAR16 *CmdLine, | |
IN SHELL_FILE_HANDLE StdIn, | |
IN SHELL_FILE_HANDLE StdOut | |
) | |
{ | |
EFI_STATUS Status; | |
CHAR16 *NextCommandLine; | |
CHAR16 *OurCommandLine; | |
UINTN Size1; | |
UINTN Size2; | |
SPLIT_LIST *Split; | |
SHELL_FILE_HANDLE TempFileHandle; | |
BOOLEAN Unicode; | |
ASSERT (StdOut == NULL); | |
ASSERT (StrStr (CmdLine, L"|") != NULL); | |
Status = EFI_SUCCESS; | |
NextCommandLine = NULL; | |
OurCommandLine = NULL; | |
Size1 = 0; | |
Size2 = 0; | |
NextCommandLine = StrnCatGrow (&NextCommandLine, &Size1, StrStr (CmdLine, L"|")+1, 0); | |
OurCommandLine = StrnCatGrow (&OurCommandLine, &Size2, CmdLine, StrStr (CmdLine, L"|") - CmdLine); | |
if ((NextCommandLine == NULL) || (OurCommandLine == NULL)) { | |
SHELL_FREE_NON_NULL (OurCommandLine); | |
SHELL_FREE_NON_NULL (NextCommandLine); | |
return (EFI_OUT_OF_RESOURCES); | |
} else if ((StrStr (OurCommandLine, L"|") != NULL) || (Size1 == 0) || (Size2 == 0)) { | |
SHELL_FREE_NON_NULL (OurCommandLine); | |
SHELL_FREE_NON_NULL (NextCommandLine); | |
return (EFI_INVALID_PARAMETER); | |
} else if ((NextCommandLine[0] == L'a') && | |
((NextCommandLine[1] == L' ') || (NextCommandLine[1] == CHAR_NULL)) | |
) | |
{ | |
CopyMem (NextCommandLine, NextCommandLine+1, StrSize (NextCommandLine) - sizeof (NextCommandLine[0])); | |
while (NextCommandLine[0] == L' ') { | |
CopyMem (NextCommandLine, NextCommandLine+1, StrSize (NextCommandLine) - sizeof (NextCommandLine[0])); | |
} | |
if (NextCommandLine[0] == CHAR_NULL) { | |
SHELL_FREE_NON_NULL (OurCommandLine); | |
SHELL_FREE_NON_NULL (NextCommandLine); | |
return (EFI_INVALID_PARAMETER); | |
} | |
Unicode = FALSE; | |
} else { | |
Unicode = TRUE; | |
} | |
// | |
// make a SPLIT_LIST item and add to list | |
// | |
Split = AllocateZeroPool (sizeof (SPLIT_LIST)); | |
if (Split == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Split->SplitStdIn = StdIn; | |
Split->SplitStdOut = ConvertEfiFileProtocolToShellHandle (CreateFileInterfaceMem (Unicode), NULL); | |
ASSERT (Split->SplitStdOut != NULL); | |
InsertHeadList (&ShellInfoObject.SplitList.Link, &Split->Link); | |
Status = RunCommand (OurCommandLine); | |
// | |
// move the output from the first to the in to the second. | |
// | |
TempFileHandle = Split->SplitStdOut; | |
if (Split->SplitStdIn == StdIn) { | |
Split->SplitStdOut = NULL; | |
} else { | |
Split->SplitStdOut = Split->SplitStdIn; | |
} | |
Split->SplitStdIn = TempFileHandle; | |
ShellInfoObject.NewEfiShellProtocol->SetFilePosition (Split->SplitStdIn, 0); | |
if (!EFI_ERROR (Status)) { | |
Status = RunCommand (NextCommandLine); | |
} | |
// | |
// remove the top level from the ScriptList | |
// | |
ASSERT ((SPLIT_LIST *)GetFirstNode (&ShellInfoObject.SplitList.Link) == Split); | |
RemoveEntryList (&Split->Link); | |
// | |
// Note that the original StdIn is now the StdOut... | |
// | |
if (Split->SplitStdOut != NULL) { | |
ShellInfoObject.NewEfiShellProtocol->CloseFile (Split->SplitStdOut); | |
} | |
if (Split->SplitStdIn != NULL) { | |
ShellInfoObject.NewEfiShellProtocol->CloseFile (Split->SplitStdIn); | |
} | |
FreePool (Split); | |
FreePool (NextCommandLine); | |
FreePool (OurCommandLine); | |
return (Status); | |
} | |
/** | |
Take the original command line, substitute any variables, free | |
the original string, return the modified copy. | |
@param[in] CmdLine pointer to the command line to update. | |
@retval EFI_SUCCESS the function was successful. | |
@retval EFI_OUT_OF_RESOURCES a memory allocation failed. | |
**/ | |
EFI_STATUS | |
ShellSubstituteVariables ( | |
IN CHAR16 **CmdLine | |
) | |
{ | |
CHAR16 *NewCmdLine; | |
NewCmdLine = ShellConvertVariables (*CmdLine); | |
SHELL_FREE_NON_NULL (*CmdLine); | |
if (NewCmdLine == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
*CmdLine = NewCmdLine; | |
return (EFI_SUCCESS); | |
} | |
/** | |
Take the original command line, substitute any alias in the first group of space delimited characters, free | |
the original string, return the modified copy. | |
@param[in] CmdLine pointer to the command line to update. | |
@retval EFI_SUCCESS the function was successful. | |
@retval EFI_OUT_OF_RESOURCES a memory allocation failed. | |
**/ | |
EFI_STATUS | |
ShellSubstituteAliases ( | |
IN CHAR16 **CmdLine | |
) | |
{ | |
CHAR16 *NewCmdLine; | |
CHAR16 *CommandName; | |
EFI_STATUS Status; | |
UINTN PostAliasSize; | |
ASSERT (CmdLine != NULL); | |
ASSERT (*CmdLine != NULL); | |
CommandName = NULL; | |
if (StrStr ((*CmdLine), L" ") == NULL) { | |
StrnCatGrow (&CommandName, NULL, (*CmdLine), 0); | |
} else { | |
StrnCatGrow (&CommandName, NULL, (*CmdLine), StrStr ((*CmdLine), L" ") - (*CmdLine)); | |
} | |
// | |
// This cannot happen 'inline' since the CmdLine can need extra space. | |
// | |
NewCmdLine = NULL; | |
if (!ShellCommandIsCommandOnList (CommandName)) { | |
// | |
// Convert via alias | |
// | |
Status = ShellConvertAlias (&CommandName); | |
if (EFI_ERROR (Status)) { | |
return (Status); | |
} | |
PostAliasSize = 0; | |
NewCmdLine = StrnCatGrow (&NewCmdLine, &PostAliasSize, CommandName, 0); | |
if (NewCmdLine == NULL) { | |
SHELL_FREE_NON_NULL (CommandName); | |
SHELL_FREE_NON_NULL (*CmdLine); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
NewCmdLine = StrnCatGrow (&NewCmdLine, &PostAliasSize, StrStr ((*CmdLine), L" "), 0); | |
if (NewCmdLine == NULL) { | |
SHELL_FREE_NON_NULL (CommandName); | |
SHELL_FREE_NON_NULL (*CmdLine); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} else { | |
NewCmdLine = StrnCatGrow (&NewCmdLine, NULL, (*CmdLine), 0); | |
} | |
SHELL_FREE_NON_NULL (*CmdLine); | |
SHELL_FREE_NON_NULL (CommandName); | |
// | |
// re-assign the passed in double pointer to point to our newly allocated buffer | |
// | |
*CmdLine = NewCmdLine; | |
return (EFI_SUCCESS); | |
} | |
/** | |
Takes the Argv[0] part of the command line and determine the meaning of it. | |
@param[in] CmdName pointer to the command line to update. | |
@retval Internal_Command The name is an internal command. | |
@retval File_Sys_Change the name is a file system change. | |
@retval Script_File_Name the name is a NSH script file. | |
@retval Unknown_Invalid the name is unknown. | |
@retval Efi_Application the name is an application (.EFI). | |
**/ | |
SHELL_OPERATION_TYPES | |
GetOperationType ( | |
IN CONST CHAR16 *CmdName | |
) | |
{ | |
CHAR16 *FileWithPath; | |
CONST CHAR16 *TempLocation; | |
CONST CHAR16 *TempLocation2; | |
FileWithPath = NULL; | |
// | |
// test for an internal command. | |
// | |
if (ShellCommandIsCommandOnList (CmdName)) { | |
return (Internal_Command); | |
} | |
// | |
// Test for file system change request. anything ending with first : and cant have spaces. | |
// | |
if (CmdName[(StrLen (CmdName)-1)] == L':') { | |
if ( (StrStr (CmdName, L" ") != NULL) | |
|| (StrLen (StrStr (CmdName, L":")) > 1) | |
) | |
{ | |
return (Unknown_Invalid); | |
} | |
return (File_Sys_Change); | |
} | |
// | |
// Test for a file | |
// | |
if ((FileWithPath = ShellFindFilePathEx (CmdName, mExecutableExtensions)) != NULL) { | |
// | |
// See if that file has a script file extension | |
// | |
if (StrLen (FileWithPath) > 4) { | |
TempLocation = FileWithPath+StrLen (FileWithPath)-4; | |
TempLocation2 = mScriptExtension; | |
if (StringNoCaseCompare ((VOID *)(&TempLocation), (VOID *)(&TempLocation2)) == 0) { | |
SHELL_FREE_NON_NULL (FileWithPath); | |
return (Script_File_Name); | |
} | |
} | |
// | |
// Was a file, but not a script. we treat this as an application. | |
// | |
SHELL_FREE_NON_NULL (FileWithPath); | |
return (Efi_Application); | |
} | |
SHELL_FREE_NON_NULL (FileWithPath); | |
// | |
// No clue what this is... return invalid flag... | |
// | |
return (Unknown_Invalid); | |
} | |
/** | |
Determine if the first item in a command line is valid. | |
@param[in] CmdLine The command line to parse. | |
@retval EFI_SUCCESS The item is valid. | |
@retval EFI_OUT_OF_RESOURCES A memory allocation failed. | |
@retval EFI_NOT_FOUND The operation type is unknown or invalid. | |
**/ | |
EFI_STATUS | |
IsValidSplit ( | |
IN CONST CHAR16 *CmdLine | |
) | |
{ | |
CHAR16 *Temp; | |
CHAR16 *FirstParameter; | |
CHAR16 *TempWalker; | |
EFI_STATUS Status; | |
Temp = NULL; | |
Temp = StrnCatGrow (&Temp, NULL, CmdLine, 0); | |
if (Temp == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
FirstParameter = StrStr (Temp, L"|"); | |
if (FirstParameter != NULL) { | |
*FirstParameter = CHAR_NULL; | |
} | |
FirstParameter = NULL; | |
// | |
// Process the command line | |
// | |
Status = ProcessCommandLineToFinal (&Temp); | |
if (!EFI_ERROR (Status)) { | |
FirstParameter = AllocateZeroPool (StrSize (CmdLine)); | |
if (FirstParameter == NULL) { | |
SHELL_FREE_NON_NULL (Temp); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
TempWalker = (CHAR16 *)Temp; | |
if (!EFI_ERROR (GetNextParameter (&TempWalker, &FirstParameter, StrSize (CmdLine), TRUE))) { | |
if (GetOperationType (FirstParameter) == Unknown_Invalid) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); | |
SetLastError (SHELL_NOT_FOUND); | |
Status = EFI_NOT_FOUND; | |
} | |
} | |
} | |
SHELL_FREE_NON_NULL (Temp); | |
SHELL_FREE_NON_NULL (FirstParameter); | |
return Status; | |
} | |
/** | |
Determine if a command line contains with a split contains only valid commands. | |
@param[in] CmdLine The command line to parse. | |
@retval EFI_SUCCESS CmdLine has only valid commands, application, or has no split. | |
@retval EFI_ABORTED CmdLine has at least one invalid command or application. | |
**/ | |
EFI_STATUS | |
VerifySplit ( | |
IN CONST CHAR16 *CmdLine | |
) | |
{ | |
CONST CHAR16 *TempSpot; | |
EFI_STATUS Status; | |
// | |
// If this was the only item, then get out | |
// | |
if (!ContainsSplit (CmdLine)) { | |
return (EFI_SUCCESS); | |
} | |
// | |
// Verify up to the pipe or end character | |
// | |
Status = IsValidSplit (CmdLine); | |
if (EFI_ERROR (Status)) { | |
return (Status); | |
} | |
// | |
// recurse to verify the next item | |
// | |
TempSpot = FindFirstCharacter (CmdLine, L"|", L'^') + 1; | |
if ((*TempSpot == L'a') && | |
((*(TempSpot + 1) == L' ') || (*(TempSpot + 1) == CHAR_NULL)) | |
) | |
{ | |
// If it's an ASCII pipe '|a' | |
TempSpot += 1; | |
} | |
return (VerifySplit (TempSpot)); | |
} | |
/** | |
Process a split based operation. | |
@param[in] CmdLine pointer to the command line to process | |
@retval EFI_SUCCESS The operation was successful | |
@return an error occurred. | |
**/ | |
EFI_STATUS | |
ProcessNewSplitCommandLine ( | |
IN CONST CHAR16 *CmdLine | |
) | |
{ | |
SPLIT_LIST *Split; | |
EFI_STATUS Status; | |
Status = VerifySplit (CmdLine); | |
if (EFI_ERROR (Status)) { | |
return (Status); | |
} | |
Split = NULL; | |
// | |
// are we in an existing split??? | |
// | |
if (!IsListEmpty (&ShellInfoObject.SplitList.Link)) { | |
Split = (SPLIT_LIST *)GetFirstNode (&ShellInfoObject.SplitList.Link); | |
} | |
if (Split == NULL) { | |
Status = RunSplitCommand (CmdLine, NULL, NULL); | |
} else { | |
Status = RunSplitCommand (CmdLine, Split->SplitStdIn, Split->SplitStdOut); | |
} | |
if (EFI_ERROR (Status)) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_SPLIT), ShellInfoObject.HiiHandle, CmdLine); | |
} | |
return (Status); | |
} | |
/** | |
Handle a request to change the current file system. | |
@param[in] CmdLine The passed in command line. | |
@retval EFI_SUCCESS The operation was successful. | |
**/ | |
EFI_STATUS | |
ChangeMappedDrive ( | |
IN CONST CHAR16 *CmdLine | |
) | |
{ | |
EFI_STATUS Status; | |
Status = EFI_SUCCESS; | |
// | |
// make sure we are the right operation | |
// | |
ASSERT (CmdLine[(StrLen (CmdLine)-1)] == L':' && StrStr (CmdLine, L" ") == NULL); | |
// | |
// Call the protocol API to do the work | |
// | |
Status = ShellInfoObject.NewEfiShellProtocol->SetCurDir (NULL, CmdLine); | |
// | |
// Report any errors | |
// | |
if (EFI_ERROR (Status)) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_MAPPING), ShellInfoObject.HiiHandle, CmdLine); | |
} | |
return (Status); | |
} | |
/** | |
Reprocess the command line to direct all -? to the help command. | |
if found, will add "help" as argv[0], and move the rest later. | |
@param[in,out] CmdLine pointer to the command line to update | |
**/ | |
EFI_STATUS | |
DoHelpUpdate ( | |
IN OUT CHAR16 **CmdLine | |
) | |
{ | |
CHAR16 *CurrentParameter; | |
CHAR16 *Walker; | |
CHAR16 *NewCommandLine; | |
EFI_STATUS Status; | |
UINTN NewCmdLineSize; | |
Status = EFI_SUCCESS; | |
CurrentParameter = AllocateZeroPool (StrSize (*CmdLine)); | |
if (CurrentParameter == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
Walker = *CmdLine; | |
while (Walker != NULL && *Walker != CHAR_NULL) { | |
if (!EFI_ERROR (GetNextParameter (&Walker, &CurrentParameter, StrSize (*CmdLine), TRUE))) { | |
if (StrStr (CurrentParameter, L"-?") == CurrentParameter) { | |
CurrentParameter[0] = L' '; | |
CurrentParameter[1] = L' '; | |
NewCmdLineSize = StrSize (L"help ") + StrSize (*CmdLine); | |
NewCommandLine = AllocateZeroPool (NewCmdLineSize); | |
if (NewCommandLine == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
break; | |
} | |
// | |
// We know the space is sufficient since we just calculated it. | |
// | |
StrnCpyS (NewCommandLine, NewCmdLineSize/sizeof (CHAR16), L"help ", 5); | |
StrnCatS (NewCommandLine, NewCmdLineSize/sizeof (CHAR16), *CmdLine, StrLen (*CmdLine)); | |
SHELL_FREE_NON_NULL (*CmdLine); | |
*CmdLine = NewCommandLine; | |
break; | |
} | |
} | |
} | |
SHELL_FREE_NON_NULL (CurrentParameter); | |
return (Status); | |
} | |
/** | |
Function to update the shell variable "lasterror". | |
@param[in] ErrorCode the error code to put into lasterror. | |
**/ | |
EFI_STATUS | |
SetLastError ( | |
IN CONST SHELL_STATUS ErrorCode | |
) | |
{ | |
CHAR16 LeString[19]; | |
if (sizeof (EFI_STATUS) == sizeof (UINT64)) { | |
UnicodeSPrint (LeString, sizeof (LeString), L"0x%Lx", ErrorCode); | |
} else { | |
UnicodeSPrint (LeString, sizeof (LeString), L"0x%x", ErrorCode); | |
} | |
DEBUG_CODE ( | |
InternalEfiShellSetEnv (L"debuglasterror", LeString, TRUE); | |
); | |
InternalEfiShellSetEnv (L"lasterror", LeString, TRUE); | |
return (EFI_SUCCESS); | |
} | |
/** | |
Converts the command line to its post-processed form. this replaces variables and alias' per UEFI Shell spec. | |
@param[in,out] CmdLine pointer to the command line to update | |
@retval EFI_SUCCESS The operation was successful | |
@retval EFI_OUT_OF_RESOURCES A memory allocation failed. | |
@return some other error occurred | |
**/ | |
EFI_STATUS | |
ProcessCommandLineToFinal ( | |
IN OUT CHAR16 **CmdLine | |
) | |
{ | |
EFI_STATUS Status; | |
TrimSpaces (CmdLine); | |
Status = ShellSubstituteAliases (CmdLine); | |
if (EFI_ERROR (Status)) { | |
return (Status); | |
} | |
TrimSpaces (CmdLine); | |
Status = ShellSubstituteVariables (CmdLine); | |
if (EFI_ERROR (Status)) { | |
return (Status); | |
} | |
ASSERT (*CmdLine != NULL); | |
TrimSpaces (CmdLine); | |
// | |
// update for help parsing | |
// | |
if (StrStr (*CmdLine, L"?") != NULL) { | |
// | |
// This may do nothing if the ? does not indicate help. | |
// Save all the details for in the API below. | |
// | |
Status = DoHelpUpdate (CmdLine); | |
} | |
TrimSpaces (CmdLine); | |
return (EFI_SUCCESS); | |
} | |
/** | |
Run an internal shell command. | |
This API will update the shell's environment since these commands are libraries. | |
@param[in] CmdLine the command line to run. | |
@param[in] FirstParameter the first parameter on the command line | |
@param[in] ParamProtocol the shell parameters protocol pointer | |
@param[out] CommandStatus the status from the command line. | |
@retval EFI_SUCCESS The command was completed. | |
@retval EFI_ABORTED The command's operation was aborted. | |
**/ | |
EFI_STATUS | |
RunInternalCommand ( | |
IN CONST CHAR16 *CmdLine, | |
IN CHAR16 *FirstParameter, | |
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol, | |
OUT EFI_STATUS *CommandStatus | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Argc; | |
CHAR16 **Argv; | |
SHELL_STATUS CommandReturnedStatus; | |
BOOLEAN LastError; | |
CHAR16 *Walker; | |
CHAR16 *NewCmdLine; | |
NewCmdLine = AllocateCopyPool (StrSize (CmdLine), CmdLine); | |
if (NewCmdLine == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (Walker = NewCmdLine; Walker != NULL && *Walker != CHAR_NULL; Walker++) { | |
if ((*Walker == L'^') && (*(Walker+1) == L'#')) { | |
CopyMem (Walker, Walker+1, StrSize (Walker) - sizeof (Walker[0])); | |
} | |
} | |
// | |
// get the argc and argv updated for internal commands | |
// | |
Status = UpdateArgcArgv (ParamProtocol, NewCmdLine, Internal_Command, &Argv, &Argc); | |
if (!EFI_ERROR (Status)) { | |
// | |
// Run the internal command. | |
// | |
Status = ShellCommandRunCommandHandler (FirstParameter, &CommandReturnedStatus, &LastError); | |
if (!EFI_ERROR (Status)) { | |
if (CommandStatus != NULL) { | |
if (CommandReturnedStatus != SHELL_SUCCESS) { | |
*CommandStatus = (EFI_STATUS)(CommandReturnedStatus | MAX_BIT); | |
} else { | |
*CommandStatus = EFI_SUCCESS; | |
} | |
} | |
// | |
// Update last error status. | |
// some commands do not update last error. | |
// | |
if (LastError) { | |
SetLastError (CommandReturnedStatus); | |
} | |
// | |
// Pass thru the exitcode from the app. | |
// | |
if (ShellCommandGetExit ()) { | |
// | |
// An Exit was requested ("exit" command), pass its value up. | |
// | |
Status = CommandReturnedStatus; | |
} else if ((CommandReturnedStatus != SHELL_SUCCESS) && IsScriptOnlyCommand (FirstParameter)) { | |
// | |
// Always abort when a script only command fails for any reason | |
// | |
Status = EFI_ABORTED; | |
} else if ((ShellCommandGetCurrentScriptFile () != NULL) && (CommandReturnedStatus == SHELL_ABORTED)) { | |
// | |
// Abort when in a script and a command aborted | |
// | |
Status = EFI_ABORTED; | |
} | |
} | |
} | |
// | |
// This is guaranteed to be called after UpdateArgcArgv no matter what else happened. | |
// This is safe even if the update API failed. In this case, it may be a no-op. | |
// | |
RestoreArgcArgv (ParamProtocol, &Argv, &Argc); | |
// | |
// If a script is running and the command is not a script only command, then | |
// change return value to success so the script won't halt (unless aborted). | |
// | |
// Script only commands have to be able halt the script since the script will | |
// not operate if they are failing. | |
// | |
if ( (ShellCommandGetCurrentScriptFile () != NULL) | |
&& !IsScriptOnlyCommand (FirstParameter) | |
&& (Status != EFI_ABORTED) | |
) | |
{ | |
Status = EFI_SUCCESS; | |
} | |
FreePool (NewCmdLine); | |
return (Status); | |
} | |
/** | |
Function to run the command or file. | |
@param[in] Type the type of operation being run. | |
@param[in] CmdLine the command line to run. | |
@param[in] FirstParameter the first parameter on the command line | |
@param[in] ParamProtocol the shell parameters protocol pointer | |
@param[out] CommandStatus the status from the command line. | |
@retval EFI_SUCCESS The command was completed. | |
@retval EFI_ABORTED The command's operation was aborted. | |
**/ | |
EFI_STATUS | |
RunCommandOrFile ( | |
IN SHELL_OPERATION_TYPES Type, | |
IN CONST CHAR16 *CmdLine, | |
IN CHAR16 *FirstParameter, | |
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol, | |
OUT EFI_STATUS *CommandStatus | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_STATUS StartStatus; | |
CHAR16 *CommandWithPath; | |
CHAR16 *FullCommandWithPath; | |
EFI_DEVICE_PATH_PROTOCOL *DevPath; | |
SHELL_STATUS CalleeExitStatus; | |
Status = EFI_SUCCESS; | |
CommandWithPath = NULL; | |
DevPath = NULL; | |
CalleeExitStatus = SHELL_INVALID_PARAMETER; | |
switch (Type) { | |
case Internal_Command: | |
Status = RunInternalCommand (CmdLine, FirstParameter, ParamProtocol, CommandStatus); | |
break; | |
case Script_File_Name: | |
case Efi_Application: | |
// | |
// Process a fully qualified path | |
// | |
if (StrStr (FirstParameter, L":") != NULL) { | |
ASSERT (CommandWithPath == NULL); | |
if (ShellIsFile (FirstParameter) == EFI_SUCCESS) { | |
CommandWithPath = StrnCatGrow (&CommandWithPath, NULL, FirstParameter, 0); | |
} | |
} | |
// | |
// Process a relative path and also check in the path environment variable | |
// | |
if (CommandWithPath == NULL) { | |
CommandWithPath = ShellFindFilePathEx (FirstParameter, mExecutableExtensions); | |
} | |
// | |
// This should be impossible now. | |
// | |
ASSERT (CommandWithPath != NULL); | |
// | |
// Make sure that path is not just a directory (or not found) | |
// | |
if (!EFI_ERROR (ShellIsDirectory (CommandWithPath))) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); | |
SetLastError (SHELL_NOT_FOUND); | |
} | |
switch (Type) { | |
case Script_File_Name: | |
FullCommandWithPath = FullyQualifyPath (CommandWithPath); | |
if (FullCommandWithPath == NULL) { | |
Status = RunScriptFile (CommandWithPath, NULL, CmdLine, ParamProtocol); | |
} else { | |
Status = RunScriptFile (FullCommandWithPath, NULL, CmdLine, ParamProtocol); | |
FreePool (FullCommandWithPath); | |
} | |
break; | |
case Efi_Application: | |
// | |
// Get the device path of the application image | |
// | |
DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath (CommandWithPath); | |
if (DevPath == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
break; | |
} | |
// | |
// Execute the device path | |
// | |
Status = InternalShellExecuteDevicePath ( | |
&gImageHandle, | |
DevPath, | |
CmdLine, | |
NULL, | |
&StartStatus | |
); | |
SHELL_FREE_NON_NULL (DevPath); | |
if (EFI_ERROR (Status)) { | |
CalleeExitStatus = (SHELL_STATUS)(Status & (~MAX_BIT)); | |
} else { | |
CalleeExitStatus = (SHELL_STATUS)StartStatus; | |
} | |
if (CommandStatus != NULL) { | |
*CommandStatus = CalleeExitStatus; | |
} | |
// | |
// Update last error status. | |
// | |
// Status is an EFI_STATUS. Clear top bit to convert to SHELL_STATUS | |
SetLastError (CalleeExitStatus); | |
break; | |
default: | |
// | |
// Do nothing. | |
// | |
break; | |
} | |
break; | |
default: | |
// | |
// Do nothing. | |
// | |
break; | |
} | |
SHELL_FREE_NON_NULL (CommandWithPath); | |
return (Status); | |
} | |
/** | |
Function to setup StdIn, StdErr, StdOut, and then run the command or file. | |
@param[in] Type the type of operation being run. | |
@param[in] CmdLine the command line to run. | |
@param[in] FirstParameter the first parameter on the command line. | |
@param[in] ParamProtocol the shell parameters protocol pointer | |
@param[out] CommandStatus the status from the command line. | |
@retval EFI_SUCCESS The command was completed. | |
@retval EFI_ABORTED The command's operation was aborted. | |
**/ | |
EFI_STATUS | |
SetupAndRunCommandOrFile ( | |
IN SHELL_OPERATION_TYPES Type, | |
IN CHAR16 *CmdLine, | |
IN CHAR16 *FirstParameter, | |
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol, | |
OUT EFI_STATUS *CommandStatus | |
) | |
{ | |
EFI_STATUS Status; | |
SHELL_FILE_HANDLE OriginalStdIn; | |
SHELL_FILE_HANDLE OriginalStdOut; | |
SHELL_FILE_HANDLE OriginalStdErr; | |
SYSTEM_TABLE_INFO OriginalSystemTableInfo; | |
CONST SCRIPT_FILE *ConstScriptFile; | |
// | |
// Update the StdIn, StdOut, and StdErr for redirection to environment variables, files, etc... unicode and ASCII | |
// | |
Status = UpdateStdInStdOutStdErr (ParamProtocol, CmdLine, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); | |
// | |
// The StdIn, StdOut, and StdErr are set up. | |
// Now run the command, script, or application | |
// | |
if (!EFI_ERROR (Status)) { | |
TrimSpaces (&CmdLine); | |
Status = RunCommandOrFile (Type, CmdLine, FirstParameter, ParamProtocol, CommandStatus); | |
} | |
// | |
// Now print errors | |
// | |
if (EFI_ERROR (Status)) { | |
ConstScriptFile = ShellCommandGetCurrentScriptFile (); | |
if ((ConstScriptFile == NULL) || (ConstScriptFile->CurrentCommand == NULL)) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID *)(Status)); | |
} else { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR_SCRIPT), ShellInfoObject.HiiHandle, (VOID *)(Status), ConstScriptFile->CurrentCommand->Line); | |
} | |
} | |
// | |
// put back the original StdIn, StdOut, and StdErr | |
// | |
RestoreStdInStdOutStdErr (ParamProtocol, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); | |
return (Status); | |
} | |
/** | |
Function will process and run a command line. | |
This will determine if the command line represents an internal shell | |
command or dispatch an external application. | |
@param[in] CmdLine The command line to parse. | |
@param[out] CommandStatus The status from the command line. | |
@retval EFI_SUCCESS The command was completed. | |
@retval EFI_ABORTED The command's operation was aborted. | |
**/ | |
EFI_STATUS | |
RunShellCommand ( | |
IN CONST CHAR16 *CmdLine, | |
OUT EFI_STATUS *CommandStatus | |
) | |
{ | |
EFI_STATUS Status; | |
CHAR16 *CleanOriginal; | |
CHAR16 *FirstParameter; | |
CHAR16 *TempWalker; | |
SHELL_OPERATION_TYPES Type; | |
CONST CHAR16 *CurDir; | |
ASSERT (CmdLine != NULL); | |
if (StrLen (CmdLine) == 0) { | |
return (EFI_SUCCESS); | |
} | |
Status = EFI_SUCCESS; | |
CleanOriginal = NULL; | |
CleanOriginal = StrnCatGrow (&CleanOriginal, NULL, CmdLine, 0); | |
if (CleanOriginal == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
TrimSpaces (&CleanOriginal); | |
// | |
// NULL out comments (leveraged from RunScriptFileHandle() ). | |
// The # character on a line is used to denote that all characters on the same line | |
// and to the right of the # are to be ignored by the shell. | |
// Afterwards, again remove spaces, in case any were between the last command-parameter and '#'. | |
// | |
for (TempWalker = CleanOriginal; TempWalker != NULL && *TempWalker != CHAR_NULL; TempWalker++) { | |
if (*TempWalker == L'^') { | |
if (*(TempWalker + 1) == L'#') { | |
TempWalker++; | |
} | |
} else if (*TempWalker == L'#') { | |
*TempWalker = CHAR_NULL; | |
} | |
} | |
TrimSpaces (&CleanOriginal); | |
// | |
// Handle case that passed in command line is just 1 or more " " characters. | |
// | |
if (StrLen (CleanOriginal) == 0) { | |
SHELL_FREE_NON_NULL (CleanOriginal); | |
return (EFI_SUCCESS); | |
} | |
Status = ProcessCommandLineToFinal (&CleanOriginal); | |
if (EFI_ERROR (Status)) { | |
SHELL_FREE_NON_NULL (CleanOriginal); | |
return (Status); | |
} | |
// | |
// We don't do normal processing with a split command line (output from one command input to another) | |
// | |
if (ContainsSplit (CleanOriginal)) { | |
Status = ProcessNewSplitCommandLine (CleanOriginal); | |
SHELL_FREE_NON_NULL (CleanOriginal); | |
return (Status); | |
} | |
// | |
// We need the first parameter information so we can determine the operation type | |
// | |
FirstParameter = AllocateZeroPool (StrSize (CleanOriginal)); | |
if (FirstParameter == NULL) { | |
SHELL_FREE_NON_NULL (CleanOriginal); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
TempWalker = CleanOriginal; | |
if (!EFI_ERROR (GetNextParameter (&TempWalker, &FirstParameter, StrSize (CleanOriginal), TRUE))) { | |
// | |
// Depending on the first parameter we change the behavior | |
// | |
switch (Type = GetOperationType (FirstParameter)) { | |
case File_Sys_Change: | |
Status = ChangeMappedDrive (FirstParameter); | |
break; | |
case Internal_Command: | |
case Script_File_Name: | |
case Efi_Application: | |
Status = SetupAndRunCommandOrFile (Type, CleanOriginal, FirstParameter, ShellInfoObject.NewShellParametersProtocol, CommandStatus); | |
break; | |
default: | |
// | |
// Whatever was typed, it was invalid. | |
// | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); | |
SetLastError (SHELL_NOT_FOUND); | |
break; | |
} | |
} else { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); | |
SetLastError (SHELL_NOT_FOUND); | |
} | |
// | |
// Check whether the current file system still exists. If not exist, we need update "cwd" and gShellCurMapping. | |
// | |
CurDir = EfiShellGetCurDir (NULL); | |
if (CurDir != NULL) { | |
if (EFI_ERROR (ShellFileExists (CurDir))) { | |
// | |
// EfiShellSetCurDir() cannot set current directory to NULL. | |
// EfiShellSetEnv() is not allowed to set the "cwd" variable. | |
// Only InternalEfiShellSetEnv () is allowed setting the "cwd" variable. | |
// | |
InternalEfiShellSetEnv (L"cwd", NULL, TRUE); | |
gShellCurMapping = NULL; | |
} | |
} | |
SHELL_FREE_NON_NULL (CleanOriginal); | |
SHELL_FREE_NON_NULL (FirstParameter); | |
return (Status); | |
} | |
/** | |
Function will process and run a command line. | |
This will determine if the command line represents an internal shell | |
command or dispatch an external application. | |
@param[in] CmdLine The command line to parse. | |
@retval EFI_SUCCESS The command was completed. | |
@retval EFI_ABORTED The command's operation was aborted. | |
**/ | |
EFI_STATUS | |
RunCommand ( | |
IN CONST CHAR16 *CmdLine | |
) | |
{ | |
return (RunShellCommand (CmdLine, NULL)); | |
} | |
/** | |
Function to process a NSH script file via SHELL_FILE_HANDLE. | |
@param[in] Handle The handle to the already opened file. | |
@param[in] Name The name of the script file. | |
@retval EFI_SUCCESS the script completed successfully | |
**/ | |
EFI_STATUS | |
RunScriptFileHandle ( | |
IN SHELL_FILE_HANDLE Handle, | |
IN CONST CHAR16 *Name | |
) | |
{ | |
EFI_STATUS Status; | |
SCRIPT_FILE *NewScriptFile; | |
UINTN LoopVar; | |
UINTN PrintBuffSize; | |
CHAR16 *CommandLine; | |
CHAR16 *CommandLine2; | |
CHAR16 *CommandLine3; | |
SCRIPT_COMMAND_LIST *LastCommand; | |
BOOLEAN Ascii; | |
BOOLEAN PreScriptEchoState; | |
BOOLEAN PreCommandEchoState; | |
CONST CHAR16 *CurDir; | |
UINTN LineCount; | |
CHAR16 LeString[50]; | |
LIST_ENTRY OldBufferList; | |
ASSERT (!ShellCommandGetScriptExit ()); | |
PreScriptEchoState = ShellCommandGetEchoState (); | |
PrintBuffSize = PcdGet16 (PcdShellPrintBufferSize); | |
NewScriptFile = (SCRIPT_FILE *)AllocateZeroPool (sizeof (SCRIPT_FILE)); | |
if (NewScriptFile == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
// | |
// Set up the name | |
// | |
ASSERT (NewScriptFile->ScriptName == NULL); | |
NewScriptFile->ScriptName = StrnCatGrow (&NewScriptFile->ScriptName, NULL, Name, 0); | |
if (NewScriptFile->ScriptName == NULL) { | |
DeleteScriptFileStruct (NewScriptFile); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
// | |
// Save the parameters (used to replace %0 to %9 later on) | |
// | |
NewScriptFile->Argc = ShellInfoObject.NewShellParametersProtocol->Argc; | |
if (NewScriptFile->Argc != 0) { | |
NewScriptFile->Argv = (CHAR16 **)AllocateZeroPool (NewScriptFile->Argc * sizeof (CHAR16 *)); | |
if (NewScriptFile->Argv == NULL) { | |
DeleteScriptFileStruct (NewScriptFile); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
// | |
// Put the full path of the script file into Argv[0] as required by section | |
// 3.6.2 of version 2.2 of the shell specification. | |
// | |
NewScriptFile->Argv[0] = StrnCatGrow (&NewScriptFile->Argv[0], NULL, NewScriptFile->ScriptName, 0); | |
for (LoopVar = 1; LoopVar < 10 && LoopVar < NewScriptFile->Argc; LoopVar++) { | |
ASSERT (NewScriptFile->Argv[LoopVar] == NULL); | |
NewScriptFile->Argv[LoopVar] = StrnCatGrow (&NewScriptFile->Argv[LoopVar], NULL, ShellInfoObject.NewShellParametersProtocol->Argv[LoopVar], 0); | |
if (NewScriptFile->Argv[LoopVar] == NULL) { | |
DeleteScriptFileStruct (NewScriptFile); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
} | |
} else { | |
NewScriptFile->Argv = NULL; | |
} | |
InitializeListHead (&NewScriptFile->CommandList); | |
InitializeListHead (&NewScriptFile->SubstList); | |
// | |
// Now build the list of all script commands. | |
// | |
LineCount = 0; | |
while (!ShellFileHandleEof (Handle)) { | |
CommandLine = ShellFileHandleReturnLine (Handle, &Ascii); | |
LineCount++; | |
if ((CommandLine == NULL) || (StrLen (CommandLine) == 0) || (CommandLine[0] == '#')) { | |
SHELL_FREE_NON_NULL (CommandLine); | |
continue; | |
} | |
NewScriptFile->CurrentCommand = AllocateZeroPool (sizeof (SCRIPT_COMMAND_LIST)); | |
if (NewScriptFile->CurrentCommand == NULL) { | |
SHELL_FREE_NON_NULL (CommandLine); | |
DeleteScriptFileStruct (NewScriptFile); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
NewScriptFile->CurrentCommand->Cl = CommandLine; | |
NewScriptFile->CurrentCommand->Data = NULL; | |
NewScriptFile->CurrentCommand->Line = LineCount; | |
InsertTailList (&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link); | |
} | |
// | |
// Add this as the topmost script file | |
// | |
ShellCommandSetNewScript (NewScriptFile); | |
// | |
// Now enumerate through the commands and run each one. | |
// | |
CommandLine = AllocateZeroPool (PrintBuffSize); | |
if (CommandLine == NULL) { | |
DeleteScriptFileStruct (NewScriptFile); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
CommandLine2 = AllocateZeroPool (PrintBuffSize); | |
if (CommandLine2 == NULL) { | |
FreePool (CommandLine); | |
DeleteScriptFileStruct (NewScriptFile); | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
for ( NewScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetFirstNode (&NewScriptFile->CommandList) | |
; !IsNull (&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link) | |
; // conditional increment in the body of the loop | |
) | |
{ | |
ASSERT (CommandLine2 != NULL); | |
StrnCpyS ( | |
CommandLine2, | |
PrintBuffSize/sizeof (CHAR16), | |
NewScriptFile->CurrentCommand->Cl, | |
PrintBuffSize/sizeof (CHAR16) - 1 | |
); | |
SaveBufferList (&OldBufferList); | |
// | |
// NULL out comments | |
// | |
for (CommandLine3 = CommandLine2; CommandLine3 != NULL && *CommandLine3 != CHAR_NULL; CommandLine3++) { | |
if (*CommandLine3 == L'^') { | |
if ( *(CommandLine3+1) == L':') { | |
CopyMem (CommandLine3, CommandLine3+1, StrSize (CommandLine3) - sizeof (CommandLine3[0])); | |
} else if (*(CommandLine3+1) == L'#') { | |
CommandLine3++; | |
} | |
} else if (*CommandLine3 == L'#') { | |
*CommandLine3 = CHAR_NULL; | |
} | |
} | |
if ((CommandLine2 != NULL) && (StrLen (CommandLine2) >= 1)) { | |
// | |
// Due to variability in starting the find and replace action we need to have both buffers the same. | |
// | |
StrnCpyS ( | |
CommandLine, | |
PrintBuffSize/sizeof (CHAR16), | |
CommandLine2, | |
PrintBuffSize/sizeof (CHAR16) - 1 | |
); | |
// | |
// Remove the %0 to %9 from the command line (if we have some arguments) | |
// | |
if (NewScriptFile->Argv != NULL) { | |
switch (NewScriptFile->Argc) { | |
default: | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%9", NewScriptFile->Argv[9], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 9: | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%8", NewScriptFile->Argv[8], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 8: | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%7", NewScriptFile->Argv[7], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 7: | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%6", NewScriptFile->Argv[6], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 6: | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%5", NewScriptFile->Argv[5], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 5: | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%4", NewScriptFile->Argv[4], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 4: | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%3", NewScriptFile->Argv[3], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 3: | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%2", NewScriptFile->Argv[2], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 2: | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%1", NewScriptFile->Argv[1], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
case 1: | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%0", NewScriptFile->Argv[0], FALSE, FALSE); | |
ASSERT_EFI_ERROR (Status); | |
break; | |
case 0: | |
break; | |
} | |
} | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%1", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%2", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%3", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%4", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%5", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%6", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%7", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine, CommandLine2, PrintBuffSize, L"%8", L"\"\"", FALSE, FALSE); | |
Status = ShellCopySearchAndReplace (CommandLine2, CommandLine, PrintBuffSize, L"%9", L"\"\"", FALSE, FALSE); | |
StrnCpyS ( | |
CommandLine2, | |
PrintBuffSize/sizeof (CHAR16), | |
CommandLine, | |
PrintBuffSize/sizeof (CHAR16) - 1 | |
); | |
LastCommand = NewScriptFile->CurrentCommand; | |
for (CommandLine3 = CommandLine2; CommandLine3[0] == L' '; CommandLine3++) { | |
} | |
if ((CommandLine3 != NULL) && (CommandLine3[0] == L':')) { | |
// | |
// This line is a goto target / label | |
// | |
} else { | |
if ((CommandLine3 != NULL) && (StrLen (CommandLine3) > 0)) { | |
if (CommandLine3[0] == L'@') { | |
// | |
// We need to save the current echo state | |
// and disable echo for just this command. | |
// | |
PreCommandEchoState = ShellCommandGetEchoState (); | |
ShellCommandSetEchoState (FALSE); | |
Status = RunCommand (CommandLine3+1); | |
// | |
// If command was "@echo -off" or "@echo -on" then don't restore echo state | |
// | |
if ((StrCmp (L"@echo -off", CommandLine3) != 0) && | |
(StrCmp (L"@echo -on", CommandLine3) != 0)) | |
{ | |
// | |
// Now restore the pre-'@' echo state. | |
// | |
ShellCommandSetEchoState (PreCommandEchoState); | |
} | |
} else { | |
if (ShellCommandGetEchoState ()) { | |
CurDir = ShellInfoObject.NewEfiShellProtocol->GetEnv (L"cwd"); | |
if ((CurDir != NULL) && (StrLen (CurDir) > 1)) { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_CURDIR), ShellInfoObject.HiiHandle, CurDir); | |
} else { | |
ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SHELL_SHELL), ShellInfoObject.HiiHandle); | |
} | |
ShellPrintEx (-1, -1, L"%s\r\n", CommandLine2); | |
} | |
Status = RunCommand (CommandLine3); | |
} | |
} | |
if (ShellCommandGetScriptExit ()) { | |
// | |
// ShellCommandGetExitCode() always returns a UINT64 | |
// | |
UnicodeSPrint (LeString, sizeof (LeString), L"0x%Lx", ShellCommandGetExitCode ()); | |
DEBUG_CODE ( | |
InternalEfiShellSetEnv (L"debuglasterror", LeString, TRUE); | |
); | |
InternalEfiShellSetEnv (L"lasterror", LeString, TRUE); | |
ShellCommandRegisterExit (FALSE, 0); | |
Status = EFI_SUCCESS; | |
RestoreBufferList (&OldBufferList); | |
break; | |
} | |
if (ShellGetExecutionBreakFlag ()) { | |
RestoreBufferList (&OldBufferList); | |
break; | |
} | |
if (EFI_ERROR (Status)) { | |
RestoreBufferList (&OldBufferList); | |
break; | |
} | |
if (ShellCommandGetExit ()) { | |
RestoreBufferList (&OldBufferList); | |
break; | |
} | |
} | |
// | |
// If that commend did not update the CurrentCommand then we need to advance it... | |
// | |
if (LastCommand == NewScriptFile->CurrentCommand) { | |
NewScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetNextNode (&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link); | |
if (!IsNull (&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link)) { | |
NewScriptFile->CurrentCommand->Reset = TRUE; | |
} | |
} | |
} else { | |
NewScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetNextNode (&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link); | |
if (!IsNull (&NewScriptFile->CommandList, &NewScriptFile->CurrentCommand->Link)) { | |
NewScriptFile->CurrentCommand->Reset = TRUE; | |
} | |
} | |
RestoreBufferList (&OldBufferList); | |
} | |
FreePool (CommandLine); | |
FreePool (CommandLine2); | |
ShellCommandSetNewScript (NULL); | |
// | |
// Only if this was the last script reset the state. | |
// | |
if (ShellCommandGetCurrentScriptFile () == NULL) { | |
ShellCommandSetEchoState (PreScriptEchoState); | |
} | |
return (EFI_SUCCESS); | |
} | |
/** | |
Function to process a NSH script file. | |
@param[in] ScriptPath Pointer to the script file name (including file system path). | |
@param[in] Handle the handle of the script file already opened. | |
@param[in] CmdLine the command line to run. | |
@param[in] ParamProtocol the shell parameters protocol pointer | |
@retval EFI_SUCCESS the script completed successfully | |
**/ | |
EFI_STATUS | |
RunScriptFile ( | |
IN CONST CHAR16 *ScriptPath, | |
IN SHELL_FILE_HANDLE Handle OPTIONAL, | |
IN CONST CHAR16 *CmdLine, | |
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol | |
) | |
{ | |
EFI_STATUS Status; | |
SHELL_FILE_HANDLE FileHandle; | |
UINTN Argc; | |
CHAR16 **Argv; | |
if (ShellIsFile (ScriptPath) != EFI_SUCCESS) { | |
return (EFI_INVALID_PARAMETER); | |
} | |
// | |
// get the argc and argv updated for scripts | |
// | |
Status = UpdateArgcArgv (ParamProtocol, CmdLine, Script_File_Name, &Argv, &Argc); | |
if (!EFI_ERROR (Status)) { | |
if (Handle == NULL) { | |
// | |
// open the file | |
// | |
Status = ShellOpenFileByName (ScriptPath, &FileHandle, EFI_FILE_MODE_READ, 0); | |
if (!EFI_ERROR (Status)) { | |
// | |
// run it | |
// | |
Status = RunScriptFileHandle (FileHandle, ScriptPath); | |
// | |
// now close the file | |
// | |
ShellCloseFile (&FileHandle); | |
} | |
} else { | |
Status = RunScriptFileHandle (Handle, ScriptPath); | |
} | |
} | |
// | |
// This is guaranteed to be called after UpdateArgcArgv no matter what else happened. | |
// This is safe even if the update API failed. In this case, it may be a no-op. | |
// | |
RestoreArgcArgv (ParamProtocol, &Argv, &Argc); | |
return (Status); | |
} | |
/** | |
Return the pointer to the first occurrence of any character from a list of characters. | |
@param[in] String the string to parse | |
@param[in] CharacterList the list of character to look for | |
@param[in] EscapeCharacter An escape character to skip | |
@return the location of the first character in the string | |
@retval CHAR_NULL no instance of any character in CharacterList was found in String | |
**/ | |
CONST CHAR16 * | |
FindFirstCharacter ( | |
IN CONST CHAR16 *String, | |
IN CONST CHAR16 *CharacterList, | |
IN CONST CHAR16 EscapeCharacter | |
) | |
{ | |
UINT32 WalkChar; | |
UINT32 WalkStr; | |
for (WalkStr = 0; WalkStr < StrLen (String); WalkStr++) { | |
if (String[WalkStr] == EscapeCharacter) { | |
WalkStr++; | |
continue; | |
} | |
for (WalkChar = 0; WalkChar < StrLen (CharacterList); WalkChar++) { | |
if (String[WalkStr] == CharacterList[WalkChar]) { | |
return (&String[WalkStr]); | |
} | |
} | |
} | |
return (String + StrLen (String)); | |
} |