/** @file
  Main file for ls shell level 2 function.

  (C) Copyright 2013-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 "UefiShellLevel2CommandsLib.h"
#include <Guid/FileSystemInfo.h>

UINTN  mDayOfMonth[] = { 31, 28, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 };

/**
  print out the list of files and directories from the LS command

  @param[in] Rec            TRUE to automatically recurse into each found directory
                            FALSE to only list the specified directory.
  @param[in] Attribs        List of required Attribute for display.
                            If 0 then all non-system and non-hidden files will be printed.
  @param[in] Sfo            TRUE to use Standard Format Output, FALSE otherwise
  @param[in] RootPath       String with starting path to search in.
  @param[in] SearchString   String with search string.
  @param[in] Found          Set to TRUE, if anyone were found.
  @param[in] Count          The count of bits enabled in Attribs.
  @param[in] ListUnfiltered TRUE to request listing the directory contents
                            unfiltered.

  @retval SHELL_SUCCESS     the printing was successful.
**/
STATIC
SHELL_STATUS
PrintLsOutput (
  IN CONST BOOLEAN  Rec,
  IN CONST UINT64   Attribs,
  IN CONST BOOLEAN  Sfo,
  IN CONST CHAR16   *RootPath,
  IN CONST CHAR16   *SearchString,
  IN       BOOLEAN  *Found,
  IN CONST UINTN    Count,
  IN CONST BOOLEAN  ListUnfiltered
  );

/**
  print out the standard format output volume entry.

  @param[in] TheList           a list of files from the volume.
**/
EFI_STATUS
PrintSfoVolumeInfoTableEntry (
  IN CONST EFI_SHELL_FILE_INFO  *TheList
  )
{
  EFI_STATUS            Status;
  EFI_SHELL_FILE_INFO   *Node;
  CHAR16                *DirectoryName;
  EFI_FILE_SYSTEM_INFO  *SysInfo;
  UINTN                 SysInfoSize;
  SHELL_FILE_HANDLE     ShellFileHandle;
  EFI_FILE_PROTOCOL     *EfiFpHandle;

  //
  // Get the first valid handle (directories)
  //
  for ( Node = (EFI_SHELL_FILE_INFO *)GetFirstNode (&TheList->Link)
        ; !IsNull (&TheList->Link, &Node->Link) && Node->Handle == NULL
        ; Node = (EFI_SHELL_FILE_INFO *)GetNextNode (&TheList->Link, &Node->Link)
        )
  {
  }

  if (Node->Handle == NULL) {
    DirectoryName = GetFullyQualifiedPath (((EFI_SHELL_FILE_INFO *)GetFirstNode (&TheList->Link))->FullName);
    if (DirectoryName == NULL) {
      return (EFI_OUT_OF_RESOURCES);
    }

    //
    // We need to open something up to get system information
    //
    Status = gEfiShellProtocol->OpenFileByName (
                                  DirectoryName,
                                  &ShellFileHandle,
                                  EFI_FILE_MODE_READ
                                  );

    ASSERT_EFI_ERROR (Status);
    FreePool (DirectoryName);

    //
    // Get the Volume Info from ShellFileHandle
    //
    SysInfo     = NULL;
    SysInfoSize = 0;
    EfiFpHandle = ConvertShellHandleToEfiFileProtocol (ShellFileHandle);
    Status      = EfiFpHandle->GetInfo (
                                 EfiFpHandle,
                                 &gEfiFileSystemInfoGuid,
                                 &SysInfoSize,
                                 SysInfo
                                 );

    if (Status == EFI_BUFFER_TOO_SMALL) {
      SysInfo = AllocateZeroPool (SysInfoSize);
      if (SysInfo == NULL) {
        gEfiShellProtocol->CloseFile (ShellFileHandle);
        return (EFI_OUT_OF_RESOURCES);
      }

      Status = EfiFpHandle->GetInfo (
                              EfiFpHandle,
                              &gEfiFileSystemInfoGuid,
                              &SysInfoSize,
                              SysInfo
                              );
    }

    ASSERT_EFI_ERROR (Status);

    gEfiShellProtocol->CloseFile (ShellFileHandle);
  } else {
    //
    // Get the Volume Info from Node->Handle
    //
    SysInfo     = NULL;
    SysInfoSize = 0;
    EfiFpHandle = ConvertShellHandleToEfiFileProtocol (Node->Handle);
    Status      = EfiFpHandle->GetInfo (
                                 EfiFpHandle,
                                 &gEfiFileSystemInfoGuid,
                                 &SysInfoSize,
                                 SysInfo
                                 );

    if (Status == EFI_BUFFER_TOO_SMALL) {
      SysInfo = AllocateZeroPool (SysInfoSize);
      if (SysInfo == NULL) {
        return (EFI_OUT_OF_RESOURCES);
      }

      Status = EfiFpHandle->GetInfo (
                              EfiFpHandle,
                              &gEfiFileSystemInfoGuid,
                              &SysInfoSize,
                              SysInfo
                              );
    }

    ASSERT_EFI_ERROR (Status);
  }

  ShellPrintHiiDefaultEx (
    STRING_TOKEN (STR_GEN_SFO_HEADER),
    gShellLevel2HiiHandle,
    L"ls"
    );
  //
  // print VolumeInfo table
  //
  ASSERT (SysInfo != NULL);
  ShellPrintHiiEx (
    0,
    gST->ConOut->Mode->CursorRow,
    NULL,
    STRING_TOKEN (STR_LS_SFO_VOLINFO),
    gShellLevel2HiiHandle,
    SysInfo->VolumeLabel,
    SysInfo->VolumeSize,
    SysInfo->ReadOnly ? L"TRUE" : L"FALSE",
    SysInfo->FreeSpace,
    SysInfo->BlockSize
    );

  SHELL_FREE_NON_NULL (SysInfo);

  return (Status);
}

