/** @file
This is THE shell (application)
Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.
(C) Copyright 2013-2014, Hewlett-Packard Development Company, L.P.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include "Shell.h"
//
// Initialize the global structure
//
SHELL_INFO ShellInfoObject = {
NULL,
NULL,
FALSE,
FALSE,
{
{{
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";
/**
Cleans off leading and trailing spaces and tabs.
@param[in] String pointer to the string to trim them off.
**/
EFI_STATUS
EFIAPI
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);
}
/**
Find a command line contains a split operation
@param[in] CmdLine The command line to parse.
@retval A pointer to the | character in CmdLine or NULL if not present.
**/
CONST CHAR16*
EFIAPI
FindSplit(
IN CONST CHAR16 *CmdLine
)
{
CONST CHAR16 *TempSpot;
TempSpot = NULL;
if (StrStr(CmdLine, L"|") != NULL) {
for (TempSpot = CmdLine ; TempSpot != NULL && *TempSpot != CHAR_NULL ; TempSpot++) {
if (*TempSpot == L'^' && *(TempSpot+1) == L'|') {
TempSpot++;
} else if (*TempSpot == L'|') {
break;
}
}
}
return (TempSpot);
}
/**
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
EFIAPI
ContainsSplit(
IN CONST CHAR16 *CmdLine
)
{
CONST CHAR16 *TempSpot;
TempSpot = FindSplit(CmdLine);
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 mnemory available.
**/
EFI_STATUS
EFIAPI
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;
UINTN ExitDataSize;
CHAR16 *ExitData;
SHELL_STATUS ExitStatus;
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)) {
ExitStatus = (SHELL_STATUS) (Status & (~MAX_BIT));
} else {
ExitStatus = SHELL_SUCCESS;
}
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);
//
// 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();
}
//
// 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", NULL);
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,
&ExitStatus
);
}
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 * 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);
ShellInfoObject.ConsoleInfo->Enabled = TRUE;
ShellInfoObject.ConsoleInfo->RowCounter = 0;
//
// Reset the CTRL-C event (yes we ignore the return values)
//
Status = gBS->CheckEvent (ShellInfoObject.NewEfiShellProtocol->ExecutionBreak);
//
// Display Prompt
//
Status = DoShellPrompt();
} while (!ShellCommandGetExit());
ExitStatus = (SHELL_STATUS) ShellCommandGetExitCode();
}
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);
}
CleanUpShellProtocol(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.
}
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;);
}
if (!EFI_ERROR (Status)) {
// If the command exited with an error, we pass this error out in the ExitData
// so that it can be retrieved by the EfiShellExecute function (which may
// start the shell with gBS->StartImage)
if (ExitStatus != SHELL_SUCCESS) {
// Allocate a buffer for exit data to pass to gBS->Exit().
// This buffer will contain the empty string immediately followed by
// the shell's exit status. (The empty string is required by the UEFI spec)
ExitDataSize = (sizeof (CHAR16) + sizeof (SHELL_STATUS));
ExitData = AllocatePool (ExitDataSize);
if (ExitData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
ExitData[0] = '\0';
// Use CopyMem to avoid alignment faults
CopyMem ((ExitData + 1), &ExitStatus, sizeof (ExitStatus));
gBS->Exit (ImageHandle, EFI_ABORTED, ExitDataSize, ExitData);
ASSERT (FALSE);
return EFI_SUCCESS;
} else {
return EFI_SUCCESS;
}
} else {
return Status;
}
}
/**
Sets all the alias' that were registered with the ShellCommandLib library.
@retval EFI_SUCCESS all init commands were run sucessfully.
**/
EFI_STATUS
EFIAPI
SetBuiltInAlias(
)
{
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
EFIAPI
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
EFIAPI
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 sucessful return the device path to the loaded image.
@param[in, out] FilePath On a sucessful return the device path to the file.
@retval EFI_SUCCESS The 2 device paths were sucessfully returned.
@retval other A error from gBS->HandleProtocol.
@sa HandleProtocol
**/
EFI_STATUS
EFIAPI
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
EFIAPI
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 (
&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.Delay = 5;
// Start LoopVar at 1 to ignore Argv[0] which is the name of this binary
// (probably "Shell.efi")
for (LoopVar = 1 ; 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"-delay",
CurrentArg
) == 0) {
ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay = TRUE;
// Check for optional delay value following "-delay"
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) {
// Unrecognised option
ShellPrintHiiEx(-1, -1, NULL,
STRING_TOKEN (STR_GEN_PROBLEM),
ShellInfoObject.HiiHandle,
CurrentArg
);
return EFI_INVALID_PARAMETER;
} else {
ShellInfoObject.ShellInitSettings.FileName = AllocateZeroPool(StrSize(CurrentArg));
if (ShellInfoObject.ShellInitSettings.FileName == NULL) {
return (EFI_OUT_OF_RESOURCES);
}
//
// We found `file-name`.
//
ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = 1;
StrCpy (ShellInfoObject.ShellInitSettings.FileName, CurrentArg);
LoopVar++;
// Add `file-name-options`
for (Size = 0 ; LoopVar < gEfiShellParametersProtocol->Argc ; LoopVar++) {
ASSERT((ShellInfoObject.ShellInitSettings.FileOptions == NULL && Size == 0) || (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);
}
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);
}
}
}
}
// "-nointerrupt" overrides "-delay"
if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt) {
ShellInfoObject.ShellInitSettings.Delay = 0;
}
return EFI_SUCCESS;
}
/**
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.
@param[out] ExitStatus The exit code of the script. Ignored if NULL.
@retval EFI_SUCCESS the variable is initialized.
**/
EFI_STATUS
EFIAPI
DoStartupScript(
IN EFI_DEVICE_PATH_PROTOCOL *ImagePath,
IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
OUT SHELL_STATUS *ExitStatus
)
{
EFI_STATUS Status;
UINTN Delay;
EFI_INPUT_KEY Key;
SHELL_FILE_HANDLE FileHandle;
EFI_DEVICE_PATH_PROTOCOL *NewPath;
EFI_DEVICE_PATH_PROTOCOL *NamePath;
CHAR16 *FileStringPath;
CHAR16 *TempSpot;
UINTN NewSize;
CONST CHAR16 *MapName;
Key.UnicodeChar = CHAR_NULL;
Key.ScanCode = 0;
FileHandle = NULL;
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);
}
StrCpy(FileStringPath, ShellInfoObject.ShellInitSettings.FileName);
if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) {
StrCat(FileStringPath, L" ");
StrCat(FileStringPath, ShellInfoObject.ShellInitSettings.FileOptions);
}
Status = RunCommand(FileStringPath, ExitStatus);
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);
}
//
// Try the first location (must be file system)
//
MapName = ShellInfoObject.NewEfiShellProtocol->GetMapFromDevicePath(&ImagePath);
if (MapName != NULL) {
FileStringPath = NULL;
NewSize = 0;
FileStringPath = StrnCatGrow(&FileStringPath, &NewSize, MapName, 0);
if (FileStringPath == NULL) {
Status = EFI_OUT_OF_RESOURCES;
} else {
TempSpot = StrStr(FileStringPath, L";");
if (TempSpot != NULL) {
*TempSpot = CHAR_NULL;
}
FileStringPath = StrnCatGrow(&FileStringPath, &NewSize, ((FILEPATH_DEVICE_PATH*)FilePath)->PathName, 0);
PathRemoveLastItem(FileStringPath);
FileStringPath = StrnCatGrow(&FileStringPath, &NewSize, mStartupScript, 0);
Status = ShellInfoObject.NewEfiShellProtocol->OpenFileByName(FileStringPath, &FileHandle, EFI_FILE_MODE_READ);
FreePool(FileStringPath);
}
}
if (EFI_ERROR(Status)) {
NamePath = FileDevicePath (NULL, mStartupScript);
NewPath = AppendDevicePathNode (ImagePath, NamePath);
FreePool(NamePath);
//
// Try the location
//
Status = InternalOpenFileDevicePath(NewPath, &FileHandle, EFI_FILE_MODE_READ, 0);
FreePool(NewPath);
}
//
// If we got a file, run it
//
if (!EFI_ERROR(Status) && FileHandle != NULL) {
Status = RunScriptFile (
mStartupScript,
FileHandle,
L"",
ShellInfoObject.NewShellParametersProtocol,
ExitStatus
);
ShellInfoObject.NewEfiShellProtocol->CloseFile(FileHandle);
} else {
FileStringPath = ShellFindFilePath(mStartupScript);
if (FileStringPath == NULL) {
//
// we return success since we dont need to have a startup script
//
Status = EFI_SUCCESS;
ASSERT(FileHandle == NULL);
} else {
Status = RunScriptFile(
FileStringPath,
NULL,
L"",
ShellInfoObject.NewShellParametersProtocol,
ExitStatus
);
FreePool(FileStringPath);
}
}
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
EFIAPI
DoShellPrompt (
VOID
)
{
UINTN Column;
UINTN Row;
CHAR16 *CmdLine;
CONST CHAR16 *CurDir;
UINTN BufferSize;
EFI_STATUS Status;
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;
}
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)) {
CmdLine[BufferSize / sizeof (CHAR16)] = CHAR_NULL;
Status = RunCommand(CmdLine, NULL);
}
//
// Done with this command
//
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*
EFIAPI
AddBufferToFreeList(
VOID *Buffer
)
{
BUFFER_LIST *BufferListEntry;
if (Buffer == NULL) {
return (NULL);
}
BufferListEntry = AllocateZeroPool(sizeof(BUFFER_LIST));
ASSERT(BufferListEntry != NULL);
BufferListEntry->Buffer = Buffer;
InsertTailList(&ShellInfoObject.BufferToFreeList.Link, &BufferListEntry->Link);
return (Buffer);
}
/**
Add a buffer to the Line History List
@param Buffer The line buffer to add.
**/
VOID
EFIAPI
AddLineToCommandHistory(
IN CONST CHAR16 *Buffer
)
{
BUFFER_LIST *Node;
Node = AllocateZeroPool(sizeof(BUFFER_LIST));
ASSERT(Node != NULL);
Node->Buffer = AllocateZeroPool(StrSize(Buffer));
ASSERT(Node->Buffer != NULL);
StrCpy(Node->Buffer, Buffer);
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
EFIAPI
ShellConvertAlias(
IN OUT CHAR16 **CommandString
)
{
CONST CHAR16 *NewString;
NewString = ShellInfoObject.NewEfiShellProtocol->GetAlias(*CommandString, NULL);
if (NewString == NULL) {
return (EFI_SUCCESS);
}
FreePool(*CommandString);
*CommandString = AllocateZeroPool(StrSize(NewString));
if (*CommandString == NULL) {
return (EFI_OUT_OF_RESOURCES);
}
StrCpy(*CommandString, NewString);
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*
EFIAPI
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 dont 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);
}
/**
This function will eliminate unreplaced (and therefore non-found) environment variables.
@param[in,out] CmdLine The command line to update.
**/
EFI_STATUS
EFIAPI
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 dont have 2 % we are done.
//
break;
}
if (FirstQuote < FirstPercent) {
SecondQuote = FirstQuote!= NULL?FindNextInstance(FirstQuote+1, L"\"", TRUE):NULL;
//
// 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;
}
ASSERT(FirstPercent < FirstQuote);
if (SecondPercent < FirstQuote) {
//
// We need to remove from FirstPercent to SecondPercent
//
CopyMem(FirstPercent, SecondPercent + 1, StrSize(SecondPercent + 1));
//
// dont need to update the locator. both % characters are gone.
//
continue;
}
ASSERT(FirstQuote < SecondPercent);
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 ocurred.
@return The new command line with no environment variables present.
**/
CHAR16*
EFIAPI
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 preceeding and if there is space no ^ preceeding (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 preceeding and following % and if there is space no ^ preceeding (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);
}
StrCpy(NewCommandLine1, OriginalCommandLine);
for (MasterEnvList = EfiShellGetEnv(NULL)
; MasterEnvList != NULL && *MasterEnvList != CHAR_NULL //&& *(MasterEnvList+1) != CHAR_NULL
; MasterEnvList += StrLen(MasterEnvList) + 1
){
StrCpy(ItemTemp, L"%");
StrCat(ItemTemp, MasterEnvList);
StrCat(ItemTemp, L"%");
ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, ItemTemp, EfiShellGetEnv(MasterEnvList), TRUE, FALSE);
StrCpy(NewCommandLine1, 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);
StrCpy(NewCommandLine1, NewCommandLine2);
}
//
// Remove non-existant environment variables in scripts only
//
StripUnreplacedEnvironmentVariables(NewCommandLine1);
}
//
// Now cleanup any straggler intentionally ignored "%" characters
//
ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, L"^%", L"%", TRUE, FALSE);
StrCpy(NewCommandLine1, 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.
@param[out] ExitStatus The exit code of the last command in the pipeline.
Ignored if NULL.
@retval EFI_SUCCESS The split command is executed successfully.
@retval other Some error occurs when executing the split command.
**/
EFI_STATUS
EFIAPI
RunSplitCommand(
IN CONST CHAR16 *CmdLine,
IN SHELL_FILE_HANDLE *StdIn,
IN SHELL_FILE_HANDLE *StdOut,
OUT SHELL_STATUS *ExitStatus
)
{
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] != CHAR_NULL &&
NextCommandLine[0] == L'a' &&
NextCommandLine[1] == L' '
){
CopyMem(NextCommandLine, NextCommandLine+1, StrSize(NextCommandLine) - sizeof(NextCommandLine[0]));
Unicode = FALSE;
} else {
Unicode = TRUE;
}
//
// make a SPLIT_LIST item and add to list
//
Split = AllocateZeroPool(sizeof(SPLIT_LIST));
ASSERT(Split != NULL);
Split->SplitStdIn = StdIn;
Split->SplitStdOut = ConvertEfiFileProtocolToShellHandle(CreateFileInterfaceMem(Unicode), NULL);
ASSERT(Split->SplitStdOut != NULL);
InsertHeadList(&ShellInfoObject.SplitList.Link, &Split->Link);
Status = RunCommand(OurCommandLine, NULL);
//
// 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(ConvertShellHandleToEfiFileProtocol(Split->SplitStdIn), 0);
if (!EFI_ERROR(Status)) {
Status = RunCommand(NextCommandLine, ExitStatus);
}
//
// 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 && Split->SplitStdOut != StdIn) {
ShellInfoObject.NewEfiShellProtocol->CloseFile(ConvertShellHandleToEfiFileProtocol(Split->SplitStdOut));
}
if (Split->SplitStdIn != NULL) {
ShellInfoObject.NewEfiShellProtocol->CloseFile(ConvertShellHandleToEfiFileProtocol(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
EFIAPI
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
EFIAPI
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
EFIAPI
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
EFIAPI
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;
GetNextParameter(&TempWalker, &FirstParameter);
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
EFIAPI
VerifySplit(
IN CONST CHAR16 *CmdLine
)
{
CONST CHAR16 *TempSpot;
EFI_STATUS Status;
//
// Verify up to the pipe or end character
//
Status = IsValidSplit(CmdLine);
if (EFI_ERROR(Status)) {
return (Status);
}
//
// If this was the only item, then get out
//
if (!ContainsSplit(CmdLine)) {
return (EFI_SUCCESS);
}
//
// recurse to verify the next item
//
TempSpot = FindSplit(CmdLine)+1;
return (VerifySplit(TempSpot));
}
/**
Process a split based operation.
@param[in] CmdLine Pointer to the command line to process
@param[out] ExitStatus The exit status of the command. Ignored if NULL.
Invalid if this function returns an error.
@retval EFI_SUCCESS The operation was successful
@return an error occured.
**/
EFI_STATUS
EFIAPI
ProcessNewSplitCommandLine(
IN CONST CHAR16 *CmdLine,
OUT SHELL_STATUS *ExitStatus
)
{
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, ExitStatus);
} else {
Status = RunSplitCommand(
CmdLine,
Split->SplitStdIn,
Split->SplitStdOut,
ExitStatus
);
}
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
EFIAPI
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
EFIAPI
DoHelpUpdate(
IN OUT CHAR16 **CmdLine
)
{
CHAR16 *CurrentParameter;
CHAR16 *Walker;
CHAR16 *LastWalker;
CHAR16 *NewCommandLine;
EFI_STATUS Status;
Status = EFI_SUCCESS;
CurrentParameter = AllocateZeroPool(StrSize(*CmdLine));
if (CurrentParameter == NULL) {
return (EFI_OUT_OF_RESOURCES);
}
Walker = *CmdLine;
while(Walker != NULL && *Walker != CHAR_NULL) {
LastWalker = Walker;
GetNextParameter(&Walker, &CurrentParameter);
if (StrStr(CurrentParameter, L"-?") == CurrentParameter) {
LastWalker[0] = L' ';
LastWalker[1] = L' ';
NewCommandLine = AllocateZeroPool(StrSize(L"help ") + StrSize(*CmdLine));
if (NewCommandLine == NULL) {
Status = EFI_OUT_OF_RESOURCES;
break;
}
StrCpy(NewCommandLine, L"help ");
StrCat(NewCommandLine, *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
EFIAPI
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 it's 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 occured
**/
EFI_STATUS
EFIAPI
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);
}
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 upadate 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] ExitStatus The exit code of the command. Ignored if NULL.
@retval EFI_SUCCESS The command was completed.
@retval EFI_ABORTED The command's operation was aborted.
**/
EFI_STATUS
EFIAPI
RunInternalCommand(
IN CONST CHAR16 *CmdLine,
IN CHAR16 *FirstParameter,
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol,
OUT SHELL_STATUS *ExitStatus OPTIONAL
)
{
EFI_STATUS Status;
UINTN Argc;
CHAR16 **Argv;
SHELL_STATUS CommandReturnedStatus;
BOOLEAN LastError;
//
// get the argc and argv updated for internal commands
//
Status = UpdateArgcArgv(ParamProtocol, CmdLine, &Argv, &Argc);
if (!EFI_ERROR(Status)) {
//
// Run the internal command.
//
Status = ShellCommandRunCommandHandler(FirstParameter, &CommandReturnedStatus, &LastError);
if (!EFI_ERROR(Status)) {
//
// Update last error status.
// some commands do not update last error.
//
if (LastError) {
SetLastError(CommandReturnedStatus);
}
if (ExitStatus != NULL) {
*ExitStatus = 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 guarenteed 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 scipt 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;
}
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] ExitStatus The exit code of the command or file.
Ignored if NULL.
@retval EFI_SUCCESS The command was completed.
@retval EFI_ABORTED The command's operation was aborted.
**/
EFI_STATUS
EFIAPI
RunCommandOrFile(
IN SHELL_OPERATION_TYPES Type,
IN CONST CHAR16 *CmdLine,
IN CHAR16 *FirstParameter,
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol,
OUT SHELL_STATUS *ExitStatus
)
{
EFI_STATUS Status;
CHAR16 *CommandWithPath;
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,
&CalleeExitStatus
);
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:
Status = RunScriptFile (
CommandWithPath,
NULL,
CmdLine,
ParamProtocol,
&CalleeExitStatus
);
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,
NULL,
NULL
);
SHELL_FREE_NON_NULL(DevPath);
if(EFI_ERROR (Status)) {
CalleeExitStatus = (SHELL_STATUS) (Status & (~MAX_BIT));
} else {
CalleeExitStatus = SHELL_SUCCESS;
}
//
// 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);
if (ExitStatus != NULL) {
*ExitStatus = CalleeExitStatus;
}
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] ExitStatus The exit code of the command or file.
Ignored if NULL.
@retval EFI_SUCCESS The command was completed.
@retval EFI_ABORTED The command's operation was aborted.
**/
EFI_STATUS
EFIAPI
SetupAndRunCommandOrFile(
IN SHELL_OPERATION_TYPES Type,
IN CHAR16 *CmdLine,
IN CHAR16 *FirstParameter,
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol,
OUT SHELL_STATUS *ExitStatus
)
{
EFI_STATUS Status;
SHELL_FILE_HANDLE OriginalStdIn;
SHELL_FILE_HANDLE OriginalStdOut;
SHELL_FILE_HANDLE OriginalStdErr;
SYSTEM_TABLE_INFO OriginalSystemTableInfo;
//
// 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)) {
Status = RunCommandOrFile(
Type,
CmdLine,
FirstParameter,
ParamProtocol,
ExitStatus
);
}
//
// Now print errors
//
if (EFI_ERROR(Status)) {
ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID*)(Status));
}
//
// 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] ExitStatus The exit code of the command. Ignored if NULL.
@retval EFI_SUCCESS The command was completed.
@retval EFI_ABORTED The command's operation was aborted.
**/
EFI_STATUS
EFIAPI
RunCommand(
IN CONST CHAR16 *CmdLine,
OUT SHELL_STATUS *ExitStatus
)
{
EFI_STATUS Status;
CHAR16 *CleanOriginal;
CHAR16 *FirstParameter;
CHAR16 *TempWalker;
SHELL_OPERATION_TYPES Type;
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);
//
// 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 dont do normal processing with a split command line (output from one command input to another)
//
if (ContainsSplit(CleanOriginal)) {
Status = ProcessNewSplitCommandLine(CleanOriginal, ExitStatus);
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;
GetNextParameter(&TempWalker, &FirstParameter);
//
// 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,
ExitStatus
);
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;
}
SHELL_FREE_NON_NULL(CleanOriginal);
SHELL_FREE_NON_NULL(FirstParameter);
return (Status);
}
STATIC CONST UINT16 InvalidChars[] = {L'*', L'?', L'<', L'>', L'\\', L'/', L'\"', 0x0001, 0x0002};
/**
Function determins if the CommandName COULD be a valid command. It does not determine whether
this is a valid command. It only checks for invalid characters.
@param[in] CommandName The name to check
@retval TRUE CommandName could be a command name
@retval FALSE CommandName could not be a valid command name
**/
BOOLEAN
EFIAPI
IsValidCommandName(
IN CONST CHAR16 *CommandName
)
{
UINTN Count;
if (CommandName == NULL) {
ASSERT(FALSE);
return (FALSE);
}
for ( Count = 0
; Count < sizeof(InvalidChars) / sizeof(InvalidChars[0])
; Count++
){
if (ScanMem16(CommandName, StrSize(CommandName), InvalidChars[Count]) != NULL) {
return (FALSE);
}
}
return (TRUE);
}
/**
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.
@param[out] ExitStatus The exit code of the script. Ignored if NULL.
@retval EFI_SUCCESS the script completed sucessfully
**/
EFI_STATUS
EFIAPI
RunScriptFileHandle (
IN SHELL_FILE_HANDLE Handle,
IN CONST CHAR16 *Name,
OUT SHELL_STATUS *ExitStatus
)
{
EFI_STATUS Status;
SCRIPT_FILE *NewScriptFile;
UINTN LoopVar;
CHAR16 *CommandLine;
CHAR16 *CommandLine2;
CHAR16 *CommandLine3;
SCRIPT_COMMAND_LIST *LastCommand;
BOOLEAN Ascii;
BOOLEAN PreScriptEchoState;
BOOLEAN PreCommandEchoState;
CONST CHAR16 *CurDir;
UINTN LineCount;
CHAR16 LeString[50];
SHELL_STATUS CalleeExitStatus;
ASSERT(!ShellCommandGetScriptExit());
CalleeExitStatus = SHELL_SUCCESS;
PreScriptEchoState = ShellCommandGetEchoState();
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);
}
for (LoopVar = 0 ; 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(PcdGet16(PcdShellPrintBufferSize));
if (CommandLine == NULL) {
DeleteScriptFileStruct(NewScriptFile);
return (EFI_OUT_OF_RESOURCES);
}
CommandLine2 = AllocateZeroPool(PcdGet16(PcdShellPrintBufferSize));
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);
StrCpy(CommandLine2, NewScriptFile->CurrentCommand->Cl);
//
// NULL out comments
//
for (CommandLine3 = CommandLine2 ; CommandLine3 != NULL && *CommandLine3 != CHAR_NULL ; CommandLine3++) {
if (*CommandLine3 == L'^') {
if (*(CommandLine3+1) == L'#' || *(CommandLine3+1) == L':') {
CopyMem(CommandLine3, CommandLine3+1, StrSize(CommandLine3) - sizeof(CommandLine3[0]));
}
} 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.
//
StrCpy(CommandLine, CommandLine2);
//
// 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, PcdGet16 (PcdShellPrintBufferSize), L"%9", NewScriptFile->Argv[9], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 9:
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%8", NewScriptFile->Argv[8], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 8:
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%7", NewScriptFile->Argv[7], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 7:
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%6", NewScriptFile->Argv[6], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 6:
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%5", NewScriptFile->Argv[5], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 5:
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%4", NewScriptFile->Argv[4], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 4:
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%3", NewScriptFile->Argv[3], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 3:
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%2", NewScriptFile->Argv[2], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 2:
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%1", NewScriptFile->Argv[1], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
case 1:
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%0", NewScriptFile->Argv[0], FALSE, TRUE);
ASSERT_EFI_ERROR(Status);
break;
case 0:
break;
}
}
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%1", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%2", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%3", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%4", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%5", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%6", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%7", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine, CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%8", L"\"\"", FALSE, FALSE);
Status = ShellCopySearchAndReplace(CommandLine2, CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%9", L"\"\"", FALSE, FALSE);
StrCpy(CommandLine2, CommandLine);
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, NULL);
//
// 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, NULL);
}
}
if (ShellCommandGetScriptExit()) {
//
// ShellCommandGetExitCode() always returns a UINT64
//
CalleeExitStatus = (SHELL_STATUS) ShellCommandGetExitCode();
UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", CalleeExitStatus);
DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE););
InternalEfiShellSetEnv(L"lasterror", LeString, TRUE);
ShellCommandRegisterExit(FALSE, 0);
Status = EFI_SUCCESS;
break;
}
if (ShellGetExecutionBreakFlag()) {
break;
}
if (EFI_ERROR(Status)) {
CalleeExitStatus = (SHELL_STATUS) Status;
break;
}
if (ShellCommandGetExit()) {
CalleeExitStatus = (SHELL_STATUS) ShellCommandGetExitCode();
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;
}
}
}
FreePool(CommandLine);
FreePool(CommandLine2);
ShellCommandSetNewScript (NULL);
//
// Only if this was the last script reset the state.
//
if (ShellCommandGetCurrentScriptFile()==NULL) {
ShellCommandSetEchoState(PreScriptEchoState);
}
if (ExitStatus != NULL) {
*ExitStatus = CalleeExitStatus;
}
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
@param[out] ExitStatus The exit code of the script. Ignored if NULL.
@retval EFI_SUCCESS the script completed sucessfully
**/
EFI_STATUS
EFIAPI
RunScriptFile (
IN CONST CHAR16 *ScriptPath,
IN SHELL_FILE_HANDLE Handle OPTIONAL,
IN CONST CHAR16 *CmdLine,
IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol,
OUT SHELL_STATUS *ExitStatus
)
{
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, &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, ExitStatus);
//
// now close the file
//
ShellCloseFile(&FileHandle);
}
} else {
Status = RunScriptFileHandle(Handle, ScriptPath, ExitStatus);
}
}
//
// This is guarenteed 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);
}