From af1a266670d040d2f4083ff309d732d648afba2a Mon Sep 17 00:00:00 2001 From: Angelos Mouzakitis Date: Tue, 10 Oct 2023 14:33:42 +0000 Subject: Add submodule dependency files Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec --- roms/edk2/ShellPkg/Application/Shell/Shell.c | 3183 ++++++++++++++++++++++++++ 1 file changed, 3183 insertions(+) create mode 100644 roms/edk2/ShellPkg/Application/Shell/Shell.c (limited to 'roms/edk2/ShellPkg/Application/Shell/Shell.c') diff --git a/roms/edk2/ShellPkg/Application/Shell/Shell.c b/roms/edk2/ShellPkg/Application/Shell/Shell.c new file mode 100644 index 000000000..a55630709 --- /dev/null +++ b/roms/edk2/ShellPkg/Application/Shell/Shell.c @@ -0,0 +1,3183 @@ +/** @file + This is THE shell (application) + + Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P.
+ Copyright 2015-2018 Dell Technologies.
+ 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 * 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 = 5; + + // + // 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; + + 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)); +} -- cgit