/**
  print out the info on a single file.

  @param[in] Sfo      TRUE if in SFO, false otherwise.
  @param[in] TheNode  the EFI_SHELL_FILE_INFO node to print out information on.
  @param[in] Files    incremented if a file is printed.
  @param[in] Size     incremented by file size.
  @param[in] Dirs     incremented if a directory is printed.

**/
VOID
PrintFileInformation (
  IN CONST BOOLEAN              Sfo,
  IN CONST EFI_SHELL_FILE_INFO  *TheNode,
  IN UINT64                     *Files,
  IN UINT64                     *Size,
  IN UINT64                     *Dirs
  )
{
  ASSERT (Files    != NULL);
  ASSERT (Size     != NULL);
  ASSERT (Dirs     != NULL);
  ASSERT (TheNode  != NULL);

  if (Sfo) {
    //
    // Print the FileInfo Table
    //
    ShellPrintHiiEx (
      0,
      gST->ConOut->Mode->CursorRow,
      NULL,
      STRING_TOKEN (STR_LS_SFO_FILEINFO),
      gShellLevel2HiiHandle,
      TheNode->FullName,
      TheNode->Info->FileSize,
      TheNode->Info->PhysicalSize,
      (TheNode->Info->Attribute & EFI_FILE_ARCHIVE)   != 0 ? L"a" : L"",
      (TheNode->Info->Attribute & EFI_FILE_DIRECTORY) != 0 ? L"d" : L"",
      (TheNode->Info->Attribute & EFI_FILE_HIDDEN)    != 0 ? L"h" : L"",
      (TheNode->Info->Attribute & EFI_FILE_READ_ONLY) != 0 ? L"r" : L"",
      (TheNode->Info->Attribute & EFI_FILE_SYSTEM)    != 0 ? L"s" : L"",
      TheNode->Info->CreateTime.Hour,
      TheNode->Info->CreateTime.Minute,
      TheNode->Info->CreateTime.Second,
      TheNode->Info->CreateTime.Day,
      TheNode->Info->CreateTime.Month,
      TheNode->Info->CreateTime.Year,
      TheNode->Info->LastAccessTime.Hour,
      TheNode->Info->LastAccessTime.Minute,
      TheNode->Info->LastAccessTime.Second,
      TheNode->Info->LastAccessTime.Day,
      TheNode->Info->LastAccessTime.Month,
      TheNode->Info->LastAccessTime.Year,
      TheNode->Info->ModificationTime.Hour,
      TheNode->Info->ModificationTime.Minute,
      TheNode->Info->ModificationTime.Second,
      TheNode->Info->ModificationTime.Day,
      TheNode->Info->ModificationTime.Month,
      TheNode->Info->ModificationTime.Year
      );
  } else {
    //
    // print this one out...
    // first print the universal start, next print the type specific name format, last print the CRLF
    //
    ShellPrintHiiDefaultEx (
      STRING_TOKEN (STR_LS_LINE_START_ALL),
      gShellLevel2HiiHandle,
      &TheNode->Info->ModificationTime,
      (TheNode->Info->Attribute & EFI_FILE_DIRECTORY) != 0 ? L"<DIR>" : L"",
      (TheNode->Info->Attribute & EFI_FILE_READ_ONLY) != 0 ? L'r' : L' ',
      TheNode->Info->FileSize
      );
    if (TheNode->Info->Attribute & EFI_FILE_DIRECTORY) {
      (*Dirs)++;
      ShellPrintHiiDefaultEx (
        STRING_TOKEN (STR_LS_LINE_END_DIR),
        gShellLevel2HiiHandle,
        TheNode->FileName
        );
    } else {
      (*Files)++;
      (*Size) += TheNode->Info->FileSize;
      if (  (gUnicodeCollation->StriColl (gUnicodeCollation, (CHAR16 *)L".nsh", (CHAR16 *)&(TheNode->FileName[StrLen (TheNode->FileName) - 4])) == 0)
         || (gUnicodeCollation->StriColl (gUnicodeCollation, (CHAR16 *)L".efi", (CHAR16 *)&(TheNode->FileName[StrLen (TheNode->FileName) - 4])) == 0)
            )
      {
        ShellPrintHiiDefaultEx (
          STRING_TOKEN (STR_LS_LINE_END_EXE),
          gShellLevel2HiiHandle,
          TheNode->FileName
          );
      } else {
        ShellPrintHiiDefaultEx (
          STRING_TOKEN (STR_LS_LINE_END_FILE),
          gShellLevel2HiiHandle,
          TheNode->FileName
          );
      }
    }
  }
}

