From: Jaben Carsey Date: Thu, 19 Dec 2013 16:05:34 +0000 (+0000) Subject: ShellPkg: Refactor the RunCommand API X-Git-Tag: edk2-stable201903~11950 X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=commitdiff_plain;h=806c49db0538080ac397892c750b86d1c55d32af ShellPkg: Refactor the RunCommand API This almost completely splits the RunCommand API into sub-routines. - the ProcessCommandLineToFinal API handles replacing the a found alias and any found environment variables. This will redirect "-?" to "help", if necessary. Upon return, the command line is complete and finalized. It may still have redirection in it, and those will get chopped off later (but no further modifications occur). - the SetupAndRunCommandOrFile API handles updating and then later restoring StdIn, StdOut, and StdErr (and removing their information from the command line). It will call into RunCommandOrFile. - the RunCommandOrFile API divides the logic to RunInternalCommand, RunScriptFile, or running an .EFI file directly. - the RunInternalCommand API handles updating and then restoring Argc and Argv. It will run the internal command in between. - the SetLastError API handles updating of the environment variable "lasterror" - the DoHelpUpdateArgcArgv was changed to DoHelpUpdate and now works on the raw command line and not the argc/argv. This allows the processing to be moved earlier. Note this change has the following positive side effects (this eliminates unnecessary step): - Argc/Argv are only updated for internal commands (as they are library based) - no Argv/Argc/StdIn/StdOut/StdErr processing is done for file system changes. - The ProcessCommandLineToFinal API exists and it's critical to the ability to correctly pre-process split ("|") command lines ahead of time to verify their correctness. Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Jaben Carsey Reviewed-by: Erik Bjorge git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@15007 6f19259b-4bc3-4df7-8a09-765794883524 --- diff --git a/ShellPkg/Application/Shell/Shell.c b/ShellPkg/Application/Shell/Shell.c index 53f1bc3059..fe722e9554 100644 --- a/ShellPkg/Application/Shell/Shell.c +++ b/ShellPkg/Application/Shell/Shell.c @@ -1674,50 +1674,353 @@ ChangeMappedDrive( if found, will add "help" as argv[0], and move the rest later. - @param[in,out] Argc The pointer to argc to update - @param[in,out] Argv The pointer to argv to update (this is a pointer to an array of string pointers) + @param[in,out] CmdLine pointer to the command line to update **/ EFI_STATUS EFIAPI -DoHelpUpdateArgcArgv( - IN OUT UINTN *Argc, - IN OUT CHAR16 ***Argv +DoHelpUpdate( + IN OUT CHAR16 **CmdLine ) { - UINTN Count; - UINTN Count2; + 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 UINT64 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 +ProcessCommandLineAliasVariable( + 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); + // - // Check each parameter + // update for help parsing // - for (Count = 0 ; Count < (*Argc) ; Count++) { + if (StrStr(*CmdLine, L"?") != NULL) { // - // if it's "-?" or if the first parameter is "?" + // This may do nothing if the ? does not indicate help. + // Save all the details for in the API below. // - if (StrStr((*Argv)[Count], L"-?") == (*Argv)[Count] - || ((*Argv)[0][0] == L'?' && (*Argv)[0][1] == CHAR_NULL) - ) { + 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 + + @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 +) +{ + 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)) { // - // 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 + // Update last error status. + // some commands do not update last error. // - FreePool((*Argv)[Count]); - (*Argv)[Count] = NULL; - for (Count2 = Count ; (Count2 + 1) < (*Argc) ; Count2++) { - (*Argv)[Count2] = (*Argv)[Count2+1]; + if (LastError) { + SetLastError(CommandReturnedStatus); } - (*Argv)[Count2] = NULL; - for (Count2 = (*Argc) -1 ; Count2 > 0 ; Count2--) { - (*Argv)[Count2] = (*Argv)[Count2-1]; + + // + // Pass thru the exitcode from the app. + // + if (ShellCommandGetExit()) { + Status = CommandReturnedStatus; + } else if (CommandReturnedStatus != 0 && IsScriptOnlyCommand(FirstParameter)) { + Status = EFI_ABORTED; } - (*Argv)[0] = NULL; - (*Argv)[0] = StrnCatGrow(&(*Argv)[0], NULL, L"help", 0); - if ((*Argv)[0] == NULL) { - return (EFI_OUT_OF_RESOURCES); + } + } + + // + // 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 (ShellCommandGetCurrentScriptFile() != NULL && !IsScriptOnlyCommand(FirstParameter)) { + // + // 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 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 + + @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 +) +{ + EFI_STATUS Status; + EFI_STATUS StatusCode; + CHAR16 *CommandWithPath; + EFI_DEVICE_PATH_PROTOCOL *DevPath; + + Status = EFI_SUCCESS; + CommandWithPath = NULL; + DevPath = NULL; + + switch (Type) { + case INTERNAL_COMMAND: + Status = RunInternalCommand(CmdLine, FirstParameter, ParamProtocol); + 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(EFI_NOT_FOUND); + } + switch (Type) { + case SCRIPT_FILE_NAME: + Status = RunScriptFile (CommandWithPath); + 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, + &StatusCode + ); + + SHELL_FREE_NON_NULL(DevPath); + + // + // Update last error status. + // + SetLastError(StatusCode); + break; } break; - } } - return (EFI_SUCCESS); + + SHELL_FREE_NON_NULL(CommandWithPath); + + return (Status); +} + +/** + Function to setup StdIn, StdErr, StdOut, and then run the command or file. + + @param[in] Type the type of operation being run. + @param[in] CmdLine the command line to run. + @param[in] FirstParameter the first parameter on the command line. + @param[in] ParamProtocol the shell parameters protocol pointer + + @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 +) +{ + 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); + } + + // + // 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); } /** @@ -1738,31 +2041,16 @@ RunCommand( ) { EFI_STATUS Status; - EFI_STATUS StatusCode; - CHAR16 *CommandName; - SHELL_STATUS ShellStatus; - UINTN Argc; - CHAR16 **Argv; - BOOLEAN LastError; - CHAR16 LeString[19]; - 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 *CleanOriginal; + CHAR16 *FirstParameter; + CHAR16 *TempWalker; + SHELL_OPERATION_TYPES Type; ASSERT(CmdLine != NULL); if (StrLen(CmdLine) == 0) { return (EFI_SUCCESS); } - CommandName = NULL; - CommandWithPath = NULL; - DevPath = NULL; Status = EFI_SUCCESS; CleanOriginal = NULL; @@ -1777,180 +2065,59 @@ RunCommand( // Handle case that passed in command line is just 1 or more " " characters. // if (StrLen (CleanOriginal) == 0) { - if (CleanOriginal != NULL) { - FreePool(CleanOriginal); - CleanOriginal = NULL; - } + SHELL_FREE_NON_NULL(CleanOriginal); return (EFI_SUCCESS); } - Status = ShellSubstituteAliases(&CleanOriginal); - if (EFI_ERROR(Status)) { - return (Status); - } - - Status = ShellSubstituteVariables(&CleanOriginal); + Status = ProcessCommandLineAliasVariable(&CleanOriginal); if (EFI_ERROR(Status)) { + SHELL_FREE_NON_NULL(CleanOriginal); return (Status); } - TrimSpaces(&CleanOriginal); - // // We dont do normal processing with a split command line (output from one command input to another) // if (ContainsSplit(CleanOriginal)) { Status = ProcessNewSplitCommandLine(CleanOriginal); - } else { - // - // If this is a mapped drive change handle that... - // - if (CleanOriginal[(StrLen(CleanOriginal)-1)] == L':' && StrStr(CleanOriginal, L" ") == NULL) { - Status = ChangeMappedDrive(CleanOriginal); - SHELL_FREE_NON_NULL(CleanOriginal); - 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... - - - - Status = UpdateStdInStdOutStdErr(ShellInfoObject.NewShellParametersProtocol, CleanOriginal, &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 { - TrimSpaces(&CleanOriginal); - - // - // get the argc and argv updated for internal commands - // - Status = UpdateArgcArgv(ShellInfoObject.NewShellParametersProtocol, CleanOriginal, &Argv, &Argc); - ASSERT_EFI_ERROR(Status); - - if (StrStr(CleanOriginal, L"?") != NULL) { - Status = DoHelpUpdateArgcArgv( - &ShellInfoObject.NewShellParametersProtocol->Argc, - &ShellInfoObject.NewShellParametersProtocol->Argv); - } - - // - // 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); - - 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); - } - // - // 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]); - - 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. - // - 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, - CleanOriginal, - NULL, - &StatusCode - ); + SHELL_FREE_NON_NULL(CleanOriginal); + return (Status); + } - // - // 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); - } - } - } + // + // 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); + break; + default: // - // Print some error info. + // Whatever was typed, it was invalid. // - if (EFI_ERROR(Status)) { - ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_ERROR), ShellInfoObject.HiiHandle, (VOID*)(Status)); - } - - CommandName = StrnCatGrow(&CommandName, NULL, ShellInfoObject.NewShellParametersProtocol->Argv[0], 0); - - RestoreArgcArgv(ShellInfoObject.NewShellParametersProtocol, &Argv, &Argc); - - 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; - } - } + ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter); + SetLastError(EFI_NOT_FOUND); + break; } - - SHELL_FREE_NON_NULL(CommandName); - SHELL_FREE_NON_NULL(CommandWithPath); + SHELL_FREE_NON_NULL(CleanOriginal); + SHELL_FREE_NON_NULL(FirstParameter); return (Status); }