/** @file
  Main file for map shell level 2 command.

  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P.<BR>
  (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "UefiShellLevel2CommandsLib.h"
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>

/**
  Determine if a string has only numbers and letters.

  This is useful for such things as Map names which can only be letters and numbers.

  @param[in] String       pointer to the string to analyze,
  @param[in] Len          Number of characters to analyze.

  @retval TRUE            String has only numbers and letters
  @retval FALSE           String has at least one other character.
**/
BOOLEAN
IsNumberLetterOnly (
  IN CONST CHAR16  *String,
  IN CONST UINTN   Len
  )
{
  UINTN  Count;

  for (Count = 0; Count < Len && String != NULL && *String != CHAR_NULL; String++, Count++) {
    if (!(((*String >= L'a') && (*String <= L'z')) ||
          ((*String >= L'A') && (*String <= L'Z')) ||
          ((*String >= L'0') && (*String <= L'9')))
        )
    {
      return (FALSE);
    }
  }

  return (TRUE);
}

/**
  Do a search in the Target delimited list.

  @param[in] List         The list to seatch in.
  @param[in] MetaTarget   The item to search for. MetaMatching supported.
  @param[out] FullName    Optional pointer to an allocated buffer containing
                          the match.
  @param[in] Meta         TRUE to use MetaMatching.
  @param[in] SkipTrailingNumbers  TRUE to allow for numbers after the MetaTarget.
  @param[in] Target       The single character that delimits list
                          items (";" normally).
**/
BOOLEAN
SearchList (
  IN CONST CHAR16   *List,
  IN CONST CHAR16   *MetaTarget,
  OUT CHAR16        **FullName OPTIONAL,
  IN CONST BOOLEAN  Meta,
  IN CONST BOOLEAN  SkipTrailingNumbers,
  IN CONST CHAR16   *Target

  )
{
  CHAR16        *TempList;
  CONST CHAR16  *ListWalker;
  BOOLEAN       Result;
  CHAR16        *TempSpot;

  for (ListWalker = List, TempList = NULL
       ; ListWalker != NULL && *ListWalker != CHAR_NULL
       ;
       )
  {
    TempList = StrnCatGrow (&TempList, NULL, ListWalker, 0);
    ASSERT (TempList != NULL);
    TempSpot = StrStr (TempList, Target);
    if (TempSpot != NULL) {
      *TempSpot = CHAR_NULL;
    }

    while (SkipTrailingNumbers && (ShellIsDecimalDigitCharacter (TempList[StrLen (TempList)-1]) || TempList[StrLen (TempList)-1] == L':')) {
      TempList[StrLen (TempList)-1] = CHAR_NULL;
    }

    ListWalker = StrStr (ListWalker, Target);
    while (ListWalker != NULL && *ListWalker == *Target) {
      ListWalker++;
    }

    if (Meta) {
      Result = gUnicodeCollation->MetaiMatch (gUnicodeCollation, (CHAR16 *)TempList, (CHAR16 *)MetaTarget);
    } else {
      Result = (BOOLEAN)(StrCmp (TempList, MetaTarget) == 0);
    }

    if (Result) {
      if (FullName != NULL) {
        *FullName = TempList;
      } else {
        FreePool (TempList);
      }

      return (TRUE);
    }

    FreePool (TempList);
    TempList = NULL;
  }

  return (FALSE);
}

/**
  Determine what type of device is represented and return it's string.  The
  string is in allocated memory and must be callee freed.  The HII is is listed below.
  The actual string cannot be determined.

  @param[in] DevicePath     The device to analyze.

  @retval STR_MAP_MEDIA_UNKNOWN   The media type is unknown.
  @retval STR_MAP_MEDIA_HARDDISK  The media is a hard drive.
  @retval STR_MAP_MEDIA_CDROM     The media is a CD ROM.
  @retval STR_MAP_MEDIA_FLOPPY    The media is a floppy drive.
**/
CHAR16 *
GetDeviceMediaType (
  IN  EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  ACPI_HID_DEVICE_PATH  *Acpi;

  //
  //  Parse the device path:
  //  Devicepath sub type                 mediatype
  //    MEDIA_HANRDDRIVE_DP      ->       Hard Disk
  //    MEDIA_CDROM_DP           ->       CD Rom
  //    Acpi.HID = 0X0604        ->       Floppy
  //
  if (NULL == DevicePath) {
    return HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_MAP_MEDIA_UNKNOWN), NULL);
  }

  for ( ; !IsDevicePathEndType (DevicePath); DevicePath = NextDevicePathNode (DevicePath)) {
    if (DevicePathType (DevicePath) == MEDIA_DEVICE_PATH) {
      switch (DevicePathSubType (DevicePath)) {
        case MEDIA_HARDDRIVE_DP:
          return HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_MAP_MEDIA_HARDDISK), NULL);
        case MEDIA_CDROM_DP:
          return HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_MAP_MEDIA_CDROM), NULL);
      }
    } else if (DevicePathType (DevicePath) == ACPI_DEVICE_PATH) {
      Acpi = (ACPI_HID_DEVICE_PATH *)DevicePath;
      if (EISA_ID_TO_NUM (Acpi->HID) == 0x0604) {
        return HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_MAP_MEDIA_FLOPPY), NULL);
      }
    }
  }

  return HiiGetString (gShellLevel2HiiHandle, STRING_TOKEN (STR_MAP_MEDIA_UNKNOWN), NULL);
}

