/** @file
  Main file for endfor and for shell level 1 functions.

  (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "UefiShellLevel1CommandsLib.h"
#include <Library/PrintLib.h>

/**
  Determine if a valid string is a valid number for the 'for' command.

  @param[in] Number The pointer to the string representation of the number to test.

  @retval TRUE    The number is valid.
  @retval FALSE   The number is not valid.
**/
BOOLEAN
ShellIsValidForNumber (
  IN CONST CHAR16  *Number
  )
{
  if ((Number == NULL) || (*Number == CHAR_NULL)) {
    return (FALSE);
  }

  if (*Number == L'-') {
    Number++;
  }

  if (StrLen (Number) == 0) {
    return (FALSE);
  }

  if (StrLen (Number) >= 7) {
    if ((StrStr (Number, L" ") == NULL) || (((StrStr (Number, L" ") != NULL) && ((StrStr (Number, L" ") - Number) >= 7)))) {
      return (FALSE);
    }
  }

  if (!ShellIsDecimalDigitCharacter (*Number)) {
    return (FALSE);
  }

  return (TRUE);
}

/**
  Function for 'endfor' command.

  @param[in] ImageHandle  Handle to the Image (NULL if Internal).
  @param[in] SystemTable  Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunEndFor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS   Status;
  BOOLEAN      Found;
  SCRIPT_FILE  *CurrentScriptFile;

  Status = CommandInit ();
  ASSERT_EFI_ERROR (Status);

  if (!gEfiShellProtocol->BatchIsActive ()) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_NO_SCRIPT), gShellLevel1HiiHandle, L"endfor");
    return (SHELL_UNSUPPORTED);
  }

  if (gEfiShellParametersProtocol->Argc > 1) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_TOO_MANY), gShellLevel1HiiHandle, L"endfor");
    return (SHELL_INVALID_PARAMETER);
  }

  Found = MoveToTag (GetPreviousNode, L"for", L"endfor", NULL, ShellCommandGetCurrentScriptFile (), FALSE, FALSE, FALSE);

  if (!Found) {
    CurrentScriptFile = ShellCommandGetCurrentScriptFile ();
    ShellPrintHiiDefaultEx (
      STRING_TOKEN (STR_SYNTAX_NO_MATCHING),
      gShellLevel1HiiHandle,
      L"For",
      L"EndFor",
      CurrentScriptFile != NULL
                           && CurrentScriptFile->CurrentCommand != NULL
          ? CurrentScriptFile->CurrentCommand->Line : 0
      );
    return (SHELL_NOT_FOUND);
  }

  return (SHELL_SUCCESS);
}

typedef struct {
  UINT32     Signature;
  INTN       Current;
  INTN       End;
  INTN       Step;
  CHAR16     *ReplacementName;
  CHAR16     *CurrentValue;
  BOOLEAN    RemoveSubstAlias;
  CHAR16     Set[1];
} SHELL_FOR_INFO;
#define SIZE_OF_SHELL_FOR_INFO    OFFSET_OF (SHELL_FOR_INFO, Set)
#define SHELL_FOR_INFO_SIGNATURE  SIGNATURE_32 ('S', 'F', 'I', 's')

/**
  Update the value of a given alias on the list.  If the alias is not there then add it.

  @param[in] Alias               The alias to test for.
  @param[in] CommandString       The updated command string.
  @param[in, out] List           The list to search.

  @retval EFI_SUCCESS           The operation was completed successfully.
  @retval EFI_OUT_OF_RESOURCES  There was not enough free memory.
**/
EFI_STATUS
InternalUpdateAliasOnList (
  IN CONST CHAR16    *Alias,
  IN CONST CHAR16    *CommandString,
  IN OUT LIST_ENTRY  *List
  )
{
  ALIAS_LIST  *Node;
  BOOLEAN     Found;

  //
  // assert for NULL parameter
  //
  ASSERT (Alias != NULL);

  //
  // check for the Alias
  //
  for ( Node = (ALIAS_LIST *)GetFirstNode (List), Found = FALSE
        ; !IsNull (List, &Node->Link)
        ; Node = (ALIAS_LIST *)GetNextNode (List, &Node->Link)
        )
  {
    ASSERT (Node->CommandString != NULL);
    ASSERT (Node->Alias != NULL);
    if (StrCmp (Node->Alias, Alias) == 0) {
      FreePool (Node->CommandString);
      Node->CommandString = NULL;
      Node->CommandString = StrnCatGrow (&Node->CommandString, NULL, CommandString, 0);
      Found               = TRUE;
      break;
    }
  }

  if (!Found) {
    Node = AllocateZeroPool (sizeof (ALIAS_LIST));
    if (Node == NULL) {
      return (EFI_OUT_OF_RESOURCES);
    }

    ASSERT (Node->Alias == NULL);
    Node->Alias = StrnCatGrow (&Node->Alias, NULL, Alias, 0);
    ASSERT (Node->CommandString == NULL);
    Node->CommandString = StrnCatGrow (&Node->CommandString, NULL, CommandString, 0);
    InsertTailList (List, &Node->Link);
  }

  return (EFI_SUCCESS);
}