/**
  print out the header when not using standard format output.

  @param[in] Path           String with starting path.
**/
VOID
PrintNonSfoHeader (
  IN CONST CHAR16  *Path
  )
{
  CHAR16  *DirectoryName;

  //
  // get directory name from path...
  //
  DirectoryName = GetFullyQualifiedPath (Path);

  if (DirectoryName != NULL) {
    //
    // print header
    //
    ShellPrintHiiEx (
      0,
      gST->ConOut->Mode->CursorRow,
      NULL,
      STRING_TOKEN (STR_LS_HEADER_LINE1),
      gShellLevel2HiiHandle,
      DirectoryName
      );

    SHELL_FREE_NON_NULL (DirectoryName);
  }
}

/**
  print out the footer when not using standard format output.

  @param[in] Files            The number of files.
  @param[in] Size             The size of files in bytes.
  @param[in] Dirs             The number of directories.
**/
VOID
PrintNonSfoFooter (
  IN UINT64  Files,
  IN UINT64  Size,
  IN UINT64  Dirs
  )
{
  //
  // print footer
  //
  ShellPrintHiiDefaultEx (
    STRING_TOKEN (STR_LS_FOOTER_LINE),
    gShellLevel2HiiHandle,
    Files,
    Size,
    Dirs
    );
}

/**
  Change the file time to local time based on the timezone.

  @param[in] Time               The file time.
  @param[in] LocalTimeZone      Local time zone.
**/
VOID
FileTimeToLocalTime (
  IN EFI_TIME  *Time,
  IN INT16     LocalTimeZone
  )
{
  INTN  MinuteDiff;
  INTN  TempMinute;
  INTN  HourNumberOfTempMinute;
  INTN  TempHour;
  INTN  DayNumberOfTempHour;
  INTN  TempDay;
  INTN  MonthNumberOfTempDay;
  INTN  TempMonth;
  INTN  YearNumberOfTempMonth;
  INTN  MonthRecord;

  ASSERT ((Time->TimeZone >= -1440) && (Time->TimeZone <= 1440));
  ASSERT ((LocalTimeZone >= -1440) && (LocalTimeZone <= 1440));
  ASSERT ((Time->Month >= 1) && (Time->Month <= 12));

  if (Time->TimeZone == LocalTimeZone) {
    //
    // if the file timezone is equal to the local timezone, there is no need to adjust the file time.
    //
    return;
  }

  if (((Time->Year % 4 == 0) && (Time->Year / 100 != 0)) || (Time->Year % 400 == 0)) {
    //
    // Day in February of leap year is 29.
    //
    mDayOfMonth[1] = 29;
  }

  MinuteDiff = Time->TimeZone - LocalTimeZone;
  TempMinute = Time->Minute + MinuteDiff;

  //
  // Calculate Time->Minute
  // TempHour will be used to calculate Time->Hour
  //
  HourNumberOfTempMinute = TempMinute / 60;
  if (TempMinute < 0) {
    HourNumberOfTempMinute--;
  }

  TempHour     = Time->Hour + HourNumberOfTempMinute;
  Time->Minute = (UINT8)(TempMinute - 60 * HourNumberOfTempMinute);

  //
  // Calculate Time->Hour
  // TempDay will be used to calculate Time->Day
  //
  DayNumberOfTempHour = TempHour / 24;
  if (TempHour < 0) {
    DayNumberOfTempHour--;
  }

  TempDay    = Time->Day + DayNumberOfTempHour;
  Time->Hour = (UINT8)(TempHour - 24 * DayNumberOfTempHour);

  //
  // Calculate Time->Day
  // TempMonth will be used to calculate Time->Month
  //
  MonthNumberOfTempDay = (TempDay - 1) / (INTN)mDayOfMonth[Time->Month - 1];
  MonthRecord          = (INTN)(Time->Month);
  if (TempDay - 1 < 0) {
    MonthNumberOfTempDay--;
    MonthRecord--;
  }

  TempMonth = Time->Month + MonthNumberOfTempDay;
  Time->Day = (UINT8)(TempDay - (INTN)mDayOfMonth[(MonthRecord - 1 + 12) % 12] * MonthNumberOfTempDay);

  //
  // Calculate Time->Month, Time->Year
  //
  YearNumberOfTempMonth = (TempMonth - 1) / 12;
  if (TempMonth - 1 < 0) {
    YearNumberOfTempMonth--;
  }

  Time->Month = (UINT8)(TempMonth - 12 * (YearNumberOfTempMonth));
  Time->Year  = (UINT16)(Time->Year + YearNumberOfTempMonth);
}

