X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=ShellPkg%2FApplication%2FShell%2FShell.c;h=ba3be3f6c344236e393bc9a9174ddfa1a06d5725;hb=d5b5440bf205a37c8ae0ac4b873cd976a9598326;hp=0ce1665d84577f6c4b443ac7c941fa9be0078783;hpb=5b5cd144f3187f4208c6c9430c9752a4c5cdd415;p=mirror_edk2.git diff --git a/ShellPkg/Application/Shell/Shell.c b/ShellPkg/Application/Shell/Shell.c index 0ce1665d84..ba3be3f6c3 100644 --- a/ShellPkg/Application/Shell/Shell.c +++ b/ShellPkg/Application/Shell/Shell.c @@ -1,7 +1,7 @@ /** @file This is THE shell (application) - Copyright (c) 2009 - 2013, Intel Corporation. All rights reserved.
+ Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.
Copyright (c) 2013, 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 @@ -70,6 +70,82 @@ 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. @@ -131,7 +207,7 @@ InternalEfiShellStartCtrlSMonitor( SimpleEx, &KeyData, NotificationFunction, - &ShellInfoObject.CtrlSNotifyHandle2); + &ShellInfoObject.CtrlSNotifyHandle3); } KeyData.KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID|EFI_RIGHT_CONTROL_PRESSED; if (!EFI_ERROR(Status)) { @@ -139,7 +215,7 @@ InternalEfiShellStartCtrlSMonitor( SimpleEx, &KeyData, NotificationFunction, - &ShellInfoObject.CtrlSNotifyHandle2); + &ShellInfoObject.CtrlSNotifyHandle4); } return (Status); } @@ -168,6 +244,9 @@ UefiMain ( 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); @@ -221,6 +300,12 @@ UefiMain ( // 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 @@ -243,7 +328,8 @@ UefiMain ( ///@todo Add our package into Framework HII } if (ShellInfoObject.HiiHandle == NULL) { - return (EFI_NOT_STARTED); + Status = EFI_NOT_STARTED; + goto FreeResources; } } @@ -273,7 +359,10 @@ UefiMain ( // // Check the command line // - Status = ProcessCommandLine(); + Status = ProcessCommandLine (); + if (EFI_ERROR (Status)) { + goto FreeResources; + } // // If shell support level is >= 1 create the mappings and paths @@ -330,7 +419,7 @@ UefiMain ( // Display the mapping // if (PcdGet8(PcdShellSupportLevel) >= 2 && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap) { - Status = RunCommand(L"map"); + Status = RunCommand(L"map", NULL); ASSERT_EFI_ERROR(Status); } @@ -396,7 +485,11 @@ UefiMain ( // // process the startup script or launch the called app. // - Status = DoStartupScript(ShellInfoObject.ImageDevPath, ShellInfoObject.FileDevPath); + 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) { @@ -429,6 +522,7 @@ UefiMain ( // Status = DoShellPrompt(); } while (!ShellCommandGetExit()); + ExitStatus = (SHELL_STATUS) ShellCommandGetExitCode(); } if (OldConIn != NULL && ConInHandle != NULL) { CloseSimpleTextInOnFile (gST->ConIn); @@ -438,6 +532,7 @@ UefiMain ( } } +FreeResources: // // uninstall protocols / free memory / etc... // @@ -499,10 +594,33 @@ UefiMain ( DEBUG_CODE(ShellInfoObject.ConsoleInfo = NULL;); } - if (ShellCommandGetExit()) { - return ((EFI_STATUS)ShellCommandGetExitCode()); + 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; } - return (Status); } /** @@ -650,20 +768,6 @@ GetDevicePathsForImageAndFile ( return (Status); } -STATIC CONST SHELL_PARAM_ITEM mShellParamList[] = { - {L"-nostartup", TypeFlag}, - {L"-startup", TypeFlag}, - {L"-noconsoleout", TypeFlag}, - {L"-noconsolein", TypeFlag}, - {L"-nointerrupt", TypeFlag}, - {L"-nomap", TypeFlag}, - {L"-noversion", TypeFlag}, - {L"-startup", TypeFlag}, - {L"-delay", TypeValue}, - {L"-_exit", TypeFlag}, - {NULL, TypeMax} - }; - /** Process all Uefi Shell 2.0 command line options. @@ -697,95 +801,171 @@ ProcessCommandLine( VOID ) { - EFI_STATUS Status; - LIST_ENTRY *Package; - UINTN Size; - CONST CHAR16 *TempConst; - UINTN Count; - UINTN LoopVar; - CHAR16 *ProblemParam; - UINT64 Intermediate; - - Package = NULL; - ProblemParam = NULL; - - Status = ShellCommandLineParse (mShellParamList, &Package, NULL, FALSE); - - Count = 1; - Size = 0; - TempConst = ShellCommandLineGetRawValue(Package, Count++); - if (TempConst != NULL && StrLen(TempConst)) { - ShellInfoObject.ShellInitSettings.FileName = AllocateZeroPool(StrSize(TempConst)); - if (ShellInfoObject.ShellInitSettings.FileName == NULL) { - return (EFI_OUT_OF_RESOURCES); + 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; } - StrCpy(ShellInfoObject.ShellInitSettings.FileName, TempConst); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = 1; - for (LoopVar = 0 ; LoopVar < gEfiShellParametersProtocol->Argc ; LoopVar++) { - if (StrCmp(gEfiShellParametersProtocol->Argv[LoopVar], ShellInfoObject.ShellInitSettings.FileName)==0) { - LoopVar++; - // - // We found the file... add the rest of the params... - // - for ( ; 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); - } + 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); } } - } - } else { - ShellCommandLineFreeVarList(Package); - Package = NULL; - Status = ShellCommandLineParse (mShellParamList, &Package, &ProblemParam, FALSE); - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), ShellInfoObject.HiiHandle, ProblemParam); - FreePool(ProblemParam); - ShellCommandLineFreeVarList(Package); - return (EFI_INVALID_PARAMETER); } } - ShellInfoObject.ShellInitSettings.BitUnion.Bits.Startup = ShellCommandLineGetFlag(Package, L"-startup"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = ShellCommandLineGetFlag(Package, L"-nostartup"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleOut = ShellCommandLineGetFlag(Package, L"-noconsoleout"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn = ShellCommandLineGetFlag(Package, L"-noconsolein"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt = ShellCommandLineGetFlag(Package, L"-nointerrupt"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap = ShellCommandLineGetFlag(Package, L"-nomap"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion = ShellCommandLineGetFlag(Package, L"-noversion"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay = ShellCommandLineGetFlag(Package, L"-delay"); - ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit = ShellCommandLineGetFlag(Package, L"-_exit"); - - ShellInfoObject.ShellInitSettings.Delay = 5; - + // "-nointerrupt" overrides "-delay" if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoInterrupt) { ShellInfoObject.ShellInitSettings.Delay = 0; - } else if (ShellInfoObject.ShellInitSettings.BitUnion.Bits.Delay) { - TempConst = ShellCommandLineGetValue(Package, L"-delay"); - if (TempConst != NULL && *TempConst == L':') { - TempConst++; - } - if (TempConst != NULL && !EFI_ERROR(ShellConvertStringToUint64(TempConst, &Intermediate, FALSE, FALSE))) { - ShellInfoObject.ShellInitSettings.Delay = (UINTN)Intermediate; - } } - ShellCommandLineFreeVarList(Package); - return (Status); + return EFI_SUCCESS; } /** @@ -796,13 +976,16 @@ ProcessCommandLine( @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( - EFI_DEVICE_PATH_PROTOCOL *ImagePath, - EFI_DEVICE_PATH_PROTOCOL *FilePath + IN EFI_DEVICE_PATH_PROTOCOL *ImagePath, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + OUT SHELL_STATUS *ExitStatus ) { EFI_STATUS Status; @@ -837,7 +1020,7 @@ DoStartupScript( StrCat(FileStringPath, L" "); StrCat(FileStringPath, ShellInfoObject.ShellInitSettings.FileOptions); } - Status = RunCommand(FileStringPath); + Status = RunCommand(FileStringPath, ExitStatus); FreePool(FileStringPath); return (Status); @@ -914,7 +1097,13 @@ DoStartupScript( // If we got a file, run it // if (!EFI_ERROR(Status) && FileHandle != NULL) { - Status = RunScriptFileHandle (FileHandle, mStartupScript); + Status = RunScriptFile ( + mStartupScript, + FileHandle, + L"", + ShellInfoObject.NewShellParametersProtocol, + ExitStatus + ); ShellInfoObject.NewEfiShellProtocol->CloseFile(FileHandle); } else { FileStringPath = ShellFindFilePath(mStartupScript); @@ -925,7 +1114,13 @@ DoStartupScript( Status = EFI_SUCCESS; ASSERT(FileHandle == NULL); } else { - Status = RunScriptFile(FileStringPath); + Status = RunScriptFile( + FileStringPath, + NULL, + L"", + ShellInfoObject.NewShellParametersProtocol, + ExitStatus + ); FreePool(FileStringPath); } } @@ -990,8 +1185,8 @@ DoShellPrompt ( // if (!EFI_ERROR (Status)) { CmdLine[BufferSize / sizeof (CHAR16)] = CHAR_NULL; - Status = RunCommand(CmdLine); - } + Status = RunCommand(CmdLine, NULL); + } // // Done with this command @@ -1081,6 +1276,113 @@ ShellConvertAlias( 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. @@ -1103,7 +1405,6 @@ ShellConvertVariables ( CHAR16 *NewCommandLine1; CHAR16 *NewCommandLine2; CHAR16 *Temp; - CHAR16 *Temp2; UINTN ItemSize; CHAR16 *ItemTemp; SCRIPT_FILE *CurrentScriptFile; @@ -1196,39 +1497,7 @@ ShellConvertVariables ( // // Remove non-existant environment variables in scripts only // - for (Temp = NewCommandLine1 ; Temp != NULL ; ) { - Temp = StrStr(Temp, L"%"); - if (Temp == NULL) { - break; - } - while (*(Temp - 1) == L'^') { - Temp = StrStr(Temp + 1, L"%"); - if (Temp == NULL) { - break; - } - } - if (Temp == NULL) { - break; - } - - Temp2 = StrStr(Temp + 1, L"%"); - if (Temp2 == NULL) { - break; - } - while (*(Temp2 - 1) == L'^') { - Temp2 = StrStr(Temp2 + 1, L"%"); - if (Temp2 == NULL) { - break; - } - } - if (Temp2 == NULL) { - break; - } - - Temp2++; - CopyMem(Temp, Temp2, StrSize(Temp2)); - } - + StripUnreplacedEnvironmentVariables(NewCommandLine1); } // @@ -1250,6 +1519,9 @@ ShellConvertVariables ( @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. **/ @@ -1258,7 +1530,8 @@ EFIAPI RunSplitCommand( IN CONST CHAR16 *CmdLine, IN SHELL_FILE_HANDLE *StdIn, - IN SHELL_FILE_HANDLE *StdOut + IN SHELL_FILE_HANDLE *StdOut, + OUT SHELL_STATUS *ExitStatus ) { EFI_STATUS Status; @@ -1312,7 +1585,7 @@ RunSplitCommand( ASSERT(Split->SplitStdOut != NULL); InsertHeadList(&ShellInfoObject.SplitList.Link, &Split->Link); - Status = RunCommand(OurCommandLine); + Status = RunCommand(OurCommandLine, NULL); // // move the output from the first to the in to the second. @@ -1327,7 +1600,7 @@ RunSplitCommand( ShellInfoObject.NewEfiShellProtocol->SetFilePosition(ConvertShellHandleToEfiFileProtocol(Split->SplitStdIn), 0); if (!EFI_ERROR(Status)) { - Status = RunCommand(NextCommandLine); + Status = RunCommand(NextCommandLine, ExitStatus); } // @@ -1354,347 +1627,884 @@ RunSplitCommand( } /** - 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. + Take the original command line, substitute any variables, free + the original string, return the modified copy. - @param[in] CmdLine The command line to parse. + @param[in] CmdLine pointer to the command line to update. - @retval EFI_SUCCESS The command was completed. - @retval EFI_ABORTED The command's operation was aborted. + @retval EFI_SUCCESS the function was successful. + @retval EFI_OUT_OF_RESOURCES a memory allocation failed. **/ EFI_STATUS EFIAPI -RunCommand( - IN CONST CHAR16 *CmdLine +ShellSubstituteVariables( + IN CHAR16 **CmdLine ) { - EFI_STATUS Status; - EFI_STATUS StatusCode; - CHAR16 *CommandName; - SHELL_STATUS ShellStatus; - UINTN Argc; - CHAR16 **Argv; - BOOLEAN LastError; - CHAR16 LeString[19]; - CHAR16 *PostAliasCmdLine; - UINTN PostAliasSize; - CHAR16 *PostVariableCmdLine; - CHAR16 *CommandWithPath; - CONST EFI_DEVICE_PATH_PROTOCOL *DevPath; - CONST CHAR16 *TempLocation; - CONST CHAR16 *TempLocation2; - SHELL_FILE_HANDLE OriginalStdIn; - SHELL_FILE_HANDLE OriginalStdOut; - SHELL_FILE_HANDLE OriginalStdErr; - SYSTEM_TABLE_INFO OriginalSystemTableInfo; - CHAR16 *TempLocation3; - UINTN Count; - UINTN Count2; - CHAR16 *CleanOriginal; - SPLIT_LIST *Split; - - ASSERT(CmdLine != NULL); - if (StrLen(CmdLine) == 0) { - return (EFI_SUCCESS); - } - - CommandName = NULL; - PostVariableCmdLine = NULL; - PostAliasCmdLine = NULL; - CommandWithPath = NULL; - DevPath = NULL; - Status = EFI_SUCCESS; - CleanOriginal = NULL; - Split = NULL; - - CleanOriginal = StrnCatGrow(&CleanOriginal, NULL, CmdLine, 0); - if (CleanOriginal == NULL) { + CHAR16 *NewCmdLine; + NewCmdLine = ShellConvertVariables(*CmdLine); + SHELL_FREE_NON_NULL(*CmdLine); + if (NewCmdLine == NULL) { return (EFI_OUT_OF_RESOURCES); } + *CmdLine = NewCmdLine; + return (EFI_SUCCESS); +} - // - // Remove any spaces at the beginning of the string. - // - while (CleanOriginal[0] == L' ') { - CopyMem(CleanOriginal, CleanOriginal+1, StrSize(CleanOriginal) - sizeof(CleanOriginal[0])); - } +/** + Take the original command line, substitute any alias in the first group of space delimited characters, free + the original string, return the modified copy. - // - // Handle case that passed in command line is just 1 or more " " characters. - // - if (StrLen (CleanOriginal) == 0) { - if (CleanOriginal != NULL) { - FreePool(CleanOriginal); - CleanOriginal = NULL; - } - return (EFI_SUCCESS); - } + @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); - // - // Remove any spaces at the end of the string. - // - while (CleanOriginal[StrLen(CleanOriginal)-1] == L' ') { - CleanOriginal[StrLen(CleanOriginal)-1] = CHAR_NULL; - } CommandName = NULL; - if (StrStr(CleanOriginal, L" ") == NULL){ - StrnCatGrow(&CommandName, NULL, CleanOriginal, 0); + if (StrStr((*CmdLine), L" ") == NULL){ + StrnCatGrow(&CommandName, NULL, (*CmdLine), 0); } else { - StrnCatGrow(&CommandName, NULL, CleanOriginal, StrStr(CleanOriginal, L" ") - CleanOriginal); + StrnCatGrow(&CommandName, NULL, (*CmdLine), StrStr((*CmdLine), L" ") - (*CmdLine)); } - ASSERT(PostAliasCmdLine == NULL); + // + // 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; - PostAliasCmdLine = StrnCatGrow(&PostAliasCmdLine, &PostAliasSize, CommandName, 0); - PostAliasCmdLine = StrnCatGrow(&PostAliasCmdLine, &PostAliasSize, StrStr(CleanOriginal, L" "), 0); - ASSERT_EFI_ERROR(Status); + 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 { - PostAliasCmdLine = StrnCatGrow(&PostAliasCmdLine, NULL, CleanOriginal, 0); + NewCmdLine = StrnCatGrow(&NewCmdLine, NULL, (*CmdLine), 0); } - if (CleanOriginal != NULL) { - FreePool(CleanOriginal); - CleanOriginal = NULL; - } + 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; - if (CommandName != NULL) { - FreePool(CommandName); - CommandName = NULL; - } + return (EFI_SUCCESS); +} + +/** + Takes the Argv[0] part of the command line and determine the meaning of it. - PostVariableCmdLine = ShellConvertVariables(PostAliasCmdLine); + @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; // - // we can now free the modified by alias command line + // test for an internal command. // - if (PostAliasCmdLine != NULL) { - FreePool(PostAliasCmdLine); - PostAliasCmdLine = NULL; - } - - if (PostVariableCmdLine == NULL) { - return (EFI_OUT_OF_RESOURCES); + if (ShellCommandIsCommandOnList(CmdName)) { + return (Internal_Command); } - while (PostVariableCmdLine[StrLen(PostVariableCmdLine)-1] == L' ') { - PostVariableCmdLine[StrLen(PostVariableCmdLine)-1] = CHAR_NULL; - } - while (PostVariableCmdLine[0] == L' ') { - CopyMem(PostVariableCmdLine, PostVariableCmdLine+1, StrSize(PostVariableCmdLine) - sizeof(PostVariableCmdLine[0])); + // + // 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); } // - // We dont do normal processing with a split command line (output from one command input to another) + // Test for a file // - TempLocation3 = NULL; - if (StrStr(PostVariableCmdLine, L"|") != NULL) { - for (TempLocation3 = PostVariableCmdLine ; TempLocation3 != NULL && *TempLocation3 != CHAR_NULL ; TempLocation3++) { - if (*TempLocation3 == L'^' && *(TempLocation3+1) == L'|') { - TempLocation3++; - } else if (*TempLocation3 == L'|') { - break; + 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); } } - } - if (TempLocation3 != NULL && *TempLocation3 != CHAR_NULL) { + // - // are we in an existing split??? + // Was a file, but not a script. we treat this as an application. // - if (!IsListEmpty(&ShellInfoObject.SplitList.Link)) { - Split = (SPLIT_LIST*)GetFirstNode(&ShellInfoObject.SplitList.Link); + 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 (Split == NULL) { - Status = RunSplitCommand(PostVariableCmdLine, NULL, NULL); - } else { - Status = RunSplitCommand(PostVariableCmdLine, Split->SplitStdIn, Split->SplitStdOut); + 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; } - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_SPLIT), ShellInfoObject.HiiHandle, PostVariableCmdLine); + } + + 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) { // - // If this is a mapped drive change handle that... + // This may do nothing if the ? does not indicate help. + // Save all the details for in the API below. // - if (PostVariableCmdLine[(StrLen(PostVariableCmdLine)-1)] == L':' && StrStr(PostVariableCmdLine, L" ") == NULL) { - Status = ShellInfoObject.NewEfiShellProtocol->SetCurDir(NULL, PostVariableCmdLine); - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_MAPPING), ShellInfoObject.HiiHandle, PostVariableCmdLine); - } - FreePool(PostVariableCmdLine); - return (Status); - } + Status = DoHelpUpdate(CmdLine); + } - ///@todo update this section to divide into 3 ways - run internal command, run split (above), and run an external file... - /// We waste a lot of time doing processing like StdIn,StdOut,Argv,Argc for things that are external files... + TrimSpaces(CmdLine); + return (EFI_SUCCESS); +} +/** + Run an internal shell command. - Status = UpdateStdInStdOutStdErr(ShellInfoObject.NewShellParametersProtocol, PostVariableCmdLine, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); - if (EFI_ERROR(Status)) { - if (Status == EFI_NOT_FOUND) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_REDUNDA_REDIR), ShellInfoObject.HiiHandle); - } else { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_REDIR), ShellInfoObject.HiiHandle); - } - } else { - while (PostVariableCmdLine[StrLen(PostVariableCmdLine)-1] == L' ') { - PostVariableCmdLine[StrLen(PostVariableCmdLine)-1] = CHAR_NULL; + 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); } - while (PostVariableCmdLine[0] == L' ') { - CopyMem(PostVariableCmdLine, PostVariableCmdLine+1, StrSize(PostVariableCmdLine) - sizeof(PostVariableCmdLine[0])); + if (ExitStatus != NULL) { + *ExitStatus = CommandReturnedStatus; } // - // get the argc and argv updated for internal commands + // Pass thru the exitcode from the app. // - Status = UpdateArgcArgv(ShellInfoObject.NewShellParametersProtocol, PostVariableCmdLine, &Argv, &Argc); - ASSERT_EFI_ERROR(Status); + 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; + } + } + } - for (Count = 0 ; Count < ShellInfoObject.NewShellParametersProtocol->Argc ; Count++) { - if (StrStr(ShellInfoObject.NewShellParametersProtocol->Argv[Count], L"-?") == ShellInfoObject.NewShellParametersProtocol->Argv[Count] - || (ShellInfoObject.NewShellParametersProtocol->Argv[0][0] == L'?' && ShellInfoObject.NewShellParametersProtocol->Argv[0][1] == CHAR_NULL) - ) { - // - // We need to redo the arguments since a parameter was -? - // move them all down 1 to the end, then up one then replace the first with help - // - FreePool(ShellInfoObject.NewShellParametersProtocol->Argv[Count]); - ShellInfoObject.NewShellParametersProtocol->Argv[Count] = NULL; - for (Count2 = Count ; (Count2 + 1) < ShellInfoObject.NewShellParametersProtocol->Argc ; Count2++) { - ShellInfoObject.NewShellParametersProtocol->Argv[Count2] = ShellInfoObject.NewShellParametersProtocol->Argv[Count2+1]; - } - ShellInfoObject.NewShellParametersProtocol->Argv[Count2] = NULL; - for (Count2 = ShellInfoObject.NewShellParametersProtocol->Argc -1 ; Count2 > 0 ; Count2--) { - ShellInfoObject.NewShellParametersProtocol->Argv[Count2] = ShellInfoObject.NewShellParametersProtocol->Argv[Count2-1]; - } - ShellInfoObject.NewShellParametersProtocol->Argv[0] = NULL; - ShellInfoObject.NewShellParametersProtocol->Argv[0] = StrnCatGrow(&ShellInfoObject.NewShellParametersProtocol->Argv[0], NULL, L"help", 0); - break; + // + // 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); } } // - // command or file? + // Process a relative path and also check in the path environment variable // - if (ShellCommandIsCommandOnList(ShellInfoObject.NewShellParametersProtocol->Argv[0])) { - // - // Run the command (which was converted if it was an alias) - // - if (!EFI_ERROR(Status)) { - Status = ShellCommandRunCommandHandler(ShellInfoObject.NewShellParametersProtocol->Argv[0], &ShellStatus, &LastError); - ASSERT_EFI_ERROR(Status); + if (CommandWithPath == NULL) { + CommandWithPath = ShellFindFilePathEx(FirstParameter, mExecutableExtensions); + } - if (sizeof(EFI_STATUS) == sizeof(UINT64)) { - UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", ShellStatus); - } else { - UnicodeSPrint(LeString, sizeof(LeString), L"0x%x", ShellStatus); - } - DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE);); - if (LastError) { - InternalEfiShellSetEnv(L"lasterror", LeString, TRUE); - } + // + // 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: // - // Pass thru the exitcode from the app. + // Get the device path of the application image // - if (ShellCommandGetExit()) { - Status = ShellStatus; - } else if (ShellStatus != 0 && IsScriptOnlyCommand(ShellInfoObject.NewShellParametersProtocol->Argv[0])) { - Status = EFI_ABORTED; - } - } - } else { - // - // run an external file (or script) - // - if (StrStr(ShellInfoObject.NewShellParametersProtocol->Argv[0], L":") != NULL) { - ASSERT (CommandWithPath == NULL); - if (ShellIsFile(ShellInfoObject.NewShellParametersProtocol->Argv[0]) == EFI_SUCCESS) { - CommandWithPath = StrnCatGrow(&CommandWithPath, NULL, ShellInfoObject.NewShellParametersProtocol->Argv[0], 0); + DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath); + if (DevPath == NULL){ + Status = EFI_OUT_OF_RESOURCES; + break; } - } - if (CommandWithPath == NULL) { - CommandWithPath = ShellFindFilePathEx(ShellInfoObject.NewShellParametersProtocol->Argv[0], mExecutableExtensions); - } - if (CommandWithPath == NULL || ShellIsDirectory(CommandWithPath) == EFI_SUCCESS) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, ShellInfoObject.NewShellParametersProtocol->Argv[0]); - if (sizeof(EFI_STATUS) == sizeof(UINT64)) { - UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", EFI_NOT_FOUND); - } else { - UnicodeSPrint(LeString, sizeof(LeString), L"0x%x", EFI_NOT_FOUND); - } - DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE);); - InternalEfiShellSetEnv(L"lasterror", LeString, TRUE); - } else { // - // Check if it's a NSH (script) file. + // Execute the device path // - TempLocation = CommandWithPath+StrLen(CommandWithPath)-4; - TempLocation2 = mScriptExtension; - if ((StrLen(CommandWithPath) > 4) && (StringNoCaseCompare((VOID*)(&TempLocation), (VOID*)(&TempLocation2)) == 0)) { - Status = RunScriptFile (CommandWithPath); + Status = InternalShellExecuteDevicePath( + &gImageHandle, + DevPath, + CmdLine, + NULL, + NULL, + NULL + ); + + SHELL_FREE_NON_NULL(DevPath); + + if(EFI_ERROR (Status)) { + CalleeExitStatus = (SHELL_STATUS) (Status & (~MAX_BIT)); } else { - DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath); - ASSERT(DevPath != NULL); - Status = InternalShellExecuteDevicePath( - &gImageHandle, - DevPath, - PostVariableCmdLine, - NULL, - &StatusCode - ); - - // - // Update last error status. - // - if (sizeof(EFI_STATUS) == sizeof(UINT64)) { - UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", StatusCode); - } else { - UnicodeSPrint(LeString, sizeof(LeString), L"0x%x", StatusCode); - } - DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE);); - InternalEfiShellSetEnv(L"lasterror", LeString, TRUE); + 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: // - // Print some error info. + // Do nothing. // - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID*)(Status)); - } + break; + } - CommandName = StrnCatGrow(&CommandName, NULL, ShellInfoObject.NewShellParametersProtocol->Argv[0], 0); + SHELL_FREE_NON_NULL(CommandWithPath); - RestoreArgcArgv(ShellInfoObject.NewShellParametersProtocol, &Argv, &Argc); + if (ExitStatus != NULL) { + *ExitStatus = CalleeExitStatus; + } - RestoreStdInStdOutStdErr(ShellInfoObject.NewShellParametersProtocol, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); - } - if (CommandName != NULL) { - if (ShellCommandGetCurrentScriptFile() != NULL && !IsScriptOnlyCommand(CommandName)) { - // - // if this is NOT a scipt only command return success so the script won't quit. - // prevent killing the script - this is the only place where we know the actual command name (after alias and variable replacement...) - // - Status = EFI_SUCCESS; - } - } + 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 + ); } - SHELL_FREE_NON_NULL(CommandName); - SHELL_FREE_NON_NULL(CommandWithPath); - SHELL_FREE_NON_NULL(PostVariableCmdLine); + // + // 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(CleanOriginal); + 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); } @@ -1737,13 +2547,16 @@ IsValidCommandName( @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 + IN SHELL_FILE_HANDLE Handle, + IN CONST CHAR16 *Name, + OUT SHELL_STATUS *ExitStatus ) { EFI_STATUS Status; @@ -1759,8 +2572,11 @@ RunScriptFileHandle ( CONST CHAR16 *CurDir; UINTN LineCount; CHAR16 LeString[50]; + SHELL_STATUS CalleeExitStatus; ASSERT(!ShellCommandGetScriptExit()); + + CalleeExitStatus = SHELL_SUCCESS; PreScriptEchoState = ShellCommandGetEchoState(); @@ -1811,11 +2627,13 @@ RunScriptFileHandle ( while(!ShellFileHandleEof(Handle)) { CommandLine = ShellFileHandleReturnLine(Handle, &Ascii); LineCount++; - if (CommandLine == NULL || StrLen(CommandLine) == 0) { + 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); } @@ -1942,7 +2760,7 @@ RunScriptFileHandle ( // PreCommandEchoState = ShellCommandGetEchoState(); ShellCommandSetEchoState(FALSE); - Status = RunCommand(CommandLine3+1); + Status = RunCommand(CommandLine3+1, NULL); // // If command was "@echo -off" or "@echo -on" then don't restore echo state @@ -1964,7 +2782,7 @@ RunScriptFileHandle ( } ShellPrintEx(-1, -1, L"%s\r\n", CommandLine2); } - Status = RunCommand(CommandLine3); + Status = RunCommand(CommandLine3, NULL); } } @@ -1972,7 +2790,8 @@ RunScriptFileHandle ( // // ShellCommandGetExitCode() always returns a UINT64 // - UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", ShellCommandGetExitCode()); + CalleeExitStatus = (SHELL_STATUS) ShellCommandGetExitCode(); + UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", CalleeExitStatus); DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE);); InternalEfiShellSetEnv(L"lasterror", LeString, TRUE); @@ -1984,9 +2803,11 @@ RunScriptFileHandle ( break; } if (EFI_ERROR(Status)) { + CalleeExitStatus = (SHELL_STATUS) Status; break; } if (ShellCommandGetExit()) { + CalleeExitStatus = (SHELL_STATUS) ShellCommandGetExitCode(); break; } } @@ -2018,6 +2839,11 @@ RunScriptFileHandle ( if (ShellCommandGetCurrentScriptFile()==NULL) { ShellCommandSetEchoState(PreScriptEchoState); } + + if (ExitStatus != NULL) { + *ExitStatus = CalleeExitStatus; + } + return (EFI_SUCCESS); } @@ -2025,30 +2851,65 @@ RunScriptFileHandle ( 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 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); } - Status = ShellOpenFileByName(ScriptPath, &FileHandle, EFI_FILE_MODE_READ, 0); - if (EFI_ERROR(Status)) { - return (Status); - } + // + // get the argc and argv updated for scripts + // + Status = UpdateArgcArgv(ParamProtocol, CmdLine, &Argv, &Argc); + if (!EFI_ERROR(Status)) { - Status = RunScriptFileHandle(FileHandle, ScriptPath); + 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); + } + } - ShellCloseFile(&FileHandle); + // + // 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); }