/**
  Function to detemine if a handle has removable storage.

  @param[in] DevicePath             DevicePath to test.

  @retval TRUE                      The handle has removable storage.
  @retval FALSE                     The handle does not have removable storage.
**/
BOOLEAN
IsRemoveableDevice (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  if (NULL == DevicePath) {
    return FALSE;
  }

  while (!IsDevicePathEndType (DevicePath)) {
    if (DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) {
      switch (DevicePathSubType (DevicePath)) {
        case MSG_USB_DP:
        case MSG_SCSI_DP:
          return TRUE;
        default:
          return FALSE;
      }
    }

    DevicePath = NextDevicePathNode (DevicePath);
  }

  return FALSE;
}

/**
  Function to detemine if a something on the map list matches.

  @param[in] MapList          The pointer to the list to test.
  @param[in] Specific         The pointer to a specific name to test for.
  @param[in] TypeString       The pointer to the list of types.
  @param[in] Normal           Always show normal mappings.
  @param[in] Consist          Always show consistent mappings.

  @retval TRUE                The map should be displayed.
  @retval FALSE               The map should not be displayed.
**/
BOOLEAN
MappingListHasType (
  IN CONST CHAR16   *MapList,
  IN CONST CHAR16   *Specific,
  IN CONST CHAR16   *TypeString,
  IN CONST BOOLEAN  Normal,
  IN CONST BOOLEAN  Consist
  )
{
  CHAR16         *NewSpecific;
  RETURN_STATUS  Status;
  UINTN          Length;

  //
  // specific has priority
  //
  if (Specific != NULL) {
    Length = StrLen (Specific);
    //
    // Allocate enough buffer for Specific and potential ":"
    //
    NewSpecific = AllocatePool ((Length + 2) * sizeof (CHAR16));
    if (NewSpecific == NULL) {
      return FALSE;
    }

    StrCpyS (NewSpecific, Length + 2, Specific);
    if (Specific[Length - 1] != L':') {
      Status = StrnCatS (NewSpecific, Length + 2, L":", StrLen (L":"));
      if (EFI_ERROR (Status)) {
        FreePool (NewSpecific);
        return FALSE;
      }
    }

    if (SearchList (MapList, NewSpecific, NULL, TRUE, FALSE, L";")) {
      FreePool (NewSpecific);
      return (TRUE);
    }

    FreePool (NewSpecific);
  }

  if (  Consist
     && (Specific == NULL)
     && (  SearchList (MapList, L"HD*", NULL, TRUE, TRUE, L";")
        || SearchList (MapList, L"CD*", NULL, TRUE, TRUE, L";")
        || SearchList (MapList, L"F*", NULL, TRUE, TRUE, L";")
        || SearchList (MapList, L"FP*", NULL, TRUE, TRUE, L";")))
  {
    return (TRUE);
  }

  if (  Normal
     && (Specific == NULL)
     && (  SearchList (MapList, L"FS", NULL, FALSE, TRUE, L";")
        || SearchList (MapList, L"BLK", NULL, FALSE, TRUE, L";")))
  {
    return (TRUE);
  }

  if ((TypeString != NULL) && SearchList (MapList, TypeString, NULL, TRUE, TRUE, L";")) {
    return (TRUE);
  }

  return (FALSE);
}