/** Update the file time to local time.

  @param[in] Node   File Node to update.
**/
STATIC
VOID
UpdateFileLocalTime (
  IN  EFI_SHELL_FILE_INFO  *Node
  )
{
  EFI_STATUS  Status;
  EFI_TIME    LocalTime;

  Status = gRT->GetTime (&LocalTime, NULL);
  if (EFI_ERROR (Status) || (LocalTime.TimeZone == EFI_UNSPECIFIED_TIMEZONE)) {
    return;
  }

  if ((Node->Info->CreateTime.TimeZone != EFI_UNSPECIFIED_TIMEZONE) &&
      ((Node->Info->CreateTime.Month >= 1) && (Node->Info->CreateTime.Month <= 12)))
  {
    //
    // FileTimeToLocalTime () requires Month is in a valid range, other buffer out-of-band access happens.
    //
    FileTimeToLocalTime (&Node->Info->CreateTime, LocalTime.TimeZone);
  }

  if ((Node->Info->LastAccessTime.TimeZone != EFI_UNSPECIFIED_TIMEZONE) &&
      ((Node->Info->LastAccessTime.Month >= 1) && (Node->Info->LastAccessTime.Month <= 12)))
  {
    FileTimeToLocalTime (&Node->Info->LastAccessTime, LocalTime.TimeZone);
  }

  if ((Node->Info->ModificationTime.TimeZone != EFI_UNSPECIFIED_TIMEZONE) &&
      ((Node->Info->ModificationTime.Month >= 1) && (Node->Info->ModificationTime.Month <= 12)))
  {
    FileTimeToLocalTime (&Node->Info->ModificationTime, LocalTime.TimeZone);
  }
}

/** Get Corrected Path.

  @param[in] CorrectedPath   String with the corrected path.
  @param[in] RootPath        String with starting path to search in.
  @param[in] SearchString    String with search string.

  @return Shell Status.
**/
STATIC
SHELL_STATUS
GetCorrectedPath (
  IN            CHAR16  **CorrectedPath,
  IN      CONST CHAR16  *RootPath,
  IN      CONST CHAR16  *SearchString
  )
{
  UINTN   LongestPath;
  CHAR16  *Path;

  ASSERT (CorrectedPath != NULL);

  LongestPath    = 0;
  Path           = NULL;
  *CorrectedPath = NULL;

  Path = StrnCatGrow (&Path, &LongestPath, RootPath, 0);
  if (Path == NULL) {
    return SHELL_OUT_OF_RESOURCES;
  }

  if (  (Path[StrLen (Path)-1] != L'\\')
     && (Path[StrLen (Path)-1] != L'/'))
  {
    Path = StrnCatGrow (&Path, &LongestPath, L"\\", 0);
  }

  Path = StrnCatGrow (&Path, &LongestPath, SearchString, 0);
  if (Path == NULL) {
    return SHELL_OUT_OF_RESOURCES;
  }

  *CorrectedPath = Path;
  return SHELL_SUCCESS;
}