/**
  Find out if an alias is on the given list.

  @param[in] Alias              The alias to test for.
  @param[in] List               The list to search.

  @retval TRUE                  The alias is on the list.
  @retval FALSE                 The alias is not on the list.
**/
BOOLEAN
InternalIsAliasOnList (
  IN CONST CHAR16      *Alias,
  IN CONST LIST_ENTRY  *List
  )
{
  ALIAS_LIST  *Node;

  //
  // assert for NULL parameter
  //
  ASSERT (Alias != NULL);

  //
  // check for the Alias
  //
  for ( Node = (ALIAS_LIST *)GetFirstNode (List)
        ; !IsNull (List, &Node->Link)
        ; Node = (ALIAS_LIST *)GetNextNode (List, &Node->Link)
        )
  {
    ASSERT (Node->CommandString != NULL);
    ASSERT (Node->Alias != NULL);
    if (StrCmp (Node->Alias, Alias) == 0) {
      return (TRUE);
    }
  }

  return (FALSE);
}

/**
  Remove an alias from the given list.

  @param[in] Alias               The alias to remove.
  @param[in, out] List           The list to search.
**/
BOOLEAN
InternalRemoveAliasFromList (
  IN CONST CHAR16    *Alias,
  IN OUT LIST_ENTRY  *List
  )
{
  ALIAS_LIST  *Node;

  //
  // assert for NULL parameter
  //
  ASSERT (Alias != NULL);

  //
  // check for the Alias
  //
  for ( Node = (ALIAS_LIST *)GetFirstNode (List)
        ; !IsNull (List, &Node->Link)
        ; Node = (ALIAS_LIST *)GetNextNode (List, &Node->Link)
        )
  {
    ASSERT (Node->CommandString != NULL);
    ASSERT (Node->Alias != NULL);
    if (StrCmp (Node->Alias, Alias) == 0) {
      RemoveEntryList (&Node->Link);
      FreePool (Node->Alias);
      FreePool (Node->CommandString);
      FreePool (Node);
      return (TRUE);
    }
  }

  return (FALSE);
}

/**
  Function to determine whether a string is decimal or hex representation of a number
  and return the number converted from the string.

  @param[in] String   String representation of a number

  @return             the number
  @retval (UINTN)(-1) An error ocurred.
**/
UINTN
ReturnUintn (
  IN CONST CHAR16  *String
  )
{
  UINT64  RetVal;

  if (!EFI_ERROR (ShellConvertStringToUint64 (String, &RetVal, FALSE, TRUE))) {
    return ((UINTN)RetVal);
  }

  return ((UINTN)(-1));
}