/**
  Display a single map line for device Handle if conditions are met.

  @param[in] Verbose                TRUE to display (extra) verbose information.
  @param[in] Consist                TRUE to display consistent mappings.
  @param[in] Normal                 TRUE to display normal (not consist) mappings.
  @param[in] TypeString             pointer to string of filter types.
  @param[in] SFO                    TRUE to display output in Standard Output Format.
  @param[in] Specific               pointer to string for specific map to display.
  @param[in] Handle                 The handle to display from.

  @retval EFI_SUCCESS               The mapping was displayed.
**/
EFI_STATUS
PerformSingleMappingDisplay (
  IN CONST BOOLEAN     Verbose,
  IN CONST BOOLEAN     Consist,
  IN CONST BOOLEAN     Normal,
  IN CONST CHAR16      *TypeString,
  IN CONST BOOLEAN     SFO,
  IN CONST CHAR16      *Specific OPTIONAL,
  IN CONST EFI_HANDLE  Handle
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevPath;
  EFI_DEVICE_PATH_PROTOCOL  *DevPathCopy;
  CONST CHAR16              *MapList;
  CHAR16                    *CurrentName;
  CHAR16                    *MediaType;
  CHAR16                    *DevPathString;
  CHAR16                    *TempSpot;
  CHAR16                    *Alias;
  UINTN                     TempLen;
  BOOLEAN                   Removable;
  CONST CHAR16              *TempSpot2;

  Alias       = NULL;
  TempSpot2   = NULL;
  CurrentName = NULL;
  DevPath     = DevicePathFromHandle (Handle);
  DevPathCopy = DevPath;
  MapList     = gEfiShellProtocol->GetMapFromDevicePath (&DevPathCopy);
  if (MapList == NULL) {
    return EFI_NOT_FOUND;
  }

  if (!MappingListHasType (MapList, Specific, TypeString, Normal, Consist)) {
    return EFI_NOT_FOUND;
  }

  if (Normal || !Consist) {
    //
    // need the Normal here since people can use both on command line.  otherwise unused.
    //

    //
    // Allocate a name
    //
    CurrentName = NULL;
    CurrentName = StrnCatGrow (&CurrentName, 0, MapList, 0);
    if (CurrentName == NULL) {
      return (EFI_OUT_OF_RESOURCES);
    }

    //
    // Chop off the other names that become "Alias(s)"
    // leaving just the normal name
    //
    TempSpot = StrStr (CurrentName, L";");
    if (TempSpot != NULL) {
      *TempSpot = CHAR_NULL;
    }
  } else {
    CurrentName = NULL;

    //
    // Skip the first name.  This is the standard name.
    //
    TempSpot = StrStr (MapList, L";");
    if (TempSpot != NULL) {
      TempSpot++;
    }

    SearchList (TempSpot, L"HD*", &CurrentName, TRUE, FALSE, L";");
    if (CurrentName == NULL) {
      SearchList (TempSpot, L"CD*", &CurrentName, TRUE, FALSE, L";");
    }

    if (CurrentName == NULL) {
      SearchList (TempSpot, L"FP*", &CurrentName, TRUE, FALSE, L";");
    }

    if (CurrentName == NULL) {
      SearchList (TempSpot, L"F*", &CurrentName, TRUE, FALSE, L";");
    }

    if (CurrentName == NULL) {
      //
      // We didnt find anything, so just the first one in the list...
      //
      CurrentName = StrnCatGrow (&CurrentName, 0, MapList, 0);
      if (CurrentName == NULL) {
        return (EFI_OUT_OF_RESOURCES);
      }

      TempSpot = StrStr (CurrentName, L";");
      if (TempSpot != NULL) {
        *TempSpot = CHAR_NULL;
      }
    } else {
      Alias = StrnCatGrow (&Alias, 0, MapList, 0);
      if (Alias == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      TempSpot = StrStr (Alias, CurrentName);
      if (TempSpot != NULL) {
        TempSpot2 = StrStr (TempSpot, L";");
        if (TempSpot2 != NULL) {
          TempSpot2++; // Move past ";" from CurrentName
          CopyMem (TempSpot, TempSpot2, StrSize (TempSpot2));
        } else {
          *TempSpot = CHAR_NULL;
        }
      }

      if (Alias[StrLen (Alias)-1] == L';') {
        Alias[StrLen (Alias)-1] = CHAR_NULL;
      }
    }
  }

  DevPathString = ConvertDevicePathToText (DevPath, TRUE, FALSE);
  TempLen       = StrLen (CurrentName);
  if (!SFO) {
    ShellPrintHiiEx (
      -1,
      -1,
      NULL,
      STRING_TOKEN (STR_MAP_ENTRY),
      gShellLevel2HiiHandle,
      CurrentName,
      Alias != NULL ? Alias : (TempLen < StrLen (MapList) ? MapList + TempLen+1 : L""),
      DevPathString
      );
    if (Verbose) {
      //
      // also print handle, media type, removable (y/n), and current directory
      //
      MediaType = GetDeviceMediaType (DevPath);
      if (((TypeString != NULL) && (MediaType != NULL) && (StrStr (TypeString, MediaType) != NULL)) || (TypeString == NULL)) {
        Removable = IsRemoveableDevice (DevPath);
        TempSpot2 = ShellGetCurrentDir (CurrentName);
        ShellPrintHiiEx (
          -1,
          -1,
          NULL,
          STRING_TOKEN (STR_MAP_ENTRY_VERBOSE),
          gShellLevel2HiiHandle,
          ConvertHandleToHandleIndex (Handle),
          MediaType,
          Removable ? L"Yes" : L"No",
          TempSpot2
          );
      }

      SHELL_FREE_NON_NULL (MediaType);
    }
  } else {
    ShellPrintHiiEx (
      -1,
      -1,
      NULL,
      STRING_TOKEN (STR_MAP_SFO_MAPPINGS),
      gShellLevel2HiiHandle,
      CurrentName,
      DevPathString,
      Consist ? L"" : (TempLen < StrLen (MapList) ? MapList + TempLen+1 : L"")
      );
  }

  SHELL_FREE_NON_NULL (DevPathString);
  SHELL_FREE_NON_NULL (CurrentName);
  SHELL_FREE_NON_NULL (Alias);
  return EFI_SUCCESS;
}

/**
  Delete Specific from the list of maps for device Handle.

  @param[in] Specific   The name to delete.
  @param[in] Handle     The device to look on.

  @retval EFI_SUCCESS     The delete was successful.
  @retval EFI_NOT_FOUND   Name was not a map on Handle.
**/
EFI_STATUS
PerformSingleMappingDelete (
  IN CONST CHAR16      *Specific,
  IN CONST EFI_HANDLE  Handle
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevPath;
  EFI_DEVICE_PATH_PROTOCOL  *DevPathCopy;
  CONST CHAR16              *MapList;
  CHAR16                    *CurrentName;

  DevPath     = DevicePathFromHandle (Handle);
  DevPathCopy = DevPath;
  MapList     = gEfiShellProtocol->GetMapFromDevicePath (&DevPathCopy);
  CurrentName = NULL;

  if (MapList == NULL) {
    return (EFI_NOT_FOUND);
  }

  //
  // if there is a specific and its not on the list...
  //
  if (!SearchList (MapList, Specific, &CurrentName, TRUE, FALSE, L";")) {
    return (EFI_NOT_FOUND);
  }

  return (gEfiShellProtocol->SetMap (NULL, CurrentName));
}

CONST CHAR16  Cd[]   = L"cd*";
CONST CHAR16  Hd[]   = L"hd*";
CONST CHAR16  Fp[]   = L"fp*";
CONST CHAR16  AnyF[] = L"F*";

/**
  Function to display mapping information to the user.

  If Specific is specified then Consist and Normal will be ignored since information will
  be printed for the specific item only.

  @param[in] Verbose                TRUE to display (extra) verbose information.
  @param[in] Consist                TRUE to display consistent mappings.
  @param[in] Normal                 TRUE to display normal (not consist) mappings.
  @param[in] TypeString             Pointer to string of filter types.
  @param[in] SFO                    TRUE to display output in Standard Output Format.
  @param[in] Specific               Pointer to string for specific map to display.
  @param[in] Header                 TRUE to print the header block.

  @retval SHELL_SUCCESS               The display was printed.
  @retval SHELL_INVALID_PARAMETER     One of Consist or Normal must be TRUE if no Specific.

**/
SHELL_STATUS
PerformMappingDisplay (
  IN CONST BOOLEAN  Verbose,
  IN CONST BOOLEAN  Consist,
  IN CONST BOOLEAN  Normal,
  IN CONST CHAR16   *TypeString,
  IN CONST BOOLEAN  SFO,
  IN CONST CHAR16   *Specific OPTIONAL,
  IN CONST BOOLEAN  Header
  )
{
  EFI_STATUS  Status;
  EFI_HANDLE  *HandleBuffer;
  UINTN       BufferSize;
  UINTN       LoopVar;
  CHAR16      *Test;
  BOOLEAN     Found;

  if (!Consist && !Normal && (Specific == NULL) && (TypeString == NULL)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), gShellLevel2HiiHandle, L"map");
    return (SHELL_INVALID_PARAMETER);
  }

  if (TypeString != NULL) {
    Test = (CHAR16 *)Cd;
    if (StrnCmp (TypeString, Test, StrLen (Test)-1) != 0) {
      Test = (CHAR16 *)Hd;
      if (StrnCmp (TypeString, Test, StrLen (Test)-1) != 0) {
        Test = (CHAR16 *)Fp;
        if (StrnCmp (TypeString, Test, StrLen (Test)-1) != 0) {
          ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel2HiiHandle, L"map", TypeString);
          return (SHELL_INVALID_PARAMETER);
        }
      } else if (Test == NULL) {
        Test = (CHAR16 *)AnyF;
      }
    }
  } else {
    Test = NULL;
  }

  if (Header) {
    //
    // Print the header
    //
    if (!SFO) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_MAP_HEADER), gShellLevel2HiiHandle);
    } else {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_SFO_HEADER), gShellLevel2HiiHandle, L"map");
    }
  }

  BufferSize   = 0;
  HandleBuffer = NULL;

  //
  // Look up all SimpleFileSystems in the platform
  //
  Status = gBS->LocateHandle (
                  ByProtocol,
                  &gEfiSimpleFileSystemProtocolGuid,
                  NULL,
                  &BufferSize,
                  HandleBuffer
                  );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    HandleBuffer = AllocateZeroPool (BufferSize);
    if (HandleBuffer == NULL) {
      return (SHELL_OUT_OF_RESOURCES);
    }

    Status = gBS->LocateHandle (
                    ByProtocol,
                    &gEfiSimpleFileSystemProtocolGuid,
                    NULL,
                    &BufferSize,
                    HandleBuffer
                    );
  }

  //
  // Get the map name(s) for each one.
  //
  for ( LoopVar = 0, Found = FALSE
        ; LoopVar < (BufferSize / sizeof (EFI_HANDLE)) && HandleBuffer != NULL
        ; LoopVar++
        )
  {
    Status = PerformSingleMappingDisplay (
               Verbose,
               Consist,
               Normal,
               Test,
               SFO,
               Specific,
               HandleBuffer[LoopVar]
               );
    if (!EFI_ERROR (Status)) {
      Found = TRUE;
    }
  }

  //
  // Look up all BlockIo in the platform
  //
  Status = gBS->LocateHandle (
                  ByProtocol,
                  &gEfiBlockIoProtocolGuid,
                  NULL,
                  &BufferSize,
                  HandleBuffer
                  );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    SHELL_FREE_NON_NULL (HandleBuffer);
    HandleBuffer = AllocateZeroPool (BufferSize);
    if (HandleBuffer == NULL) {
      return (SHELL_OUT_OF_RESOURCES);
    }

    Status = gBS->LocateHandle (
                    ByProtocol,
                    &gEfiBlockIoProtocolGuid,
                    NULL,
                    &BufferSize,
                    HandleBuffer
                    );
  }

  if (!EFI_ERROR (Status) && (HandleBuffer != NULL)) {
    //
    // Get the map name(s) for each one.
    //
    for ( LoopVar = 0
          ; LoopVar < BufferSize / sizeof (EFI_HANDLE)
          ; LoopVar++
          )
    {
      //
      // Skip any that were already done...
      //
      if (gBS->OpenProtocol (
                 HandleBuffer[LoopVar],
                 &gEfiSimpleFileSystemProtocolGuid,
                 NULL,
                 gImageHandle,
                 NULL,
                 EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                 ) == EFI_SUCCESS)
      {
        continue;
      }

      Status = PerformSingleMappingDisplay (
                 Verbose,
                 Consist,
                 Normal,
                 Test,
                 SFO,
                 Specific,
                 HandleBuffer[LoopVar]
                 );
      if (!EFI_ERROR (Status)) {
        Found = TRUE;
      }
    }

    FreePool (HandleBuffer);
  }

  if (!Found) {
    if (Specific != NULL) {
      ShellPrintHiiEx (gST->ConOut->Mode->CursorColumn, gST->ConOut->Mode->CursorRow-1, NULL, STRING_TOKEN (STR_MAP_NF), gShellLevel2HiiHandle, L"map", Specific);
    } else {
      ShellPrintHiiEx (gST->ConOut->Mode->CursorColumn, gST->ConOut->Mode->CursorRow-1, NULL, STRING_TOKEN (STR_CD_NF), gShellLevel2HiiHandle, L"map");
    }
  }

  return (SHELL_SUCCESS);
}