/**
  Print out the list of files and directories from the LS command at the current level.

  @param[in] Attribs        List of required Attribute for display.
                            If 0 then all non-system and non-hidden files will be printed.
  @param[in] Sfo            TRUE to use Standard Format Output, FALSE otherwise
  @param[in] RootPath       String with starting path to search in.
  @param[in] SearchString   String with search string.
  @param[in] Found          Set to TRUE, if anyone were found.
  @param[in] Count          The count of bits enabled in Attribs.
  @param[in] IsRecursive    TRUE if called from a recursion.

  @retval SHELL_SUCCESS     the printing was successful.
**/
STATIC
SHELL_STATUS
PrintLsOutputCurr (
  IN CONST UINT64   Attribs,
  IN CONST BOOLEAN  Sfo,
  IN CONST CHAR16   *RootPath,
  IN CONST CHAR16   *SearchString,
  IN       BOOLEAN  *Found,
  IN CONST UINTN    Count,
  IN       BOOLEAN  IsRecursive
  )
{
  EFI_STATUS           Status;
  EFI_SHELL_FILE_INFO  *ListHead;
  EFI_SHELL_FILE_INFO  *Node;
  SHELL_STATUS         ShellStatus;
  UINT64               FileCount;
  UINT64               DirCount;
  UINT64               FileSize;
  CHAR16               *CorrectedPath;
  BOOLEAN              FoundOne;
  BOOLEAN              HeaderPrinted;

  ASSERT (Found != NULL);

  HeaderPrinted = FALSE;
  FileCount     = 0;
  DirCount      = 0;
  FileSize      = 0;
  ListHead      = NULL;
  ShellStatus   = SHELL_SUCCESS;
  FoundOne      = FALSE;

  ShellStatus = GetCorrectedPath (&CorrectedPath, RootPath, SearchString);
  if (ShellStatus != SHELL_SUCCESS) {
    return ShellStatus;
  }

  PathCleanUpDirectories (CorrectedPath);

  Status = ShellOpenFileMetaArg ((CHAR16 *)CorrectedPath, EFI_FILE_MODE_READ, &ListHead);
  if (!EFI_ERROR (Status)) {
    if ((ListHead == NULL) || IsListEmpty (&ListHead->Link)) {
      SHELL_FREE_NON_NULL (CorrectedPath);
      return (SHELL_SUCCESS);
    }

    if (Sfo && !IsRecursive) {
      PrintSfoVolumeInfoTableEntry (ListHead);
    }

    if (!Sfo) {
      //
      // Sort the file list by FileName, stably.
      //
      // If the call below fails, then the EFI_SHELL_FILE_INFO list anchored to
      // ListHead will not be changed in any way.
      //
      ShellSortFileList (
        &ListHead,
        NULL,                       // Duplicates
        ShellSortFileListByFileName
        );
    }

    for ( Node = (EFI_SHELL_FILE_INFO *)GetFirstNode (&ListHead->Link)
          ; !IsNull (&ListHead->Link, &Node->Link)
          ; Node = (EFI_SHELL_FILE_INFO *)GetNextNode (&ListHead->Link, &Node->Link)
          )
    {
      if (ShellGetExecutionBreakFlag ()) {
        ShellStatus = SHELL_ABORTED;
        break;
      }

      ASSERT (Node != NULL);

      //
      // Change the file time to local time.
      //
      UpdateFileLocalTime (Node);

      ASSERT (Node->Info != NULL);
      ASSERT ((Node->Info->Attribute & EFI_FILE_VALID_ATTR) == Node->Info->Attribute);
      if (Attribs == 0) {
        //
        // NOT system & NOT hidden
        //
        if (  (Node->Info->Attribute & EFI_FILE_SYSTEM)
           || (Node->Info->Attribute & EFI_FILE_HIDDEN)
              )
        {
          continue;
        }
      } else if ((Attribs != EFI_FILE_VALID_ATTR) ||
                 (Count == 5))
      {
        //
        // Only matches the bits which "Attribs" contains, not
        // all files/directories with any of the bits.
        // Count == 5 is used to tell the difference between a user
        // specifying all bits (EX: -arhsda) and just specifying
        // -a (means display all files with any attribute).
        //
        if ((Node->Info->Attribute & Attribs) != Attribs) {
          continue;
        }
      }

      if (!Sfo && !HeaderPrinted) {
        PathRemoveLastItem (CorrectedPath);
        PrintNonSfoHeader (CorrectedPath);
      }

      PrintFileInformation (Sfo, Node, &FileCount, &FileSize, &DirCount);
      FoundOne      = TRUE;
      HeaderPrinted = TRUE;
    }

    if (!Sfo && (ShellStatus != SHELL_ABORTED) && HeaderPrinted) {
      PrintNonSfoFooter (FileCount, FileSize, DirCount);
    }

    ShellCloseFileMetaArg (&ListHead);
  }

  if (FoundOne) {
    *Found = FoundOne;
  }

  SHELL_FREE_NON_NULL (CorrectedPath);
  return ShellStatus;
}