/**
  Function for 'for' command.

  @param[in] ImageHandle  Handle to the Image (NULL if Internal).
  @param[in] SystemTable  Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunFor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS           Status;
  SHELL_STATUS         ShellStatus;
  SCRIPT_FILE          *CurrentScriptFile;
  CHAR16               *ArgSet;
  CHAR16               *ArgSetWalker;
  CHAR16               *Parameter;
  UINTN                ArgSize;
  UINTN                LoopVar;
  SHELL_FOR_INFO       *Info;
  CHAR16               *TempString;
  CHAR16               *TempSpot;
  BOOLEAN              FirstPass;
  EFI_SHELL_FILE_INFO  *Node;
  EFI_SHELL_FILE_INFO  *FileList;
  UINTN                NewSize;

  ArgSet       = NULL;
  ArgSize      = 0;
  ShellStatus  = SHELL_SUCCESS;
  ArgSetWalker = NULL;
  TempString   = NULL;
  Parameter    = NULL;
  FirstPass    = FALSE;

  //
  // initialize the shell lib (we must be in non-auto-init...)
  //
  Status = ShellInitialize ();
  ASSERT_EFI_ERROR (Status);

  Status = CommandInit ();
  ASSERT_EFI_ERROR (Status);

  if (!gEfiShellProtocol->BatchIsActive ()) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_NO_SCRIPT), gShellLevel1HiiHandle, L"for");
    return (SHELL_UNSUPPORTED);
  }

  if (gEfiShellParametersProtocol->Argc < 4) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_TOO_FEW), gShellLevel1HiiHandle, L"for");
    return (SHELL_INVALID_PARAMETER);
  }

  CurrentScriptFile = ShellCommandGetCurrentScriptFile ();
  if (CurrentScriptFile == NULL) {
    ASSERT (CurrentScriptFile != NULL);
    return (SHELL_INVALID_PARAMETER);
  }

  if ((CurrentScriptFile->CurrentCommand != NULL) && (CurrentScriptFile->CurrentCommand->Data == NULL)) {
    FirstPass = TRUE;

    //
    // Make sure that an End exists.
    //
    if (!MoveToTag (GetNextNode, L"endfor", L"for", NULL, CurrentScriptFile, TRUE, TRUE, FALSE)) {
      ShellPrintHiiDefaultEx (
        STRING_TOKEN (STR_SYNTAX_NO_MATCHING),
        gShellLevel1HiiHandle,
        L"EndFor",
        L"For",
        CurrentScriptFile->CurrentCommand->Line
        );
      return (SHELL_DEVICE_ERROR);
    }

    //
    // Process the line.
    //
    if (  (gEfiShellParametersProtocol->Argv[1][0] != L'%') || (gEfiShellParametersProtocol->Argv[1][2] != CHAR_NULL)
       || !(  ((gEfiShellParametersProtocol->Argv[1][1] >= L'a') && (gEfiShellParametersProtocol->Argv[1][1] <= L'z'))
           || ((gEfiShellParametersProtocol->Argv[1][1] >= L'A') && (gEfiShellParametersProtocol->Argv[1][1] <= L'Z')))
          )
    {
      ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_INV_VAR), gShellLevel1HiiHandle, gEfiShellParametersProtocol->Argv[1]);
      return (SHELL_INVALID_PARAMETER);
    }

    if (gUnicodeCollation->StriColl (
                             gUnicodeCollation,
                             L"in",
                             gEfiShellParametersProtocol->Argv[2]
                             ) == 0)
    {
      for (LoopVar = 0x3; LoopVar < gEfiShellParametersProtocol->Argc; LoopVar++) {
        ASSERT ((ArgSet == NULL && ArgSize == 0) || (ArgSet != NULL));
        if (  (StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L"*") != NULL)
           || (StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L"?") != NULL)
           || (StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L"[") != NULL)
           || (StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L"]") != NULL))
        {
          FileList = NULL;
          Status   = ShellOpenFileMetaArg ((CHAR16 *)gEfiShellParametersProtocol->Argv[LoopVar], EFI_FILE_MODE_READ, &FileList);
          if (EFI_ERROR (Status) || (FileList == NULL) || IsListEmpty (&FileList->Link)) {
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L" \"", 0);
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, gEfiShellParametersProtocol->Argv[LoopVar], 0);
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L"\"", 0);
          } else {
            for (Node = (EFI_SHELL_FILE_INFO *)GetFirstNode (&FileList->Link)
                 ; !IsNull (&FileList->Link, &Node->Link)
                 ; Node = (EFI_SHELL_FILE_INFO *)GetNextNode (&FileList->Link, &Node->Link)
                 )
            {
              ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L" \"", 0);
              ArgSet = StrnCatGrow (&ArgSet, &ArgSize, Node->FullName, 0);
              ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L"\"", 0);
            }

            ShellCloseFileMetaArg (&FileList);
          }
        } else {
          Parameter = gEfiShellParametersProtocol->Argv[LoopVar];
          if ((Parameter[0] == L'\"') && (Parameter[StrLen (Parameter)-1] == L'\"')) {
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L" ", 0);
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, Parameter, 0);
          } else {
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L" \"", 0);
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, Parameter, 0);
            ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L"\"", 0);
          }
        }
      }

      if (ArgSet == NULL) {
        ShellStatus = SHELL_OUT_OF_RESOURCES;
      } else {
        //
        // set up for an 'in' for loop
        //
        NewSize  = StrSize (ArgSet);
        NewSize += sizeof (SHELL_FOR_INFO)+StrSize (gEfiShellParametersProtocol->Argv[1]);
        Info     = AllocateZeroPool (NewSize);
        if (Info == NULL) {
          FreePool (ArgSet);
          return SHELL_OUT_OF_RESOURCES;
        }

        Info->Signature = SHELL_FOR_INFO_SIGNATURE;
        CopyMem (Info->Set, ArgSet, StrSize (ArgSet));
        NewSize = StrSize (gEfiShellParametersProtocol->Argv[1]);
        CopyMem (Info->Set+(StrSize (ArgSet)/sizeof (Info->Set[0])), gEfiShellParametersProtocol->Argv[1], NewSize);
        Info->ReplacementName = Info->Set+StrSize (ArgSet)/sizeof (Info->Set[0]);
        Info->CurrentValue    = (CHAR16 *)Info->Set;
        Info->Step            = 0;
        Info->Current         = 0;
        Info->End             = 0;

        if (InternalIsAliasOnList (Info->ReplacementName, &CurrentScriptFile->SubstList)) {
          Info->RemoveSubstAlias = FALSE;
        } else {
          Info->RemoveSubstAlias = TRUE;
        }

        CurrentScriptFile->CurrentCommand->Data = Info;
      }
    } else if (gUnicodeCollation->StriColl (
                                    gUnicodeCollation,
                                    L"run",
                                    gEfiShellParametersProtocol->Argv[2]
                                    ) == 0)
    {
      for (LoopVar = 0x3; LoopVar < gEfiShellParametersProtocol->Argc; LoopVar++) {
        ASSERT ((ArgSet == NULL && ArgSize == 0) || (ArgSet != NULL));
        if ((StrStr (gEfiShellParametersProtocol->Argv[LoopVar], L")") != NULL) &&
            ((LoopVar + 1) < gEfiShellParametersProtocol->Argc)
            )
        {
          return (SHELL_INVALID_PARAMETER);
        }

        if (ArgSet == NULL) {
          //        ArgSet = StrnCatGrow(&ArgSet, &ArgSize, L"\"", 0);
        } else {
          ArgSet = StrnCatGrow (&ArgSet, &ArgSize, L" ", 0);
        }

        ArgSet = StrnCatGrow (&ArgSet, &ArgSize, gEfiShellParametersProtocol->Argv[LoopVar], 0);
        //        ArgSet = StrnCatGrow(&ArgSet, &ArgSize, L" ", 0);
      }

      if (ArgSet == NULL) {
        ShellStatus = SHELL_OUT_OF_RESOURCES;
      } else {
        //
        // set up for a 'run' for loop
        //
        Info = AllocateZeroPool (sizeof (SHELL_FOR_INFO)+StrSize (gEfiShellParametersProtocol->Argv[1]));
        if (Info == NULL) {
          FreePool (ArgSet);
          return SHELL_OUT_OF_RESOURCES;
        }

        Info->Signature = SHELL_FOR_INFO_SIGNATURE;
        CopyMem (Info->Set, gEfiShellParametersProtocol->Argv[1], StrSize (gEfiShellParametersProtocol->Argv[1]));
        Info->ReplacementName = Info->Set;
        Info->CurrentValue    = NULL;
        ArgSetWalker          = ArgSet;
        if (ArgSetWalker[0] != L'(') {
          ShellPrintHiiDefaultEx (
            STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
            gShellLevel1HiiHandle,
            ArgSet,
            CurrentScriptFile->CurrentCommand->Line
            );
          ShellStatus = SHELL_INVALID_PARAMETER;
        } else {
          TempSpot = StrStr (ArgSetWalker, L")");
          if (TempSpot != NULL) {
            TempString = TempSpot+1;
            if (*(TempString) != CHAR_NULL) {
              while (TempString != NULL && *TempString == L' ') {
                TempString++;
              }

              if (StrLen (TempString) > 0) {
                TempSpot = NULL;
              }
            }
          }

          if (TempSpot == NULL) {
            ShellPrintHiiDefaultEx (
              STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
              gShellLevel1HiiHandle,
              CurrentScriptFile->CurrentCommand->Line
              );
            ShellStatus = SHELL_INVALID_PARAMETER;
          } else {
            *TempSpot = CHAR_NULL;
            ArgSetWalker++;
            while (ArgSetWalker != NULL && ArgSetWalker[0] == L' ') {
              ArgSetWalker++;
            }

            if (!ShellIsValidForNumber (ArgSetWalker)) {
              ShellPrintHiiDefaultEx (
                STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
                gShellLevel1HiiHandle,
                ArgSet,
                CurrentScriptFile->CurrentCommand->Line
                );
              ShellStatus = SHELL_INVALID_PARAMETER;
            } else {
              if (ArgSetWalker[0] == L'-') {
                Info->Current = 0 - (INTN)ReturnUintn (ArgSetWalker+1);
              } else {
                Info->Current = (INTN)ReturnUintn (ArgSetWalker);
              }

              ArgSetWalker = StrStr (ArgSetWalker, L" ");
              while (ArgSetWalker != NULL && ArgSetWalker[0] == L' ') {
                ArgSetWalker++;
              }

              if ((ArgSetWalker == NULL) || (*ArgSetWalker == CHAR_NULL) || !ShellIsValidForNumber (ArgSetWalker)) {
                ShellPrintHiiDefaultEx (
                  STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
                  gShellLevel1HiiHandle,
                  ArgSet,
                  CurrentScriptFile->CurrentCommand->Line
                  );
                ShellStatus = SHELL_INVALID_PARAMETER;
              } else {
                if (ArgSetWalker[0] == L'-') {
                  Info->End = 0 - (INTN)ReturnUintn (ArgSetWalker+1);
                } else {
                  Info->End = (INTN)ReturnUintn (ArgSetWalker);
                }

                if (Info->Current < Info->End) {
                  Info->Step = 1;
                } else {
                  Info->Step = -1;
                }

                ArgSetWalker = StrStr (ArgSetWalker, L" ");
                while (ArgSetWalker != NULL && ArgSetWalker[0] == L' ') {
                  ArgSetWalker++;
                }

                if ((ArgSetWalker != NULL) && (*ArgSetWalker != CHAR_NULL)) {
                  if (!ShellIsValidForNumber (ArgSetWalker)) {
                    ShellPrintHiiDefaultEx (
                      STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
                      gShellLevel1HiiHandle,
                      ArgSet,
                      CurrentScriptFile->CurrentCommand->Line
                      );
                    ShellStatus = SHELL_INVALID_PARAMETER;
                  } else {
                    if (*ArgSetWalker == L')') {
                      ASSERT (Info->Step == 1 || Info->Step == -1);
                    } else {
                      if (ArgSetWalker[0] == L'-') {
                        Info->Step = 0 - (INTN)ReturnUintn (ArgSetWalker+1);
                      } else {
                        Info->Step = (INTN)ReturnUintn (ArgSetWalker);
                      }

                      if (StrStr (ArgSetWalker, L" ") != NULL) {
                        ShellPrintHiiDefaultEx (
                          STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
                          gShellLevel1HiiHandle,
                          ArgSet,
                          CurrentScriptFile->CurrentCommand->Line
                          );
                        ShellStatus = SHELL_INVALID_PARAMETER;
                      }
                    }
                  }
                }
              }
            }
          }
        }

        if (ShellStatus == SHELL_SUCCESS) {
          if (InternalIsAliasOnList (Info->ReplacementName, &CurrentScriptFile->SubstList)) {
            Info->RemoveSubstAlias = FALSE;
          } else {
            Info->RemoveSubstAlias = TRUE;
          }
        }

        if (CurrentScriptFile->CurrentCommand != NULL) {
          CurrentScriptFile->CurrentCommand->Data = Info;
        }
      }
    } else {
      ShellPrintHiiDefaultEx (
        STRING_TOKEN (STR_GEN_PROBLEM_SCRIPT),
        gShellLevel1HiiHandle,
        ArgSet,
        CurrentScriptFile != NULL
                             && CurrentScriptFile->CurrentCommand != NULL
          ? CurrentScriptFile->CurrentCommand->Line : 0
        );
      ShellStatus = SHELL_INVALID_PARAMETER;
    }
  } else {
    //
    // These need to be NULL since they are used to determine if this is the first pass later on...
    //
    ASSERT (ArgSetWalker == NULL);
    ASSERT (ArgSet       == NULL);
  }

  if ((CurrentScriptFile != NULL) && (CurrentScriptFile->CurrentCommand != NULL)) {
    Info = (SHELL_FOR_INFO *)CurrentScriptFile->CurrentCommand->Data;
    if (CurrentScriptFile->CurrentCommand->Reset) {
      if (Info != NULL) {
        Info->CurrentValue = (CHAR16 *)Info->Set;
      }

      FirstPass                                = TRUE;
      CurrentScriptFile->CurrentCommand->Reset = FALSE;
    }
  } else {
    ShellStatus = SHELL_UNSUPPORTED;
    Info        = NULL;
  }

  if (ShellStatus == SHELL_SUCCESS) {
    ASSERT (Info != NULL);
    if (Info->Step != 0) {
      //
      // only advance if not the first pass
      //
      if (!FirstPass) {
        //
        // sequence version of for loop...
        //
        Info->Current += Info->Step;
      }

      TempString = AllocateZeroPool (50*sizeof (CHAR16));
      if (TempString == NULL) {
        SHELL_FREE_NON_NULL (ArgSet);
        SHELL_FREE_NON_NULL (Info);
        return (SHELL_OUT_OF_RESOURCES);
      }

      UnicodeSPrint (TempString, 50*sizeof (CHAR16), L"%d", Info->Current);
      InternalUpdateAliasOnList (Info->ReplacementName, TempString, &CurrentScriptFile->SubstList);
      FreePool (TempString);

      if (((Info->Step > 0) && (Info->Current > Info->End)) || ((Info->Step < 0) && (Info->Current < Info->End))) {
        CurrentScriptFile->CurrentCommand->Data = NULL;
        //
        // find the matching endfor (we're done with the loop)
        //
        if (!MoveToTag (GetNextNode, L"endfor", L"for", NULL, CurrentScriptFile, TRUE, FALSE, FALSE)) {
          ShellPrintHiiDefaultEx (
            STRING_TOKEN (STR_SYNTAX_NO_MATCHING),
            gShellLevel1HiiHandle,
            L"EndFor",
            L"For",
            CurrentScriptFile != NULL
                                 && CurrentScriptFile->CurrentCommand != NULL
              ? CurrentScriptFile->CurrentCommand->Line : 0
            );
          ShellStatus = SHELL_DEVICE_ERROR;
        }

        if (Info->RemoveSubstAlias) {
          //
          // remove item from list
          //
          InternalRemoveAliasFromList (Info->ReplacementName, &CurrentScriptFile->SubstList);
        }

        FreePool (Info);
      }
    } else {
      //
      // Must be in 'in' version of for loop...
      //
      ASSERT (Info->Set != NULL);
      if ((Info->CurrentValue != NULL) && (*Info->CurrentValue != CHAR_NULL)) {
        if (Info->CurrentValue[0] == L' ') {
          Info->CurrentValue++;
        }

        if (Info->CurrentValue[0] == L'\"') {
          Info->CurrentValue++;
        }

        //
        // do the next one of the set
        //
        ASSERT (TempString == NULL);
        TempString = StrnCatGrow (&TempString, NULL, Info->CurrentValue, 0);
        if (TempString == NULL) {
          ShellStatus = SHELL_OUT_OF_RESOURCES;
        } else {
          TempSpot = StrStr (TempString, L"\" \"");
          if (TempSpot != NULL) {
            *TempSpot = CHAR_NULL;
          }

          while (TempString[StrLen (TempString)-1] == L'\"') {
            TempString[StrLen (TempString)-1] = CHAR_NULL;
          }

          InternalUpdateAliasOnList (Info->ReplacementName, TempString, &CurrentScriptFile->SubstList);
          Info->CurrentValue += StrLen (TempString);

          if (Info->CurrentValue[0] == L'\"') {
            Info->CurrentValue++;
          }

          FreePool (TempString);
        }
      } else {
        CurrentScriptFile->CurrentCommand->Data = NULL;
        //
        // find the matching endfor (we're done with the loop)
        //
        if (!MoveToTag (GetNextNode, L"endfor", L"for", NULL, CurrentScriptFile, TRUE, FALSE, FALSE)) {
          ShellPrintHiiDefaultEx (
            STRING_TOKEN (STR_SYNTAX_NO_MATCHING),
            gShellLevel1HiiHandle,
            L"EndFor",
            L"For",
            CurrentScriptFile != NULL
                                 && CurrentScriptFile->CurrentCommand != NULL
              ? CurrentScriptFile->CurrentCommand->Line : 0
            );
          ShellStatus = SHELL_DEVICE_ERROR;
        }

        if (Info->RemoveSubstAlias) {
          //
          // remove item from list
          //
          InternalRemoveAliasFromList (Info->ReplacementName, &CurrentScriptFile->SubstList);
        }

        FreePool (Info);
      }
    }
  }

  if (ArgSet != NULL) {
    FreePool (ArgSet);
  }

  return (ShellStatus);
}