/**
  Perform a mapping display and parse for multiple types in the TypeString.

  @param[in] Verbose      TRUE to use verbose output.
  @param[in] Consist      TRUE to display consistent names.
  @param[in] Normal       TRUE to display normal names.
  @param[in] TypeString   An optional comma-delimited list of types.
  @param[in] SFO          TRUE to display in SFO format.  See Spec.
  @param[in] Specific     An optional specific map name to display alone.

  @retval SHELL_INVALID_PARAMETER   A parameter was invalid.
  @retval SHELL_SUCCESS             The display was successful.
  @sa PerformMappingDisplay
**/
SHELL_STATUS
PerformMappingDisplay2 (
  IN CONST BOOLEAN  Verbose,
  IN CONST BOOLEAN  Consist,
  IN CONST BOOLEAN  Normal,
  IN CONST CHAR16   *TypeString,
  IN CONST BOOLEAN  SFO,
  IN CONST CHAR16   *Specific OPTIONAL
  )
{
  CONST CHAR16  *TypeWalker;
  SHELL_STATUS  ShellStatus;
  CHAR16        *Comma;

  if (TypeString == NULL) {
    return (PerformMappingDisplay (Verbose, Consist, Normal, NULL, SFO, Specific, TRUE));
  }

  ShellStatus = SHELL_SUCCESS;
  for (TypeWalker = TypeString; TypeWalker != NULL && *TypeWalker != CHAR_NULL;) {
    Comma = StrStr (TypeWalker, L",");
    if (Comma == NULL) {
      if (ShellStatus == SHELL_SUCCESS) {
        ShellStatus = PerformMappingDisplay (Verbose, Consist, Normal, TypeWalker, SFO, Specific, (BOOLEAN)(TypeWalker == TypeString));
      } else {
        PerformMappingDisplay (Verbose, Consist, Normal, TypeWalker, SFO, Specific, (BOOLEAN)(TypeWalker == TypeString));
      }

      break;
    } else {
      *Comma = CHAR_NULL;
      if (ShellStatus == SHELL_SUCCESS) {
        ShellStatus = PerformMappingDisplay (Verbose, Consist, Normal, TypeWalker, SFO, Specific, (BOOLEAN)(TypeWalker == TypeString));
      } else {
        PerformMappingDisplay (Verbose, Consist, Normal, TypeWalker, SFO, Specific, (BOOLEAN)(TypeWalker == TypeString));
      }

      *Comma     = L',';
      TypeWalker = Comma + 1;
    }
  }

  return (ShellStatus);
}