/**
  print out the list of files and directories from the LS command

  @param[in] Rec            TRUE to automatically recurse into each found directory
                            FALSE to only list the specified directory.
  @param[in] Attribs        List of required Attribute for display.
                            If 0 then all non-system and non-hidden files will be printed.
  @param[in] Sfo            TRUE to use Standard Format Output, FALSE otherwise
  @param[in] RootPath       String with starting path to search in.
  @param[in] SearchString   String with search string.
  @param[in] Found          Set to TRUE, if anyone were found.
  @param[in] Count          The count of bits enabled in Attribs.

  @retval SHELL_SUCCESS     the printing was successful.
**/
STATIC
SHELL_STATUS
PrintLsOutputRec (
  IN CONST BOOLEAN  Rec,
  IN CONST UINT64   Attribs,
  IN CONST BOOLEAN  Sfo,
  IN CONST CHAR16   *RootPath,
  IN CONST CHAR16   *SearchString,
  IN       BOOLEAN  *Found,
  IN CONST UINTN    Count
  )
{
  EFI_STATUS           Status;
  EFI_SHELL_FILE_INFO  *ListHead;
  EFI_SHELL_FILE_INFO  *Node;
  SHELL_STATUS         ShellStatus;
  CHAR16               *CorrectedPath;

  //
  // Re-Open all the files under the starting path for directories that didnt necessarily match our file filter
  //
  ShellStatus = GetCorrectedPath (&CorrectedPath, RootPath, L"*");
  if (ShellStatus != SHELL_SUCCESS) {
    return ShellStatus;
  }

  Status = ShellOpenFileMetaArg ((CHAR16 *)CorrectedPath, EFI_FILE_MODE_READ, &ListHead);
  if (EFI_ERROR (Status)) {
    SHELL_FREE_NON_NULL (CorrectedPath);
    return ShellStatus;
  }

  for ( Node = (EFI_SHELL_FILE_INFO *)GetFirstNode (&ListHead->Link)
        ; !IsNull (&ListHead->Link, &Node->Link) && ShellStatus == SHELL_SUCCESS
        ; Node = (EFI_SHELL_FILE_INFO *)GetNextNode (&ListHead->Link, &Node->Link)
        )
  {
    if (ShellGetExecutionBreakFlag ()) {
      ShellStatus = SHELL_ABORTED;
      break;
    }

    //
    // recurse on any directory except the traversing ones...
    //
    if (((Node->Info->Attribute & EFI_FILE_DIRECTORY) != EFI_FILE_DIRECTORY) ||
        IsDotOrDotDot (Node->FileName))
    {
      continue;
    }

    ShellStatus = PrintLsOutput (
                    Rec,
                    Attribs,
                    Sfo,
                    Node->FullName,
                    SearchString,
                    Found,
                    Count,
                    FALSE
                    );

    //
    // Since it's running recursively, we have to break immediately when returned SHELL_ABORTED
    //
    if (ShellStatus == SHELL_ABORTED) {
      break;
    }
  }

  ShellCloseFileMetaArg (&ListHead);
  SHELL_FREE_NON_NULL (CorrectedPath);
  return ShellStatus;
}

/**
  print out the list of files and directories from the LS command

  @param[in] Rec            TRUE to automatically recurse into each found directory
                            FALSE to only list the specified directory.
  @param[in] Attribs        List of required Attribute for display.
                            If 0 then all non-system and non-hidden files will be printed.
  @param[in] Sfo            TRUE to use Standard Format Output, FALSE otherwise
  @param[in] RootPath       String with starting path to search in.
  @param[in] SearchString   String with search string.
  @param[in] Found          Set to TRUE, if anyone were found.
  @param[in] Count          The count of bits enabled in Attribs.
  @param[in] ListUnfiltered TRUE to request listing the directory contents
                            unfiltered.

  @retval SHELL_SUCCESS     the printing was successful.
**/
SHELL_STATUS
PrintLsOutput (
  IN CONST BOOLEAN  Rec,
  IN CONST UINT64   Attribs,
  IN CONST BOOLEAN  Sfo,
  IN CONST CHAR16   *RootPath,
  IN CONST CHAR16   *SearchString,
  IN       BOOLEAN  *Found,
  IN CONST UINTN    Count,
  IN CONST BOOLEAN  ListUnfiltered
  )
{
  SHELL_STATUS  ShellStatus;
  BOOLEAN       FoundOne;
  BOOLEAN       IsRecursive;

  ShellStatus = SHELL_SUCCESS;
  FoundOne    = FALSE;
  IsRecursive = (Found != NULL);

  ShellStatus = PrintLsOutputCurr (
                  Attribs,
                  Sfo,
                  RootPath,
                  SearchString,
                  &FoundOne,
                  Count,
                  IsRecursive
                  );

  if (Rec && (ShellStatus != SHELL_ABORTED)) {
    ShellStatus = PrintLsOutputRec (
                    Rec,
                    Attribs,
                    Sfo,
                    RootPath,
                    SearchString,
                    &FoundOne,
                    Count
                    );
  }

  if (!IsRecursive && !FoundOne) {
    if (ListUnfiltered) {
      //
      // When running "ls" without any filtering request, avoid outputing
      // "File not found" when the directory is entirely empty, but print
      // header and footer stating "0 File(s), 0 Dir(s)".
      //
      if (!Sfo) {
        PrintNonSfoHeader (RootPath);
        if (ShellStatus != SHELL_ABORTED) {
          PrintNonSfoFooter (0, 0, 0);
        }
      }
    } else {
      return (SHELL_NOT_FOUND);
    }
  }

  if ((Found != NULL) && FoundOne) {
    *Found = FoundOne;
  }

  return (ShellStatus);
}

