X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=ShellPkg%2FApplication%2FShell%2FShell.c;h=dc99c5c974a61323289d4e08a1efb67159e684ef;hp=3e4706c288dbd2cbac12ce28af1c34fbbfb73e10;hb=7bc3ec3d80985b10bf8f4f9c4203ccd8c1cd6e7e;hpb=ce68d3bc68eda5ce919b90aea8db094ed4b64c34 diff --git a/ShellPkg/Application/Shell/Shell.c b/ShellPkg/Application/Shell/Shell.c index 3e4706c288..dc99c5c974 100644 --- a/ShellPkg/Application/Shell/Shell.c +++ b/ShellPkg/Application/Shell/Shell.c @@ -1,7 +1,8 @@ /** @file This is THE shell (application) - Copyright (c) 2009 - 2012, 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 which accompanies this distribution. The full text of the license may be found at @@ -69,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. @@ -130,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)) { @@ -138,7 +215,7 @@ InternalEfiShellStartCtrlSMonitor( SimpleEx, &KeyData, NotificationFunction, - &ShellInfoObject.CtrlSNotifyHandle2); + &ShellInfoObject.CtrlSNotifyHandle4); } return (Status); } @@ -167,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); @@ -296,11 +376,28 @@ UefiMain ( 0, gST->ConOut->Mode->CursorRow, NULL, - STRING_TOKEN (STR_VER_OUTPUT_MAIN), + STRING_TOKEN (STR_VER_OUTPUT_MAIN_SHELL), ShellInfoObject.HiiHandle, SupportLevel[PcdGet8(PcdShellSupportLevel)], gEfiShellProtocol->MajorVersion, - gEfiShellProtocol->MinorVersion, + 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, @@ -312,7 +409,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); } @@ -378,10 +475,14 @@ UefiMain ( // // process the startup script or launch the called app. // - Status = DoStartupScript(ShellInfoObject.ImageDevPath, ShellInfoObject.FileDevPath); + Status = DoStartupScript( + ShellInfoObject.ImageDevPath, + ShellInfoObject.FileDevPath, + &ExitStatus + ); } - if (!ShellCommandGetExit() && (PcdGet8(PcdShellSupportLevel) >= 3 || PcdGetBool(PcdShellForceConsole)) && !EFI_ERROR(Status) && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoConsoleIn) { + 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 // @@ -411,6 +512,7 @@ UefiMain ( // Status = DoShellPrompt(); } while (!ShellCommandGetExit()); + ExitStatus = (SHELL_STATUS) ShellCommandGetExitCode(); } if (OldConIn != NULL && ConInHandle != NULL) { CloseSimpleTextInOnFile (gST->ConIn); @@ -441,7 +543,7 @@ UefiMain ( } if (ShellInfoObject.NewEfiShellProtocol != NULL){ if (ShellInfoObject.NewEfiShellProtocol->IsRootShell()){ - ShellInfoObject.NewEfiShellProtocol->SetEnv(L"cwd", L"", TRUE); + InternalEfiShellSetEnv(L"cwd", NULL, TRUE); } CleanUpShellProtocol(ShellInfoObject.NewEfiShellProtocol); DEBUG_CODE(ShellInfoObject.NewEfiShellProtocol = NULL;); @@ -481,10 +583,29 @@ UefiMain ( DEBUG_CODE(ShellInfoObject.ConsoleInfo = NULL;); } - if (ShellCommandGetExit()) { - return ((EFI_STATUS)ShellCommandGetExitCode()); + // 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); + } else { + return EFI_SUCCESS; } - return (Status); + + ASSERT (FALSE); + return EFI_SUCCESS; } /** @@ -642,6 +763,7 @@ STATIC CONST SHELL_PARAM_ITEM mShellParamList[] = { {L"-noversion", TypeFlag}, {L"-startup", TypeFlag}, {L"-delay", TypeValue}, + {L"-_exit", TypeFlag}, {NULL, TypeMax} }; @@ -749,6 +871,7 @@ ProcessCommandLine( 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; @@ -776,13 +899,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; @@ -817,7 +943,7 @@ DoStartupScript( StrCat(FileStringPath, L" "); StrCat(FileStringPath, ShellInfoObject.ShellInitSettings.FileOptions); } - Status = RunCommand(FileStringPath); + Status = RunCommand(FileStringPath, ExitStatus); FreePool(FileStringPath); return (Status); @@ -837,12 +963,12 @@ DoStartupScript( // // print out our warning and see if they press a key // - for ( Status = EFI_UNSUPPORTED, Delay = ShellInfoObject.ShellInitSettings.Delay * 10 + 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/10); - gBS->Stall (100000); + 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); } @@ -894,7 +1020,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); @@ -905,7 +1037,13 @@ DoStartupScript( Status = EFI_SUCCESS; ASSERT(FileHandle == NULL); } else { - Status = RunScriptFile(FileStringPath); + Status = RunScriptFile( + FileStringPath, + NULL, + L"", + ShellInfoObject.NewShellParametersProtocol, + ExitStatus + ); FreePool(FileStringPath); } } @@ -970,8 +1108,8 @@ DoShellPrompt ( // if (!EFI_ERROR (Status)) { CmdLine[BufferSize / sizeof (CHAR16)] = CHAR_NULL; - Status = RunCommand(CmdLine); - } + Status = RunCommand(CmdLine, NULL); + } // // Done with this command @@ -1083,6 +1221,7 @@ ShellConvertVariables ( CHAR16 *NewCommandLine1; CHAR16 *NewCommandLine2; CHAR16 *Temp; + CHAR16 *Temp2; UINTN ItemSize; CHAR16 *ItemTemp; SCRIPT_FILE *CurrentScriptFile; @@ -1140,15 +1279,6 @@ ShellConvertVariables ( } } - // - // Quick out if none were found... - // - if (NewSize == StrSize(OriginalCommandLine)) { - ASSERT(Temp == NULL); - Temp = StrnCatGrow(&Temp, NULL, OriginalCommandLine, 0); - return (Temp); - } - // // now do the replacements... // @@ -1180,8 +1310,51 @@ ShellConvertVariables ( ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, AliasListNode->Alias, AliasListNode->CommandString, TRUE, FALSE); StrCpy(NewCommandLine1, NewCommandLine2); } + + // + // 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)); + } + } + // + // Now cleanup any straggler intentionally ignored "%" characters + // + ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, L"^%", L"%", TRUE, FALSE); + StrCpy(NewCommandLine1, NewCommandLine2); + FreePool(NewCommandLine2); FreePool(ItemTemp); @@ -1195,6 +1368,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. **/ @@ -1203,7 +1379,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; @@ -1257,7 +1434,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. @@ -1272,7 +1449,7 @@ RunSplitCommand( ShellInfoObject.NewEfiShellProtocol->SetFilePosition(ConvertShellHandleToEfiFileProtocol(Split->SplitStdIn), 0); if (!EFI_ERROR(Status)) { - Status = RunCommand(NextCommandLine); + Status = RunCommand(NextCommandLine, ExitStatus); } // @@ -1299,323 +1476,887 @@ 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[11]; - 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); + CHAR16 *NewCmdLine; + NewCmdLine = ShellConvertVariables(*CmdLine); + SHELL_FREE_NON_NULL(*CmdLine); + if (NewCmdLine == NULL) { + return (EFI_OUT_OF_RESOURCES); } + *CmdLine = NewCmdLine; + return (EFI_SUCCESS); +} - CommandName = NULL; - PostVariableCmdLine = NULL; - PostAliasCmdLine = NULL; - CommandWithPath = NULL; - DevPath = NULL; - Status = EFI_SUCCESS; - CleanOriginal = NULL; - Split = NULL; +/** + Take the original command line, substitute any alias in the first group of space delimited characters, free + the original string, return the modified copy. + + @param[in] CmdLine pointer to the command line to update. + + @retval EFI_SUCCESS the function was successful. + @retval EFI_OUT_OF_RESOURCES a memory allocation failed. +**/ +EFI_STATUS +EFIAPI +ShellSubstituteAliases( + IN CHAR16 **CmdLine + ) +{ + CHAR16 *NewCmdLine; + CHAR16 *CommandName; + EFI_STATUS Status; + UINTN PostAliasSize; + ASSERT(CmdLine != NULL); + ASSERT(*CmdLine!= NULL); - CleanOriginal = StrnCatGrow(&CleanOriginal, NULL, CmdLine, 0); - if (CleanOriginal == NULL) { - return (EFI_OUT_OF_RESOURCES); - } - while (CleanOriginal[StrLen(CleanOriginal)-1] == L' ') { - CleanOriginal[StrLen(CleanOriginal)-1] = CHAR_NULL; - } - while (CleanOriginal[0] == L' ') { - CopyMem(CleanOriginal, CleanOriginal+1, StrSize(CleanOriginal) - sizeof(CleanOriginal[0])); - } 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); - } - - 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])); + if (ShellCommandIsCommandOnList(CmdName)) { + return (Internal_Command); } // - // We dont do normal processing with a split command line (output from one command input to another) + // Test for file system change request. anything ending with : and cant have spaces. // - 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 (CmdName[(StrLen(CmdName)-1)] == L':') { + if (StrStr(CmdName, L" ") != NULL) { + return (Unknown_Invalid); } + return (File_Sys_Change); } - if (TempLocation3 != NULL && *TempLocation3 != CHAR_NULL) { - // - // are we in an existing split??? - // - if (!IsListEmpty(&ShellInfoObject.SplitList.Link)) { - Split = (SPLIT_LIST*)GetFirstNode(&ShellInfoObject.SplitList.Link); - } - - if (Split == NULL) { - Status = RunSplitCommand(PostVariableCmdLine, NULL, NULL); - } else { - Status = RunSplitCommand(PostVariableCmdLine, Split->SplitStdIn, Split->SplitStdOut); - } - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_SPLIT), ShellInfoObject.HiiHandle, PostVariableCmdLine); - } - } else { + // + // Test for a file + // + if ((FileWithPath = ShellFindFilePathEx(CmdName, mExecutableExtensions)) != NULL) { // - // If this is a mapped drive change handle that... + // See if that file has a script file extension // - 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); + 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); } - FreePool(PostVariableCmdLine); - return (Status); } - ///@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... - - + // + // 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); +} - 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; - } - while (PostVariableCmdLine[0] == L' ') { - CopyMem(PostVariableCmdLine, PostVariableCmdLine+1, StrSize(PostVariableCmdLine) - sizeof(PostVariableCmdLine[0])); - } +/** + Determine if the first item in a command line is valid. - // - // get the argc and argv updated for internal commands - // - Status = UpdateArgcArgv(ShellInfoObject.NewShellParametersProtocol, PostVariableCmdLine, &Argv, &Argc); - ASSERT_EFI_ERROR(Status); + @param[in] CmdLine The command line to parse. - 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; - } - } + @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; - // - // command or file? - // - 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); - UnicodeSPrint(LeString, sizeof(LeString)*sizeof(LeString[0]), L"0x%08x", ShellStatus); - DEBUG_CODE(InternalEfiShellSetEnv(L"DebugLasterror", LeString, TRUE);); - if (LastError) { - InternalEfiShellSetEnv(L"Lasterror", LeString, TRUE); - } - // - // Pass thru the exitcode from the app. - // - 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); - } - } - 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]); - } else { - // - // Check if it's a NSH (script) file. - // - TempLocation = CommandWithPath+StrLen(CommandWithPath)-4; - TempLocation2 = mScriptExtension; - if ((StrLen(CommandWithPath) > 4) && (StringNoCaseCompare((VOID*)(&TempLocation), (VOID*)(&TempLocation2)) == 0)) { - Status = RunScriptFile (CommandWithPath); - } else { - DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath); - ASSERT(DevPath != NULL); - Status = InternalShellExecuteDevicePath( - &gImageHandle, - DevPath, - PostVariableCmdLine, - NULL, - &StatusCode - ); + Temp = NULL; - // - // Updatet last error status. - // - UnicodeSPrint(LeString, sizeof(LeString)*sizeof(LeString[0]), L"0x%08x", StatusCode); - DEBUG_CODE(InternalEfiShellSetEnv(L"DebugLasterror", LeString, TRUE);); - InternalEfiShellSetEnv(L"Lasterror", LeString, TRUE); - } - } - } + Temp = StrnCatGrow(&Temp, NULL, CmdLine, 0); + if (Temp == NULL) { + return (EFI_OUT_OF_RESOURCES); + } - // - // Print some error info. - // - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID*)(Status)); - } + FirstParameter = StrStr(Temp, L"|"); + if (FirstParameter != NULL) { + *FirstParameter = CHAR_NULL; + } - CommandName = StrnCatGrow(&CommandName, NULL, ShellInfoObject.NewShellParametersProtocol->Argv[0], 0); + FirstParameter = NULL; - RestoreArgcArgv(ShellInfoObject.NewShellParametersProtocol, &Argv, &Argc); + // + // Process the command line + // + Status = ProcessCommandLineToFinal(&Temp); - RestoreStdInStdOutStdErr(ShellInfoObject.NewShellParametersProtocol, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); + if (!EFI_ERROR(Status)) { + FirstParameter = AllocateZeroPool(StrSize(CmdLine)); + if (FirstParameter == NULL) { + SHELL_FREE_NON_NULL(Temp); + return (EFI_OUT_OF_RESOURCES); } - 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; - } + TempWalker = (CHAR16*)Temp; + GetNextParameter(&TempWalker, &FirstParameter); + + if (GetOperationType(FirstParameter) == Unknown_Invalid) { + ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); + SetLastError(SHELL_NOT_FOUND); + Status = EFI_NOT_FOUND; } } - SHELL_FREE_NON_NULL(CommandName); - SHELL_FREE_NON_NULL(CommandWithPath); - SHELL_FREE_NON_NULL(PostVariableCmdLine); - - return (Status); + SHELL_FREE_NON_NULL(Temp); + SHELL_FREE_NON_NULL(FirstParameter); + return Status; } -STATIC CONST UINT16 InvalidChars[] = {L'*', L'?', L'<', L'>', L'\\', L'/', L'\"', 0x0001, 0x0002}; /** - Function determins if the CommandName COULD be a valid command. It does not determine whether - this is a valid command. It only checks for invalid characters. + Determine if a command line contains with a split contains only valid commands. - @param[in] CommandName The name to check + @param[in] CmdLine The command line to parse. - @retval TRUE CommandName could be a command name + @retval EFI_SUCCESS CmdLine has only valid commands, application, or has no split. + @retval EFI_ABORTED CmdLine has at least one invalid command or application. +**/ +EFI_STATUS +EFIAPI +VerifySplit( + IN CONST CHAR16 *CmdLine + ) +{ + CONST CHAR16 *TempSpot; + EFI_STATUS Status; + + // + // Verify up to the pipe or end character + // + Status = IsValidSplit(CmdLine); + if (EFI_ERROR(Status)) { + return (Status); + } + + // + // If this was the only item, then get out + // + if (!ContainsSplit(CmdLine)) { + return (EFI_SUCCESS); + } + + // + // recurse to verify the next item + // + TempSpot = FindSplit(CmdLine)+1; + return (VerifySplit(TempSpot)); +} + +/** + Process a split based operation. + + @param[in] CmdLine Pointer to the command line to process + @param[out] ExitStatus The exit status of the command. Ignored if NULL. + Invalid if this function returns an error. + + @retval EFI_SUCCESS The operation was successful + @return an error occured. +**/ +EFI_STATUS +EFIAPI +ProcessNewSplitCommandLine( + IN CONST CHAR16 *CmdLine, + OUT SHELL_STATUS *ExitStatus + ) +{ + SPLIT_LIST *Split; + EFI_STATUS Status; + + Status = VerifySplit(CmdLine); + if (EFI_ERROR(Status)) { + return (Status); + } + + Split = NULL; + + // + // are we in an existing split??? + // + if (!IsListEmpty(&ShellInfoObject.SplitList.Link)) { + Split = (SPLIT_LIST*)GetFirstNode(&ShellInfoObject.SplitList.Link); + } + + if (Split == NULL) { + Status = RunSplitCommand(CmdLine, NULL, NULL, ExitStatus); + } else { + Status = RunSplitCommand( + CmdLine, + Split->SplitStdIn, + Split->SplitStdOut, + ExitStatus + ); + } + if (EFI_ERROR(Status)) { + ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_SPLIT), ShellInfoObject.HiiHandle, CmdLine); + } + return (Status); +} + +/** + Handle a request to change the current file system. + + @param[in] CmdLine The passed in command line. + + @retval EFI_SUCCESS The operation was successful. +**/ +EFI_STATUS +EFIAPI +ChangeMappedDrive( + IN CONST CHAR16 *CmdLine + ) +{ + EFI_STATUS Status; + Status = EFI_SUCCESS; + + // + // make sure we are the right operation + // + ASSERT(CmdLine[(StrLen(CmdLine)-1)] == L':' && StrStr(CmdLine, L" ") == NULL); + + // + // Call the protocol API to do the work + // + Status = ShellInfoObject.NewEfiShellProtocol->SetCurDir(NULL, CmdLine); + + // + // Report any errors + // + if (EFI_ERROR(Status)) { + ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_INVALID_MAPPING), ShellInfoObject.HiiHandle, CmdLine); + } + + return (Status); +} + +/** + Reprocess the command line to direct all -? to the help command. + + if found, will add "help" as argv[0], and move the rest later. + + @param[in,out] CmdLine pointer to the command line to update +**/ +EFI_STATUS +EFIAPI +DoHelpUpdate( + IN OUT CHAR16 **CmdLine + ) +{ + CHAR16 *CurrentParameter; + CHAR16 *Walker; + CHAR16 *LastWalker; + CHAR16 *NewCommandLine; + EFI_STATUS Status; + + Status = EFI_SUCCESS; + + CurrentParameter = AllocateZeroPool(StrSize(*CmdLine)); + if (CurrentParameter == NULL) { + return (EFI_OUT_OF_RESOURCES); + } + + Walker = *CmdLine; + while(Walker != NULL && *Walker != CHAR_NULL) { + LastWalker = Walker; + GetNextParameter(&Walker, &CurrentParameter); + if (StrStr(CurrentParameter, L"-?") == CurrentParameter) { + LastWalker[0] = L' '; + LastWalker[1] = L' '; + NewCommandLine = AllocateZeroPool(StrSize(L"help ") + StrSize(*CmdLine)); + if (NewCommandLine == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + StrCpy(NewCommandLine, L"help "); + StrCat(NewCommandLine, *CmdLine); + SHELL_FREE_NON_NULL(*CmdLine); + *CmdLine = NewCommandLine; + break; + } + } + + SHELL_FREE_NON_NULL(CurrentParameter); + + return (Status); +} + +/** + Function to update the shell variable "lasterror". + + @param[in] ErrorCode the error code to put into lasterror. +**/ +EFI_STATUS +EFIAPI +SetLastError( + IN CONST SHELL_STATUS ErrorCode + ) +{ + CHAR16 LeString[19]; + if (sizeof(EFI_STATUS) == sizeof(UINT64)) { + UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", ErrorCode); + } else { + UnicodeSPrint(LeString, sizeof(LeString), L"0x%x", ErrorCode); + } + DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE);); + InternalEfiShellSetEnv(L"lasterror", LeString, TRUE); + + return (EFI_SUCCESS); +} + +/** + Converts the command line to it's post-processed form. this replaces variables and alias' per UEFI Shell spec. + + @param[in,out] CmdLine pointer to the command line to update + + @retval EFI_SUCCESS The operation was successful + @retval EFI_OUT_OF_RESOURCES A memory allocation failed. + @return some other error occured +**/ +EFI_STATUS +EFIAPI +ProcessCommandLineToFinal( + IN OUT CHAR16 **CmdLine + ) +{ + EFI_STATUS Status; + TrimSpaces(CmdLine); + + Status = ShellSubstituteAliases(CmdLine); + if (EFI_ERROR(Status)) { + return (Status); + } + + TrimSpaces(CmdLine); + + Status = ShellSubstituteVariables(CmdLine); + if (EFI_ERROR(Status)) { + return (Status); + } + + TrimSpaces(CmdLine); + + // + // update for help parsing + // + if (StrStr(*CmdLine, L"?") != NULL) { + // + // This may do nothing if the ? does not indicate help. + // Save all the details for in the API below. + // + Status = DoHelpUpdate(CmdLine); + } + + TrimSpaces(CmdLine); + + return (EFI_SUCCESS); +} + +/** + Run an internal shell command. + + This API will upadate the shell's environment since these commands are libraries. + + @param[in] CmdLine the command line to run. + @param[in] FirstParameter the first parameter on the command line + @param[in] ParamProtocol the shell parameters protocol pointer + + @param[out] ExitStatus The exit code of the command. Ignored if NULL. + + @retval EFI_SUCCESS The command was completed. + @retval EFI_ABORTED The command's operation was aborted. +**/ +EFI_STATUS +EFIAPI +RunInternalCommand( + IN CONST CHAR16 *CmdLine, + IN CHAR16 *FirstParameter, + IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol, + OUT SHELL_STATUS *ExitStatus OPTIONAL +) +{ + EFI_STATUS Status; + UINTN Argc; + CHAR16 **Argv; + SHELL_STATUS CommandReturnedStatus; + BOOLEAN LastError; + + // + // get the argc and argv updated for internal commands + // + Status = UpdateArgcArgv(ParamProtocol, CmdLine, &Argv, &Argc); + if (!EFI_ERROR(Status)) { + // + // Run the internal command. + // + Status = ShellCommandRunCommandHandler(FirstParameter, &CommandReturnedStatus, &LastError); + + if (!EFI_ERROR(Status)) { + // + // Update last error status. + // some commands do not update last error. + // + if (LastError) { + SetLastError(CommandReturnedStatus); + } + if (ExitStatus != NULL) { + *ExitStatus = CommandReturnedStatus; + } + + // + // Pass thru the exitcode from the app. + // + if (ShellCommandGetExit()) { + // + // An Exit was requested ("exit" command), pass its value up. + // + Status = CommandReturnedStatus; + } else if (CommandReturnedStatus != SHELL_SUCCESS && IsScriptOnlyCommand(FirstParameter)) { + // + // Always abort when a script only command fails for any reason + // + Status = EFI_ABORTED; + } else if (ShellCommandGetCurrentScriptFile() != NULL && CommandReturnedStatus == SHELL_ABORTED) { + // + // Abort when in a script and a command aborted + // + Status = EFI_ABORTED; + } + } + } + + // + // This is guarenteed to be called after UpdateArgcArgv no matter what else happened. + // This is safe even if the update API failed. In this case, it may be a no-op. + // + RestoreArgcArgv(ParamProtocol, &Argv, &Argc); + + // + // If a script is running and the command is not a scipt only command, then + // change return value to success so the script won't halt (unless aborted). + // + // Script only commands have to be able halt the script since the script will + // not operate if they are failing. + // + if ( ShellCommandGetCurrentScriptFile() != NULL + && !IsScriptOnlyCommand(FirstParameter) + && Status != EFI_ABORTED + ) { + Status = EFI_SUCCESS; + } + + return (Status); +} + +/** + Function to run the command or file. + + @param[in] Type the type of operation being run. + @param[in] CmdLine the command line to run. + @param[in] FirstParameter the first parameter on the command line + @param[in] ParamProtocol the shell parameters protocol pointer + + @param[out] ExitStatus The exit code of the command or file. + Ignored if NULL. + + @retval EFI_SUCCESS The command was completed. + @retval EFI_ABORTED The command's operation was aborted. +**/ +EFI_STATUS +EFIAPI +RunCommandOrFile( + IN SHELL_OPERATION_TYPES Type, + IN CONST CHAR16 *CmdLine, + IN CHAR16 *FirstParameter, + IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol, + OUT SHELL_STATUS *ExitStatus +) +{ + EFI_STATUS Status; + CHAR16 *CommandWithPath; + EFI_DEVICE_PATH_PROTOCOL *DevPath; + SHELL_STATUS CalleeExitStatus; + + Status = EFI_SUCCESS; + CommandWithPath = NULL; + DevPath = NULL; + + switch (Type) { + case Internal_Command: + Status = RunInternalCommand( + CmdLine, + FirstParameter, + ParamProtocol, + &CalleeExitStatus + ); + break; + case Script_File_Name: + case Efi_Application: + // + // Process a fully qualified path + // + if (StrStr(FirstParameter, L":") != NULL) { + ASSERT (CommandWithPath == NULL); + if (ShellIsFile(FirstParameter) == EFI_SUCCESS) { + CommandWithPath = StrnCatGrow(&CommandWithPath, NULL, FirstParameter, 0); + } + } + + // + // Process a relative path and also check in the path environment variable + // + if (CommandWithPath == NULL) { + CommandWithPath = ShellFindFilePathEx(FirstParameter, mExecutableExtensions); + } + + // + // This should be impossible now. + // + ASSERT(CommandWithPath != NULL); + + // + // Make sure that path is not just a directory (or not found) + // + if (!EFI_ERROR(ShellIsDirectory(CommandWithPath))) { + ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); + SetLastError(SHELL_NOT_FOUND); + } + switch (Type) { + case Script_File_Name: + Status = RunScriptFile ( + CommandWithPath, + NULL, + CmdLine, + ParamProtocol, + &CalleeExitStatus + ); + break; + case Efi_Application: + // + // Get the device path of the application image + // + DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath); + if (DevPath == NULL){ + Status = EFI_OUT_OF_RESOURCES; + break; + } + + // + // Execute the device path + // + Status = InternalShellExecuteDevicePath( + &gImageHandle, + DevPath, + CmdLine, + NULL, + NULL, + NULL + ); + + SHELL_FREE_NON_NULL(DevPath); + + // + // Update last error status. + // + // Status is an EFI_STATUS. Clear top bit to convert to SHELL_STATUS + SetLastError((SHELL_STATUS) (Status & (~MAX_BIT))); + break; + default: + // + // Do nothing. + // + break; + } + break; + default: + // + // Do nothing. + // + break; + } + + SHELL_FREE_NON_NULL(CommandWithPath); + + if (ExitStatus != NULL) { + *ExitStatus = CalleeExitStatus; + } + + return (Status); +} + +/** + Function to setup StdIn, StdErr, StdOut, and then run the command or file. + + @param[in] Type the type of operation being run. + @param[in] CmdLine the command line to run. + @param[in] FirstParameter the first parameter on the command line. + @param[in] ParamProtocol the shell parameters protocol pointer + + @param[out] ExitStatus The exit code of the command or file. + Ignored if NULL. + + @retval EFI_SUCCESS The command was completed. + @retval EFI_ABORTED The command's operation was aborted. +**/ +EFI_STATUS +EFIAPI +SetupAndRunCommandOrFile( + IN SHELL_OPERATION_TYPES Type, + IN CHAR16 *CmdLine, + IN CHAR16 *FirstParameter, + IN EFI_SHELL_PARAMETERS_PROTOCOL *ParamProtocol, + OUT SHELL_STATUS *ExitStatus +) +{ + EFI_STATUS Status; + SHELL_FILE_HANDLE OriginalStdIn; + SHELL_FILE_HANDLE OriginalStdOut; + SHELL_FILE_HANDLE OriginalStdErr; + SYSTEM_TABLE_INFO OriginalSystemTableInfo; + + // + // Update the StdIn, StdOut, and StdErr for redirection to environment variables, files, etc... unicode and ASCII + // + Status = UpdateStdInStdOutStdErr(ParamProtocol, CmdLine, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); + + // + // The StdIn, StdOut, and StdErr are set up. + // Now run the command, script, or application + // + if (!EFI_ERROR(Status)) { + Status = RunCommandOrFile( + Type, + CmdLine, + FirstParameter, + ParamProtocol, + ExitStatus + ); + } + + // + // Now print errors + // + if (EFI_ERROR(Status)) { + ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID*)(Status)); + } + + // + // put back the original StdIn, StdOut, and StdErr + // + RestoreStdInStdOutStdErr(ParamProtocol, &OriginalStdIn, &OriginalStdOut, &OriginalStdErr, &OriginalSystemTableInfo); + + return (Status); +} + +/** + Function will process and run a command line. + + This will determine if the command line represents an internal shell + command or dispatch an external application. + + @param[in] CmdLine The command line to parse. + @param[out] ExitStatus The exit code of the command. Ignored if NULL. + + @retval EFI_SUCCESS The command was completed. + @retval EFI_ABORTED The command's operation was aborted. +**/ +EFI_STATUS +EFIAPI +RunCommand( + IN CONST CHAR16 *CmdLine, + OUT SHELL_STATUS *ExitStatus + ) +{ + EFI_STATUS Status; + CHAR16 *CleanOriginal; + CHAR16 *FirstParameter; + CHAR16 *TempWalker; + SHELL_OPERATION_TYPES Type; + + ASSERT(CmdLine != NULL); + if (StrLen(CmdLine) == 0) { + return (EFI_SUCCESS); + } + + Status = EFI_SUCCESS; + CleanOriginal = NULL; + + CleanOriginal = StrnCatGrow(&CleanOriginal, NULL, CmdLine, 0); + if (CleanOriginal == NULL) { + return (EFI_OUT_OF_RESOURCES); + } + + TrimSpaces(&CleanOriginal); + + // + // Handle case that passed in command line is just 1 or more " " characters. + // + if (StrLen (CleanOriginal) == 0) { + SHELL_FREE_NON_NULL(CleanOriginal); + return (EFI_SUCCESS); + } + + Status = ProcessCommandLineToFinal(&CleanOriginal); + if (EFI_ERROR(Status)) { + SHELL_FREE_NON_NULL(CleanOriginal); + return (Status); + } + + // + // We dont do normal processing with a split command line (output from one command input to another) + // + if (ContainsSplit(CleanOriginal)) { + Status = ProcessNewSplitCommandLine(CleanOriginal, ExitStatus); + SHELL_FREE_NON_NULL(CleanOriginal); + return (Status); + } + + // + // We need the first parameter information so we can determine the operation type + // + FirstParameter = AllocateZeroPool(StrSize(CleanOriginal)); + if (FirstParameter == NULL) { + SHELL_FREE_NON_NULL(CleanOriginal); + return (EFI_OUT_OF_RESOURCES); + } + TempWalker = CleanOriginal; + GetNextParameter(&TempWalker, &FirstParameter); + + // + // Depending on the first parameter we change the behavior + // + switch (Type = GetOperationType(FirstParameter)) { + case File_Sys_Change: + Status = ChangeMappedDrive(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); +} + +STATIC CONST UINT16 InvalidChars[] = {L'*', L'?', L'<', L'>', L'\\', L'/', L'\"', 0x0001, 0x0002}; +/** + Function determins if the CommandName COULD be a valid command. It does not determine whether + this is a valid command. It only checks for invalid characters. + + @param[in] CommandName The name to check + + @retval TRUE CommandName could be a command name @retval FALSE CommandName could not be a valid command name **/ BOOLEAN @@ -1646,13 +2387,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; @@ -1668,8 +2412,11 @@ RunScriptFileHandle ( CONST CHAR16 *CurDir; UINTN LineCount; CHAR16 LeString[50]; + SHELL_STATUS CalleeExitStatus; ASSERT(!ShellCommandGetScriptExit()); + + CalleeExitStatus = SHELL_SUCCESS; PreScriptEchoState = ShellCommandGetEchoState(); @@ -1720,11 +2467,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); } @@ -1851,12 +2600,18 @@ RunScriptFileHandle ( // PreCommandEchoState = ShellCommandGetEchoState(); ShellCommandSetEchoState(FALSE); - Status = RunCommand(CommandLine3+1); + Status = RunCommand(CommandLine3+1, NULL); // - // Now restore the pre-'@' echo state. + // If command was "@echo -off" or "@echo -on" then don't restore echo state // - ShellCommandSetEchoState(PreCommandEchoState); + 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"); @@ -1867,14 +2622,18 @@ RunScriptFileHandle ( } ShellPrintEx(-1, -1, L"%s\r\n", CommandLine2); } - Status = RunCommand(CommandLine3); + Status = RunCommand(CommandLine3, NULL); } } if (ShellCommandGetScriptExit()) { - UnicodeSPrint(LeString, sizeof(LeString)*sizeof(LeString[0]), L"0x%Lx", ShellCommandGetExitCode()); - DEBUG_CODE(InternalEfiShellSetEnv(L"DebugLasterror", LeString, TRUE);); - InternalEfiShellSetEnv(L"Lasterror", LeString, TRUE); + // + // ShellCommandGetExitCode() always returns a UINT64 + // + CalleeExitStatus = (SHELL_STATUS) ShellCommandGetExitCode(); + UnicodeSPrint(LeString, sizeof(LeString), L"0x%Lx", CalleeExitStatus); + DEBUG_CODE(InternalEfiShellSetEnv(L"debuglasterror", LeString, TRUE);); + InternalEfiShellSetEnv(L"lasterror", LeString, TRUE); ShellCommandRegisterExit(FALSE, 0); Status = EFI_SUCCESS; @@ -1884,9 +2643,11 @@ RunScriptFileHandle ( break; } if (EFI_ERROR(Status)) { + CalleeExitStatus = (SHELL_STATUS) Status; break; } if (ShellCommandGetExit()) { + CalleeExitStatus = (SHELL_STATUS) ShellCommandGetExitCode(); break; } } @@ -1918,6 +2679,11 @@ RunScriptFileHandle ( if (ShellCommandGetCurrentScriptFile()==NULL) { ShellCommandSetEchoState(PreScriptEchoState); } + + if (ExitStatus != NULL) { + *ExitStatus = CalleeExitStatus; + } + return (EFI_SUCCESS); } @@ -1925,30 +2691,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); - ShellCloseFile(&FileHandle); + // + // now close the file + // + ShellCloseFile(&FileHandle); + } + } else { + Status = RunScriptFileHandle(Handle, ScriptPath, ExitStatus); + } + } + + // + // This is guarenteed to be called after UpdateArgcArgv no matter what else happened. + // This is safe even if the update API failed. In this case, it may be a no-op. + // + RestoreArgcArgv(ParamProtocol, &Argv, &Argc); return (Status); }