/**
  Delete a specific map.

  @param[in] Specific  The pointer to the name of the map to delete.

  @retval EFI_INVALID_PARAMETER     Specific was NULL.
  @retval EFI_SUCCESS               The operation was successful.
  @retval EFI_NOT_FOUND             Specific could not be found.
**/
EFI_STATUS
PerformMappingDelete (
  IN CONST CHAR16  *Specific
  )
{
  EFI_STATUS  Status;
  EFI_HANDLE  *HandleBuffer;
  UINTN       BufferSize;
  UINTN       LoopVar;
  BOOLEAN     Deleted;

  if (Specific == NULL) {
    return (EFI_INVALID_PARAMETER);
  }

  BufferSize   = 0;
  HandleBuffer = NULL;
  Deleted      = FALSE;

  //
  // Look up all SimpleFileSystems in the platform
  //
  Status = gBS->LocateHandle (
                  ByProtocol,
                  &gEfiDevicePathProtocolGuid,
                  NULL,
                  &BufferSize,
                  HandleBuffer
                  );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    HandleBuffer = AllocateZeroPool (BufferSize);
    if (HandleBuffer == NULL) {
      return (EFI_OUT_OF_RESOURCES);
    }

    Status = gBS->LocateHandle (
                    ByProtocol,
                    &gEfiDevicePathProtocolGuid,
                    NULL,
                    &BufferSize,
                    HandleBuffer
                    );
  }

  if (EFI_ERROR (Status)) {
    SHELL_FREE_NON_NULL (HandleBuffer);
    return (Status);
  }

  if (HandleBuffer != NULL) {
    //
    // Get the map name(s) for each one.
    //
    for ( LoopVar = 0
          ; LoopVar < BufferSize / sizeof (EFI_HANDLE)
          ; LoopVar++
          )
    {
      if (PerformSingleMappingDelete (Specific, HandleBuffer[LoopVar]) == SHELL_SUCCESS) {
        Deleted = TRUE;
      }
    }
  }

  //
  // Look up all BlockIo in the platform
  //
  Status = gBS->LocateHandle (
                  ByProtocol,
                  &gEfiBlockIoProtocolGuid,
                  NULL,
                  &BufferSize,
                  HandleBuffer
                  );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    FreePool (HandleBuffer);
    HandleBuffer = AllocateZeroPool (BufferSize);
    if (HandleBuffer == NULL) {
      return (EFI_OUT_OF_RESOURCES);
    }

    Status = gBS->LocateHandle (
                    ByProtocol,
                    &gEfiBlockIoProtocolGuid,
                    NULL,
                    &BufferSize,
                    HandleBuffer
                    );
  }

  if (EFI_ERROR (Status)) {
    SHELL_FREE_NON_NULL (HandleBuffer);
    return (Status);
  }

  if (HandleBuffer != NULL) {
    //
    // Get the map name(s) for each one.
    //
    for ( LoopVar = 0
          ; LoopVar < BufferSize / sizeof (EFI_HANDLE)
          ; LoopVar++
          )
    {
      //
      // Skip any that were already done...
      //
      if (gBS->OpenProtocol (
                 HandleBuffer[LoopVar],
                 &gEfiDevicePathProtocolGuid,
                 NULL,
                 gImageHandle,
                 NULL,
                 EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                 ) == EFI_SUCCESS)
      {
        continue;
      }

      if (PerformSingleMappingDelete (Specific, HandleBuffer[LoopVar]) == SHELL_SUCCESS) {
        Deleted = TRUE;
      }
    }
  }

  SHELL_FREE_NON_NULL (HandleBuffer);
  if (!Deleted) {
    return (EFI_NOT_FOUND);
  }

  return (EFI_SUCCESS);
}