STATIC CONST SHELL_PARAM_ITEM  LsParamList[] = {
  { L"-r",   TypeFlag  },
  { L"-a",   TypeStart },
  { L"-sfo", TypeFlag  },
  { NULL,    TypeMax   }
};

/** Main function of the 'Ls' command.

  @param[in] Package    List of input parameter for the command.
**/
STATIC
SHELL_STATUS
MainCmdLs (
  LIST_ENTRY  *Package
  )
{
  CONST CHAR16  *Attribs;
  SHELL_STATUS  ShellStatus;
  UINT64        RequiredAttributes;
  CONST CHAR16  *PathName;
  CONST CHAR16  *CurDir;
  UINTN         Count;
  CHAR16        *FullPath;
  UINTN         Size;
  CHAR16        *SearchString;
  BOOLEAN       ListUnfiltered;

  Size               = 0;
  FullPath           = NULL;
  Attribs            = NULL;
  ShellStatus        = SHELL_SUCCESS;
  RequiredAttributes = 0;
  PathName           = NULL;
  SearchString       = NULL;
  CurDir             = NULL;
  Count              = 0;
  ListUnfiltered     = FALSE;

  //
  // check for "-?"
  //
  if (ShellCommandLineGetFlag (Package, L"-?")) {
    ASSERT (FALSE);
  }

  if (ShellCommandLineGetCount (Package) > 2) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_TOO_MANY), gShellLevel2HiiHandle, L"ls");
    return SHELL_INVALID_PARAMETER;
  }

  //
  // check for -a
  //
  if (ShellCommandLineGetFlag (Package, L"-a")) {
    for ( Attribs = ShellCommandLineGetValue (Package, L"-a")
          ; Attribs != NULL && *Attribs != CHAR_NULL && ShellStatus == SHELL_SUCCESS
          ; Attribs++
          )
    {
      switch (*Attribs) {
        case L'a':
        case L'A':
          RequiredAttributes |= EFI_FILE_ARCHIVE;
          Count++;
          continue;
        case L's':
        case L'S':
          RequiredAttributes |= EFI_FILE_SYSTEM;
          Count++;
          continue;
        case L'h':
        case L'H':
          RequiredAttributes |= EFI_FILE_HIDDEN;
          Count++;
          continue;
        case L'r':
        case L'R':
          RequiredAttributes |= EFI_FILE_READ_ONLY;
          Count++;
          continue;
        case L'd':
        case L'D':
          RequiredAttributes |= EFI_FILE_DIRECTORY;
          Count++;
          continue;
        default:
          ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_ATTRIBUTE), gShellLevel2HiiHandle, L"ls", ShellCommandLineGetValue (Package, L"-a"));
          ShellStatus = SHELL_INVALID_PARAMETER;
          break;
      } // switch
    } // for loop

    //
    // if nothing is specified all are specified
    //
    if (RequiredAttributes == 0) {
      RequiredAttributes = EFI_FILE_VALID_ATTR;
    }
  } // if -a present

  if (ShellStatus != SHELL_SUCCESS) {
    return ShellStatus;
  }

  PathName = ShellCommandLineGetRawValue (Package, 1);
  if (PathName == NULL) {
    //
    // Nothing specified... must start from current directory
    //
    CurDir = gEfiShellProtocol->GetCurDir (NULL);
    if (CurDir == NULL) {
      ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_NO_CWD), gShellLevel2HiiHandle, L"ls");
      return SHELL_NOT_FOUND;
    }

    ListUnfiltered = TRUE;
    //
    // Copy to the 2 strings for starting path and file search string
    //
    ASSERT (SearchString == NULL);
    ASSERT (FullPath == NULL);
    StrnCatGrow (&SearchString, NULL, L"*", 0);
    StrnCatGrow (&FullPath, NULL, CurDir, 0);
    Size = FullPath != NULL ? StrSize (FullPath) : 0;
    StrnCatGrow (&FullPath, &Size, L"\\", 0);
  } else {
    if ((StrStr (PathName, L":") == NULL) && (gEfiShellProtocol->GetCurDir (NULL) == NULL)) {
      //
      // If we got something and it doesnt have a fully qualified path, then we needed to have a CWD.
      //
      ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_NO_CWD), gShellLevel2HiiHandle, L"ls");
      return SHELL_NOT_FOUND;
    }

    //
    // We got a valid fully qualified path or we have a CWD
    //
    ASSERT ((FullPath == NULL && Size == 0) || (FullPath != NULL));
    if (StrStr (PathName, L":") == NULL) {
      StrnCatGrow (&FullPath, &Size, gEfiShellProtocol->GetCurDir (NULL), 0);
      if (FullPath == NULL) {
        ShellCommandLineFreeVarList (Package);
        return SHELL_OUT_OF_RESOURCES;
      }

      Size = FullPath != NULL ? StrSize (FullPath) : 0;
      StrnCatGrow (&FullPath, &Size, L"\\", 0);
    }

    StrnCatGrow (&FullPath, &Size, PathName, 0);
    if (FullPath == NULL) {
      ShellCommandLineFreeVarList (Package);
      return SHELL_OUT_OF_RESOURCES;
    }

    if (ShellIsDirectory (PathName) == EFI_SUCCESS) {
      //
      // is listing ends with a directory, then we list all files in that directory
      //
      ListUnfiltered = TRUE;
      StrnCatGrow (&SearchString, NULL, L"*", 0);
    } else {
      //
      // must split off the search part that applies to files from the end of the directory part
      //
      StrnCatGrow (&SearchString, NULL, FullPath, 0);
      if (SearchString == NULL) {
        FreePool (FullPath);
        ShellCommandLineFreeVarList (Package);
        return SHELL_OUT_OF_RESOURCES;
      }

      PathRemoveLastItem (FullPath);
      CopyMem (SearchString, SearchString + StrLen (FullPath), StrSize (SearchString + StrLen (FullPath)));
    }
  }

  ShellStatus = PrintLsOutput (
                  ShellCommandLineGetFlag (Package, L"-r"),
                  RequiredAttributes,
                  ShellCommandLineGetFlag (Package, L"-sfo"),
                  FullPath,
                  SearchString,
                  NULL,
                  Count,
                  ListUnfiltered
                  );
  if (ShellStatus == SHELL_NOT_FOUND) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_LS_FILE_NOT_FOUND), gShellLevel2HiiHandle, L"ls", FullPath);
  } else if (ShellStatus == SHELL_INVALID_PARAMETER) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel2HiiHandle, L"ls", FullPath);
  } else if (ShellStatus == SHELL_ABORTED) {
    //
    // Ignore aborting.
    //
  } else if (ShellStatus != SHELL_SUCCESS) {
    ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel2HiiHandle, L"ls", FullPath);
  }

  //
  // Free memory allocated
  //
  SHELL_FREE_NON_NULL (SearchString);
  SHELL_FREE_NON_NULL (FullPath);

  return ShellStatus;
}

/**
  Function for 'ls' 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
ShellCommandRunLs (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS    Status;
  LIST_ENTRY    *Package;
  CHAR16        *ProblemParam;
  SHELL_STATUS  ShellStatus;

  ProblemParam = NULL;
  ShellStatus  = SHELL_SUCCESS;

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

  //
  // Fix local copies of the protocol pointers
  //
  Status = CommandInit ();
  ASSERT_EFI_ERROR (Status);

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (LsParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR (Status)) {
    if ((Status == EFI_VOLUME_CORRUPTED) && (ProblemParam != NULL)) {
      ShellPrintHiiDefaultEx (STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel2HiiHandle, L"ls", ProblemParam);
      FreePool (ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT (FALSE);
    }

    return ShellStatus;
  }

  ShellStatus = MainCmdLs (Package);

  //
  // Free memory allocated
  //
  ShellCommandLineFreeVarList (Package);

  return (ShellStatus);
}
