]> git.proxmox.com Git - mirror_edk2.git/blobdiff - ShellPkg/Application/Shell/Shell.c
ShellPkg: Refactor string manipulation
[mirror_edk2.git] / ShellPkg / Application / Shell / Shell.c
index 2d5c89aadfe9e64a02e0e4b3559a2428fb02273a..976c314cd3d4277c40b20c4e41b992c656b64aa8 100644 (file)
@@ -2,7 +2,7 @@
   This is THE shell (application)\r
 \r
   Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.<BR>\r
-  Copyright (c) 2013, Hewlett-Packard Development Company, L.P.\r
+  (C) Copyright 2013-2014, Hewlett-Packard Development Company, L.P.\r
   This program and the accompanying materials\r
   are licensed and made available under the terms and conditions of the BSD License\r
   which accompanies this distribution.  The full text of the license may be found at\r
@@ -509,6 +509,7 @@ UefiMain (
           // Reset page break back to default.\r
           //\r
           ShellInfoObject.PageBreakEnabled        = PcdGetBool(PcdShellPageBreakDefault);\r
+          ASSERT (ShellInfoObject.ConsoleInfo != NULL);\r
           ShellInfoObject.ConsoleInfo->Enabled    = TRUE;\r
           ShellInfoObject.ConsoleInfo->RowCounter = 0;\r
 \r
@@ -835,9 +836,13 @@ ProcessCommandLine(
   ShellInfoObject.ShellInitSettings.BitUnion.Bits.Exit         = FALSE;\r
   ShellInfoObject.ShellInitSettings.Delay = 5;\r
 \r
-  // Start LoopVar at 1 to ignore Argv[0] which is the name of this binary\r
-  // (probably "Shell.efi")\r
-  for (LoopVar = 1 ; LoopVar < gEfiShellParametersProtocol->Argc ; LoopVar++) {\r
+  //\r
+  // Start LoopVar at 0 to parse only optional arguments at Argv[0]\r
+  // and parse other parameters from Argv[1].  This is for use case that\r
+  // UEFI Shell boot option is created, and OptionalData is provided\r
+  // that starts with shell command-line options.\r
+  //\r
+  for (LoopVar = 0 ; LoopVar < gEfiShellParametersProtocol->Argc ; LoopVar++) {\r
     CurrentArg = gEfiShellParametersProtocol->Argv[LoopVar];\r
     if (UnicodeCollation->StriColl (\r
                             UnicodeCollation,\r
@@ -925,7 +930,14 @@ ProcessCommandLine(
         );\r
       return EFI_INVALID_PARAMETER;\r
     } else {\r
-      ShellInfoObject.ShellInitSettings.FileName = AllocateZeroPool(StrSize(CurrentArg));\r
+      //\r
+      // First argument should be Shell.efi image name\r
+      //\r
+      if (LoopVar == 0) {\r
+        continue;\r
+      }\r
+\r
+      ShellInfoObject.ShellInitSettings.FileName = AllocateCopyPool(StrSize(CurrentArg), CurrentArg);\r
       if (ShellInfoObject.ShellInitSettings.FileName == NULL) {\r
         return (EFI_OUT_OF_RESOURCES);\r
       }\r
@@ -933,8 +945,6 @@ ProcessCommandLine(
       // We found `file-name`.\r
       //\r
       ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoStartup = 1;\r
-\r
-      StrCpy (ShellInfoObject.ShellInitSettings.FileName, CurrentArg);\r
       LoopVar++;\r
 \r
       // Add `file-name-options`\r
@@ -1015,10 +1025,10 @@ DoStartupScript(
     if (FileStringPath == NULL) {\r
       return (EFI_OUT_OF_RESOURCES);\r
     }\r
-    StrCpy(FileStringPath, ShellInfoObject.ShellInitSettings.FileName);\r
+    StrnCpy(FileStringPath, ShellInfoObject.ShellInitSettings.FileName, NewSize/sizeof(CHAR16) -1);\r
     if (ShellInfoObject.ShellInitSettings.FileOptions != NULL) {\r
-      StrCat(FileStringPath, L" ");\r
-      StrCat(FileStringPath, ShellInfoObject.ShellInitSettings.FileOptions);\r
+      StrnCat(FileStringPath, L" ", NewSize/sizeof(CHAR16) - StrLen(FileStringPath) -1);\r
+      StrnCat(FileStringPath, ShellInfoObject.ShellInitSettings.FileOptions, NewSize/sizeof(CHAR16) - StrLen(FileStringPath) -1);\r
     }\r
     Status = RunCommand(FileStringPath, ExitStatus);\r
     FreePool(FileStringPath);\r
@@ -1235,9 +1245,8 @@ AddLineToCommandHistory(
 \r
   Node = AllocateZeroPool(sizeof(BUFFER_LIST));\r
   ASSERT(Node != NULL);\r
-  Node->Buffer = AllocateZeroPool(StrSize(Buffer));\r
+  Node->Buffer = AllocateCopyPool(StrSize(Buffer), Buffer);\r
   ASSERT(Node->Buffer != NULL);\r
-  StrCpy(Node->Buffer, Buffer);\r
 \r
   InsertTailList(&ShellInfoObject.ViewingSettings.CommandHistory.Link, &Node->Link);\r
 }\r
@@ -1268,11 +1277,117 @@ ShellConvertAlias(
     return (EFI_SUCCESS);\r
   }\r
   FreePool(*CommandString);\r
-  *CommandString = AllocateZeroPool(StrSize(NewString));\r
+  *CommandString = AllocateCopyPool(StrSize(NewString), NewString);\r
   if (*CommandString == NULL) {\r
     return (EFI_OUT_OF_RESOURCES);\r
   }\r
-  StrCpy(*CommandString, NewString);\r
+  return (EFI_SUCCESS);\r
+}\r
+\r
+/**\r
+  Parse for the next instance of one string within another string. Can optionally make sure that \r
+  the string was not escaped (^ character) per the shell specification.\r
+\r
+  @param[in] SourceString             The string to search within\r
+  @param[in] FindString               The string to look for\r
+  @param[in] CheckForEscapeCharacter  TRUE to skip escaped instances of FinfString, otherwise will return even escaped instances\r
+**/\r
+CHAR16*\r
+EFIAPI\r
+FindNextInstance(\r
+  IN CONST CHAR16   *SourceString,\r
+  IN CONST CHAR16   *FindString,\r
+  IN CONST BOOLEAN  CheckForEscapeCharacter\r
+  )\r
+{\r
+  CHAR16 *Temp;\r
+  if (SourceString == NULL) {\r
+    return (NULL);\r
+  }\r
+  Temp = StrStr(SourceString, FindString);\r
+\r
+  //\r
+  // If nothing found, or we dont care about escape characters\r
+  //\r
+  if (Temp == NULL || !CheckForEscapeCharacter) {\r
+    return (Temp);\r
+  }\r
+\r
+  //\r
+  // If we found an escaped character, try again on the remainder of the string\r
+  //\r
+  if ((Temp > (SourceString)) && *(Temp-1) == L'^') {\r
+    return FindNextInstance(Temp+1, FindString, CheckForEscapeCharacter);\r
+  }\r
+\r
+  //\r
+  // we found the right character\r
+  //\r
+  return (Temp);\r
+}\r
+\r
+/**\r
+  This function will eliminate unreplaced (and therefore non-found) environment variables.\r
+\r
+  @param[in,out] CmdLine   The command line to update.\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+StripUnreplacedEnvironmentVariables(\r
+  IN OUT CHAR16 *CmdLine\r
+  )\r
+{\r
+  CHAR16 *FirstPercent;\r
+  CHAR16 *FirstQuote;\r
+  CHAR16 *SecondPercent;\r
+  CHAR16 *SecondQuote;\r
+  CHAR16 *CurrentLocator;\r
+\r
+  for (CurrentLocator = CmdLine ; CurrentLocator != NULL ; ) {\r
+    FirstQuote = FindNextInstance(CurrentLocator, L"\"", TRUE);\r
+    FirstPercent = FindNextInstance(CurrentLocator, L"%", TRUE);\r
+    SecondPercent = FirstPercent!=NULL?FindNextInstance(FirstPercent+1, L"%", TRUE):NULL;\r
+    if (FirstPercent == NULL || SecondPercent == NULL) {\r
+      //\r
+      // If we ever dont have 2 % we are done.\r
+      //\r
+      break;\r
+    }\r
+\r
+    if (FirstQuote < FirstPercent) {\r
+      SecondQuote = FirstQuote!= NULL?FindNextInstance(FirstQuote+1, L"\"", TRUE):NULL;\r
+      //\r
+      // Quote is first found\r
+      //\r
+\r
+      if (SecondQuote < FirstPercent) {\r
+        //\r
+        // restart after the pair of "\r
+        //\r
+        CurrentLocator = SecondQuote + 1;\r
+      } else /* FirstPercent < SecondQuote */{\r
+        //\r
+        // Restart on the first percent\r
+        //\r
+        CurrentLocator = FirstPercent;\r
+      }\r
+      continue;\r
+    }\r
+    ASSERT(FirstPercent < FirstQuote);\r
+    if (SecondPercent < FirstQuote) {\r
+      FirstPercent[0] = L'\"';\r
+      SecondPercent[0] = L'\"';\r
+\r
+      //\r
+      // We need to remove from FirstPercent to SecondPercent\r
+      //\r
+      CopyMem(FirstPercent + 1, SecondPercent, StrSize(SecondPercent));\r
+      CurrentLocator = FirstPercent + 2;\r
+      continue;\r
+    }\r
+    ASSERT(FirstQuote < SecondPercent);\r
+    CurrentLocator = FirstQuote;\r
+  }\r
   return (EFI_SUCCESS);\r
 }\r
 \r
@@ -1298,7 +1413,6 @@ ShellConvertVariables (
   CHAR16              *NewCommandLine1;\r
   CHAR16              *NewCommandLine2;\r
   CHAR16              *Temp;\r
-  CHAR16              *Temp2;\r
   UINTN               ItemSize;\r
   CHAR16              *ItemTemp;\r
   SCRIPT_FILE         *CurrentScriptFile;\r
@@ -1359,7 +1473,7 @@ ShellConvertVariables (
   //\r
   // now do the replacements...\r
   //\r
-  NewCommandLine1 = AllocateZeroPool(NewSize);\r
+  NewCommandLine1 = AllocateCopyPool(NewSize, OriginalCommandLine);\r
   NewCommandLine2 = AllocateZeroPool(NewSize);\r
   ItemTemp        = AllocateZeroPool(ItemSize+(2*sizeof(CHAR16)));\r
   if (NewCommandLine1 == NULL || NewCommandLine2 == NULL || ItemTemp == NULL) {\r
@@ -1368,16 +1482,15 @@ ShellConvertVariables (
     SHELL_FREE_NON_NULL(ItemTemp);\r
     return (NULL);\r
   }\r
-  StrCpy(NewCommandLine1, OriginalCommandLine);\r
   for (MasterEnvList = EfiShellGetEnv(NULL)\r
-    ;  MasterEnvList != NULL && *MasterEnvList != CHAR_NULL //&& *(MasterEnvList+1) != CHAR_NULL\r
+    ;  MasterEnvList != NULL && *MasterEnvList != CHAR_NULL\r
     ;  MasterEnvList += StrLen(MasterEnvList) + 1\r
    ){\r
-    StrCpy(ItemTemp, L"%");\r
-    StrCat(ItemTemp, MasterEnvList);\r
-    StrCat(ItemTemp, L"%");\r
+    *ItemTemp = L'%';\r
+    StrnCat(ItemTemp, MasterEnvList, ((ItemSize+(2*sizeof(CHAR16)))/sizeof(CHAR16))-1 - StrLen(ItemTemp));\r
+    StrnCat(ItemTemp, L"%", ((ItemSize+(2*sizeof(CHAR16)))/sizeof(CHAR16))-1 - StrLen(ItemTemp));\r
     ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, ItemTemp, EfiShellGetEnv(MasterEnvList), TRUE, FALSE);\r
-    StrCpy(NewCommandLine1, NewCommandLine2);\r
+    StrnCpy(NewCommandLine1, NewCommandLine2, NewSize/sizeof(CHAR16)-1);\r
   }\r
   if (CurrentScriptFile != NULL) {\r
     for (AliasListNode = (ALIAS_LIST*)GetFirstNode(&CurrentScriptFile->SubstList)\r
@@ -1385,52 +1498,20 @@ ShellConvertVariables (
       ;  AliasListNode = (ALIAS_LIST*)GetNextNode(&CurrentScriptFile->SubstList, &AliasListNode->Link)\r
    ){\r
     ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, AliasListNode->Alias, AliasListNode->CommandString, TRUE, FALSE);\r
-    StrCpy(NewCommandLine1, NewCommandLine2);\r
+    StrnCpy(NewCommandLine1, NewCommandLine2, NewSize/sizeof(CHAR16)-1);\r
     }\r
 \r
     //\r
     // Remove non-existant environment variables in scripts only\r
     //\r
-    for (Temp = NewCommandLine1 ; Temp != NULL ; ) {\r
-      Temp = StrStr(Temp, L"%");\r
-      if (Temp == NULL) {\r
-        break;\r
-      }\r
-      while (*(Temp - 1) == L'^') {\r
-        Temp = StrStr(Temp + 1, L"%");\r
-        if (Temp == NULL) {\r
-          break;\r
-       }\r
-      }\r
-      if (Temp == NULL) {\r
-        break;\r
-      }\r
-      \r
-      Temp2 = StrStr(Temp + 1, L"%");\r
-      if (Temp2 == NULL) {\r
-        break;\r
-      }\r
-      while (*(Temp2 - 1) == L'^') {\r
-        Temp2 = StrStr(Temp2 + 1, L"%");\r
-        if (Temp2 == NULL) {\r
-          break;\r
-        }\r
-      }\r
-      if (Temp2 == NULL) {\r
-        break;\r
-      }\r
-      \r
-      Temp2++;\r
-      CopyMem(Temp, Temp2, StrSize(Temp2));\r
-    }\r
-\r
+    StripUnreplacedEnvironmentVariables(NewCommandLine1);\r
   }\r
 \r
   //\r
   // Now cleanup any straggler intentionally ignored "%" characters\r
   //\r
   ShellCopySearchAndReplace(NewCommandLine1, NewCommandLine2, NewSize, L"^%", L"%", TRUE, FALSE);\r
-  StrCpy(NewCommandLine1, NewCommandLine2);\r
+  StrnCpy(NewCommandLine1, NewCommandLine2, NewSize/sizeof(CHAR16)-1);\r
   \r
   FreePool(NewCommandLine2);\r
   FreePool(ItemTemp);\r
@@ -1764,7 +1845,7 @@ IsValidSplit(
       return (EFI_OUT_OF_RESOURCES);\r
     }\r
     TempWalker = (CHAR16*)Temp;\r
-    GetNextParameter(&TempWalker, &FirstParameter);\r
+    GetNextParameter(&TempWalker, &FirstParameter, StrSize(CmdLine));\r
 \r
     if (GetOperationType(FirstParameter) == Unknown_Invalid) {\r
       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter);\r
@@ -1932,7 +2013,7 @@ DoHelpUpdate(
   Walker = *CmdLine;\r
   while(Walker != NULL && *Walker != CHAR_NULL) {\r
     LastWalker = Walker;\r
-    GetNextParameter(&Walker, &CurrentParameter);\r
+    GetNextParameter(&Walker, &CurrentParameter, StrSize(*CmdLine));\r
     if (StrStr(CurrentParameter, L"-?") == CurrentParameter) {\r
       LastWalker[0] = L' ';\r
       LastWalker[1] = L' ';\r
@@ -1941,8 +2022,12 @@ DoHelpUpdate(
         Status = EFI_OUT_OF_RESOURCES;\r
         break;\r
       }\r
-      StrCpy(NewCommandLine, L"help ");\r
-      StrCat(NewCommandLine, *CmdLine);\r
+\r
+      //\r
+      // We know the space is sufficient since we just calculated it.\r
+      //\r
+      StrnCpy(NewCommandLine, L"help ", 5);\r
+      StrnCat(NewCommandLine, *CmdLine, StrLen(*CmdLine));\r
       SHELL_FREE_NON_NULL(*CmdLine);\r
       *CmdLine = NewCommandLine;\r
       break;\r
@@ -2006,6 +2091,7 @@ ProcessCommandLineToFinal(
   if (EFI_ERROR(Status)) {\r
     return (Status);\r
   }\r
+  ASSERT (*CmdLine != NULL);\r
 \r
   TrimSpaces(CmdLine);\r
 \r
@@ -2146,6 +2232,7 @@ RunCommandOrFile(
 )\r
 {\r
   EFI_STATUS                Status;\r
+  EFI_STATUS                StartStatus;\r
   CHAR16                    *CommandWithPath;\r
   EFI_DEVICE_PATH_PROTOCOL  *DevPath;\r
   SHELL_STATUS              CalleeExitStatus;\r
@@ -2223,6 +2310,7 @@ RunCommandOrFile(
             DevPath,\r
             CmdLine,\r
             NULL,\r
+            &StartStatus,\r
             NULL,\r
             NULL\r
            );\r
@@ -2232,7 +2320,7 @@ RunCommandOrFile(
           if(EFI_ERROR (Status)) {\r
             CalleeExitStatus = (SHELL_STATUS) (Status & (~MAX_BIT));\r
           } else {\r
-            CalleeExitStatus = SHELL_SUCCESS;\r
+            CalleeExitStatus = (SHELL_STATUS) StartStatus;\r
           }\r
 \r
           //\r
@@ -2368,6 +2456,24 @@ RunCommand(
 \r
   TrimSpaces(&CleanOriginal);\r
 \r
+  //\r
+  // NULL out comments (leveraged from RunScriptFileHandle() ).\r
+  // The # character on a line is used to denote that all characters on the same line\r
+  // and to the right of the # are to be ignored by the shell.\r
+  // Afterward, again remove spaces, in case any were between the last command-parameter and '#'.\r
+  //\r
+  for (TempWalker = CleanOriginal; TempWalker != NULL && *TempWalker != CHAR_NULL; TempWalker++) {\r
+    if (*TempWalker == L'^') {\r
+      if (*(TempWalker + 1) == L'#') {\r
+        CopyMem (TempWalker, TempWalker + 1, StrSize (TempWalker) - sizeof (TempWalker[0]));\r
+      }\r
+    } else if (*TempWalker == L'#') {\r
+      *TempWalker = CHAR_NULL;\r
+    }\r
+  }\r
+\r
+  TrimSpaces(&CleanOriginal);\r
+\r
   //\r
   // Handle case that passed in command line is just 1 or more " " characters.\r
   //\r
@@ -2400,14 +2506,14 @@ RunCommand(
     return (EFI_OUT_OF_RESOURCES);\r
   }\r
   TempWalker = CleanOriginal;\r
-  GetNextParameter(&TempWalker, &FirstParameter);\r
+  GetNextParameter(&TempWalker, &FirstParameter, StrSize(CleanOriginal));\r
 \r
   //\r
   // Depending on the first parameter we change the behavior\r
   //\r
   switch (Type = GetOperationType(FirstParameter)) {\r
     case   File_Sys_Change:\r
-      Status = ChangeMappedDrive(CleanOriginal);\r
+      Status = ChangeMappedDrive (FirstParameter);\r
       break;\r
     case   Internal_Command:\r
     case   Script_File_Name:\r
@@ -2596,7 +2702,7 @@ RunScriptFileHandle (
       ; // conditional increment in the body of the loop\r
   ){\r
     ASSERT(CommandLine2 != NULL);\r
-    StrCpy(CommandLine2, NewScriptFile->CurrentCommand->Cl);\r
+    StrnCpy(CommandLine2, NewScriptFile->CurrentCommand->Cl, PcdGet16(PcdShellPrintBufferSize)/sizeof(CHAR16)-1);\r
 \r
     //\r
     // NULL out comments\r
@@ -2615,7 +2721,7 @@ RunScriptFileHandle (
       //\r
       // Due to variability in starting the find and replace action we need to have both buffers the same.\r
       //\r
-      StrCpy(CommandLine, CommandLine2);\r
+      StrnCpy(CommandLine, CommandLine2, PcdGet16(PcdShellPrintBufferSize)/sizeof(CHAR16)-1);\r
 \r
       //\r
       // Remove the %0 to %9 from the command line (if we have some arguments)\r
@@ -2667,7 +2773,7 @@ RunScriptFileHandle (
       Status = ShellCopySearchAndReplace(CommandLine,  CommandLine2, PcdGet16 (PcdShellPrintBufferSize), L"%8", L"\"\"", FALSE, FALSE);\r
       Status = ShellCopySearchAndReplace(CommandLine2,  CommandLine, PcdGet16 (PcdShellPrintBufferSize), L"%9", L"\"\"", FALSE, FALSE);\r
 \r
-      StrCpy(CommandLine2, CommandLine);\r
+      StrnCpy(CommandLine2, CommandLine, PcdGet16(PcdShellPrintBufferSize)/sizeof(CHAR16)-1);\r
 \r
       LastCommand = NewScriptFile->CurrentCommand;\r
 \r