/**
  function to add a mapping from mapping.

  This function will get the device path associated with the mapping and call SetMap.

  @param[in] Map         The Map to add a mapping for
  @param[in] SName       The name of the new mapping

  @retval SHELL_SUCCESS             the mapping was added
  @retval SHELL_INVALID_PARAMETER   the device path for Map could not be retrieved.
  @return                           Shell version of a return value from EfiShellProtocol->SetMap

**/
SHELL_STATUS
AddMappingFromMapping (
  IN CONST CHAR16  *Map,
  IN CONST CHAR16  *SName
  )
{
  CONST EFI_DEVICE_PATH_PROTOCOL  *DevPath;
  EFI_STATUS                      Status;
  CHAR16                          *NewSName;
  RETURN_STATUS                   StrRetStatus;

  NewSName = AllocateCopyPool (StrSize (SName) + sizeof (CHAR16), SName);
  if (NewSName == NULL) {
    return (SHELL_OUT_OF_RESOURCES);
  }

  if (NewSName[StrLen (NewSName)-1] != L':') {
    StrRetStatus = StrnCatS (NewSName, (StrSize (SName) + sizeof (CHAR16))/sizeof (CHAR16), L":", StrLen (L":"));
    if (EFI_ERROR (StrRetStatus)) {
      FreePool (NewSName);
      return ((SHELL_STATUS)(StrRetStatus & (~MAX_BIT)));
    }
  }

  if (!IsNumberLetterOnly (NewSName, StrLen (NewSName)-1)) {
    FreePool (NewSName);
    return (SHELL_INVALID_PARAMETER);
  }

  DevPath = gEfiShellProtocol->GetDevicePathFromMap (Map);
  if (DevPath == NULL) {
    FreePool (NewSName);
    return (SHELL_INVALID_PARAMETER);
  }

  Status = gEfiShellProtocol->SetMap (DevPath, NewSName);
  FreePool (NewSName);
  if (EFI_ERROR (Status)) {
    return (SHELL_DEVICE_ERROR);
  }

  return (SHELL_SUCCESS);
}

/**
  function to add a mapping from an EFI_HANDLE.

  This function will get the device path associated with the Handle and call SetMap.

  @param[in] Handle       The handle to add a mapping for
  @param[in] SName        The name of the new mapping

  @retval SHELL_SUCCESS           the mapping was added
  @retval SHELL_INVALID_PARAMETER SName was not valid for a map name.
  @return                         Shell version of a return value from either
                                  gBS->OpenProtocol or EfiShellProtocol->SetMap

**/
SHELL_STATUS
AddMappingFromHandle (
  IN CONST EFI_HANDLE  Handle,
  IN CONST CHAR16      *SName
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevPath;
  EFI_STATUS                Status;
  CHAR16                    *NewSName;
  RETURN_STATUS             StrRetStatus;

  NewSName = AllocateCopyPool (StrSize (SName) + sizeof (CHAR16), SName);
  if (NewSName == NULL) {
    return (SHELL_OUT_OF_RESOURCES);
  }

  if (NewSName[StrLen (NewSName)-1] != L':') {
    StrRetStatus = StrnCatS (NewSName, (StrSize (SName) + sizeof (CHAR16))/sizeof (CHAR16), L":", StrLen (L":"));
    if (EFI_ERROR (StrRetStatus)) {
      FreePool (NewSName);
      return ((SHELL_STATUS)(StrRetStatus & (~MAX_BIT)));
    }
  }

  if (!IsNumberLetterOnly (NewSName, StrLen (NewSName)-1)) {
    FreePool (NewSName);
    return (SHELL_INVALID_PARAMETER);
  }

  Status = gBS->OpenProtocol (
                  Handle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&DevPath,
                  gImageHandle,
                  NULL,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    FreePool (NewSName);
    return (SHELL_DEVICE_ERROR);
  }

  Status = gEfiShellProtocol->SetMap (DevPath, NewSName);
  FreePool (NewSName);
  if (EFI_ERROR (Status)) {
    return (SHELL_DEVICE_ERROR);
  }

  return (SHELL_SUCCESS);
}

STATIC CONST SHELL_PARAM_ITEM  MapParamList[] = {
  { L"-d",   TypeValue },
  { L"-r",   TypeFlag  },
  { L"-v",   TypeFlag  },
  { L"-c",   TypeFlag  },
  { L"-f",   TypeFlag  },
  { L"-u",   TypeFlag  },
  { L"-t",   TypeValue },
  { L"-sfo", TypeValue },
  { NULL,    TypeMax   }
};

/**
  The routine issues dummy read for every physical block device to cause
  the BlockIo re-installed if media change happened.
**/
VOID
ProbeForMediaChange (
  VOID
  )
{
  EFI_STATUS             Status;
  UINTN                  HandleCount;
  EFI_HANDLE             *Handles;
  EFI_BLOCK_IO_PROTOCOL  *BlockIo;
  UINTN                  Index;

  gBS->LocateHandleBuffer (
         ByProtocol,
         &gEfiBlockIoProtocolGuid,
         NULL,
         &HandleCount,
         &Handles
         );
  //
  // Probe for media change for every physical block io
  //
  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (
                    Handles[Index],
                    &gEfiBlockIoProtocolGuid,
                    (VOID **)&BlockIo
                    );
    if (!EFI_ERROR (Status)) {
      if (!BlockIo->Media->LogicalPartition) {
        //
        // Per spec:
        //   The function (ReadBlocks) must return EFI_NO_MEDIA or
        //   EFI_MEDIA_CHANGED even if LBA, BufferSize, or Buffer are invalid so the caller can probe
        //   for changes in media state.
        //
        BlockIo->ReadBlocks (
                   BlockIo,
                   BlockIo->Media->MediaId,
                   0,
                   0,
                   NULL
                   );
      }
    }
  }
}

/**
  Function for 'map' 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
ShellCommandRunMap (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS    Status;
  LIST_ENTRY    *Package;
  CHAR16        *ProblemParam;
  CONST CHAR16  *SName;
  CONST CHAR16  *Mapping;
  EFI_HANDLE    MapAsHandle;
  SHELL_STATUS  ShellStatus;
  BOOLEAN       SfoMode;
  BOOLEAN       ConstMode;
  BOOLEAN       NormlMode;
  CONST CHAR16  *Param1;
  CONST CHAR16  *TypeString;
  UINTN         TempStringLength;

  ProblemParam = NULL;
  Mapping      = NULL;
  SName        = NULL;
  ShellStatus  = SHELL_SUCCESS;
  MapAsHandle  = NULL;

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

  Status = CommandInit ();
  ASSERT_EFI_ERROR (Status);

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (MapParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR (Status)) {
    if ((Status == EFI_VOLUME_CORRUPTED) && (ProblemParam != NULL)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel2HiiHandle, L"map", ProblemParam);
      FreePool (ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT (FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    SfoMode   = ShellCommandLineGetFlag (Package, L"-sfo");
    ConstMode = ShellCommandLineGetFlag (Package, L"-c");
    NormlMode = ShellCommandLineGetFlag (Package, L"-f");
    if (ShellCommandLineGetFlag (Package, L"-?")) {
      ASSERT (FALSE);
    } else if (ShellCommandLineGetRawValue (Package, 3) != NULL) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_MANY), gShellLevel2HiiHandle, L"map");
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      //
      // Deleting a map name...
      //
      if (ShellCommandLineGetFlag (Package, L"-d")) {
        if (  ShellCommandLineGetFlag (Package, L"-r")
           || ShellCommandLineGetFlag (Package, L"-v")
           || ConstMode
           || NormlMode
           || ShellCommandLineGetFlag (Package, L"-u")
           || ShellCommandLineGetFlag (Package, L"-t")
              )
        {
          ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_CON), gShellLevel2HiiHandle, L"map");
          ShellStatus = SHELL_INVALID_PARAMETER;
        } else {
          SName = ShellCommandLineGetValue (Package, L"-d");
          if (SName != NULL) {
            Status = PerformMappingDelete (SName);
            if (EFI_ERROR (Status)) {
              if (Status == EFI_ACCESS_DENIED) {
                ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_AD), gShellLevel2HiiHandle, L"map");
                ShellStatus = SHELL_ACCESS_DENIED;
              } else if (Status == EFI_NOT_FOUND) {
                ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_MAP_NF), gShellLevel2HiiHandle, L"map", SName);
                ShellStatus = SHELL_INVALID_PARAMETER;
              } else {
                ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_UK), gShellLevel2HiiHandle, L"map", Status);
                ShellStatus = SHELL_UNSUPPORTED;
              }
            }
          } else {
            ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), gShellLevel2HiiHandle, L"map");
            ShellStatus = SHELL_INVALID_PARAMETER;
          }
        }
      } else if (  ShellCommandLineGetFlag (Package, L"-r")
                   //               || ShellCommandLineGetFlag(Package, L"-v")
                || ConstMode
                || NormlMode
                || ShellCommandLineGetFlag (Package, L"-u")
                || ShellCommandLineGetFlag (Package, L"-t")
                   )
      {
        ProbeForMediaChange ();
        if ( ShellCommandLineGetFlag (Package, L"-r")) {
          //
          // Do the reset
          //
          Status = ShellCommandCreateInitialMappingsAndPaths ();
          if (EFI_ERROR (Status)) {
            ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_UK), gShellLevel2HiiHandle, L"map", Status);
            ShellStatus = SHELL_UNSUPPORTED;
          }
        }

        if ((ShellStatus == SHELL_SUCCESS) && ShellCommandLineGetFlag (Package, L"-u")) {
          //
          // Do the Update
          //
          Status = ShellCommandUpdateMapping ();
          if (EFI_ERROR (Status)) {
            ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_UK), gShellLevel2HiiHandle, L"map", Status);
            ShellStatus = SHELL_UNSUPPORTED;
          }
        }

        if (ShellStatus == SHELL_SUCCESS) {
          Param1     = ShellCommandLineGetRawValue (Package, 1);
          TypeString = ShellCommandLineGetValue (Package, L"-t");
          if (  !ConstMode
             && !NormlMode
             && (TypeString  == NULL)
                )
          {
            //
            // now do the display...
            //
            ShellStatus = PerformMappingDisplay (
                            ShellCommandLineGetFlag (Package, L"-v"),
                            TRUE,
                            TRUE,
                            NULL,
                            SfoMode,
                            Param1,
                            TRUE
                            );
          } else {
            //
            // now do the display...
            //
            ShellStatus = PerformMappingDisplay2 (
                            ShellCommandLineGetFlag (Package, L"-v"),
                            ConstMode,
                            NormlMode,
                            TypeString,
                            SfoMode,
                            Param1
                            );
          }
        }
      } else {
        //
        // adding or displaying (there were no flags)
        //
        SName   = ShellCommandLineGetRawValue (Package, 1);
        Mapping = ShellCommandLineGetRawValue (Package, 2);
        if (  (SName == NULL)
           && (Mapping == NULL)
              )
        {
          //
          // display only since no flags
          //
          ShellStatus = PerformMappingDisplay (
                          ShellCommandLineGetFlag (Package, L"-v"),
                          TRUE,
                          TRUE,
                          NULL,
                          SfoMode,
                          NULL,
                          TRUE
                          );
        } else if (  (SName == NULL)
                  || (Mapping == NULL)
                     )
        {
          //
          // Display only the one specified
          //
          ShellStatus = PerformMappingDisplay (
                          FALSE,
                          FALSE,
                          FALSE,
                          NULL,
                          SfoMode,
                          SName, // note the variable here...
                          TRUE
                          );
        } else {
          if (ShellIsHexOrDecimalNumber (Mapping, TRUE, FALSE)) {
            MapAsHandle = ConvertHandleIndexToHandle (ShellStrToUintn (Mapping));
          } else {
            MapAsHandle = NULL;
          }

          if ((MapAsHandle == NULL) && (Mapping[StrLen (Mapping)-1] != L':')) {
            ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel2HiiHandle, L"map", Mapping);
            ShellStatus = SHELL_INVALID_PARAMETER;
          } else {
            TempStringLength = StrLen (SName);
            if (!IsNumberLetterOnly (SName, TempStringLength-((SName[TempStringLength-1] == L':') ? 1 : 0))) {
              ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel2HiiHandle, L"map", SName);
              ShellStatus = SHELL_INVALID_PARAMETER;
            }

            if (ShellStatus == SHELL_SUCCESS) {
              if (MapAsHandle != NULL) {
                ShellStatus = AddMappingFromHandle (MapAsHandle, SName);
              } else {
                ShellStatus = AddMappingFromMapping (Mapping, SName);
              }

              if (ShellStatus != SHELL_SUCCESS) {
                switch (ShellStatus) {
                  case SHELL_ACCESS_DENIED:
                    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_AD), gShellLevel2HiiHandle, L"map");
                    break;
                  case SHELL_INVALID_PARAMETER:
                    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel2HiiHandle, L"map", Mapping);
                    break;
                  case SHELL_DEVICE_ERROR:
                    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_MAP_NOF), gShellLevel2HiiHandle, L"map", Mapping);
                    break;
                  default:
                    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_ERR_UK), gShellLevel2HiiHandle, L"map", ShellStatus|MAX_BIT);
                }
              } else {
                //
                // now do the display...
                //
                ShellStatus = PerformMappingDisplay (
                                FALSE,
                                FALSE,
                                FALSE,
                                NULL,
                                SfoMode,
                                SName,
                                TRUE
                                );
              } // we were sucessful so do an output
            }
          } // got a valid map target
        } // got 2 variables
      } // we are adding a mapping
    } // got valid parameters
  }

  //
  // free the command line package
  //
  ShellCommandLineFreeVarList (Package);

  return (ShellStatus);
}
