/** @file
File IO routines inspired by Streams with an EFI flavor

Copyright (c) 2007, Intel Corporation. All rights reserved.<BR>
Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>

This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

Basic support for opening files on different device types. The device string
is in the form of DevType:Path. Current DevType is required as there is no
current mounted device concept of current working directory concept implement
by this library.

Device names are case insensative and only check the leading characters for 
unique matches. Thus the following are all the same:
LoadFile0:
l0:
L0:
Lo0:

Supported Device Names:
A0x1234:0x12 - A memory buffer starting at address 0x1234 for 0x12 bytes
l1:          - EFI LoadFile device one.
B0:          - EFI BlockIo zero.
fs3:         - EFI Simple File System device 3
Fv2:         - EFI Firmware VOlume device 2
10.0.1.102:  - TFTP service IP followed by the file name
**/

#include <PiDxe.h>
#include <Protocol/BlockIo.h>             
#include <Protocol/DiskIo.h>             
#include <Protocol/SimpleFileSystem.h>      
#include <Protocol/FirmwareVolume2.h>        
#include <Protocol/LoadFile.h>
#include <Protocol/FirmwareVolumeBlock.h>  
#include <Guid/FileInfo.h>
#include <Library/BaseLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DevicePathLib.h>
#include <Library/PrintLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiLib.h>   
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/EfiFileLib.h>
#include <Library/PcdLib.h>
#include <Library/EblNetworkLib.h>


CHAR8 *gCwd = NULL;

CONST EFI_GUID gZeroGuid  = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };

#define EFI_OPEN_FILE_GUARD_HEADER  0x4B4D4641
#define EFI_OPEN_FILE_GUARD_FOOTER  0x444D5A56

// Need to defend against this overflowing
#define MAX_CMD_LINE  0x200

typedef struct {
  UINT32            Header;
  EFI_OPEN_FILE     File;
  UINT32            Footer;
} EFI_OPEN_FILE_GUARD;


// globals to store current open device info
EFI_HANDLE            *mBlkIo = NULL;
UINTN                 mBlkIoCount = 0;

EFI_HANDLE            *mFs = NULL;
UINTN                 mFsCount = 0;
// mFsInfo[] array entries must match mFs[] handles
EFI_FILE_SYSTEM_INFO  **mFsInfo = NULL;

EFI_HANDLE            *mFv = NULL;
UINTN                 mFvCount = 0;
EFI_HANDLE            *mLoadFile = NULL;
UINTN                 mLoadFileCount = 0;



/**
Internal worker function to validate a File handle.

@param  File    Open File Handle

@return TRUE    File is valid
@return FALSE   File is not valid


**/
BOOLEAN
FileHandleValid (
  IN EFI_OPEN_FILE  *File
  )
{
  EFI_OPEN_FILE_GUARD  *GuardFile;

  // Look right before and after file structure for the correct signatures
  GuardFile = BASE_CR (File, EFI_OPEN_FILE_GUARD, File);
  if ((GuardFile->Header != EFI_OPEN_FILE_GUARD_HEADER) ||
    (GuardFile->Footer != EFI_OPEN_FILE_GUARD_FOOTER) ) {
      return FALSE;
    }

    return TRUE;
}

/**
Internal worker function. If Buffer is not NULL free it.

@param  Buffer    Buffer to FreePool()

**/
VOID
EblFreePool (
  IN  VOID  *Buffer
  )
{
  if (Buffer != NULL) {
    FreePool (Buffer);
  }
}

/**
Update Device List Global Variables

**/
VOID
EblUpdateDeviceLists (
  VOID
  )
{
  EFI_STATUS                        Status;
  UINTN                             Size;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL   *Fs;
  EFI_FILE_HANDLE                   Root;
  UINTN                             Index;

  if (mBlkIo != NULL) {
    FreePool (mBlkIo);
  }
  gBS->LocateHandleBuffer (ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &mBlkIoCount, &mBlkIo);



  if (mFv != NULL) {
    FreePool (mFv);
  }
  gBS->LocateHandleBuffer (ByProtocol, &gEfiFirmwareVolume2ProtocolGuid, NULL, &mFvCount, &mFv);

  if (mLoadFile != NULL) {
    FreePool (mLoadFile);
  }
  gBS->LocateHandleBuffer (ByProtocol, &gEfiLoadFileProtocolGuid, NULL, &mLoadFileCount, &mLoadFile);

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

  if (&mFsInfo[0] != NULL) {
    // Need to Free the mFsInfo prior to reclaculating mFsCount so don't move this code
    for (Index = 0; Index < mFsCount; Index++) {
      if (mFsInfo[Index] != NULL) {
        FreePool (mFsInfo[Index]);
      }
    }
    FreePool (mFsInfo);
  }

  gBS->LocateHandleBuffer (ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &mFsCount, &mFs);


  mFsInfo = AllocateZeroPool (mFsCount * sizeof (EFI_FILE_SYSTEM_INFO *));
  if (mFsInfo == NULL) {
    // If we can't do this then we can't support file system entries
    mFsCount = 0;
  } else {
    // Loop through all the file system structures and cache the file system info data
    for (Index =0; Index < mFsCount; Index++) {
      Status = gBS->HandleProtocol (mFs[Index], &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs);
      if (!EFI_ERROR (Status)) {
        Status = Fs->OpenVolume (Fs, &Root);
        if (!EFI_ERROR (Status)) {
          // Get information about the volume
          Size = 0;
          Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, mFsInfo[Index]);
          if (Status == EFI_BUFFER_TOO_SMALL) {
            mFsInfo[Index] = AllocatePool (Size);
            Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, mFsInfo[Index]);
          }

          Root->Close (Root);
        }
      }
    }
  }
}


/**
PathName is in the form <device name>:<path> for example fs1:\ or ROOT:\.
Return TRUE if the <devce name> prefix of PathName matches a file system
Volume Name. MatchIndex is the array  index in mFsInfo[] of the match, 
and it can be used with mFs[] to find the handle that needs to be opened

@param  PathName      PathName to check
@param  FileStart     Index of the first character of the <path>
@param  MatchIndex    Index in mFsInfo[] that matches

@return TRUE      PathName matches a Volume Label and MatchIndex is valid
@return FALSE     PathName does not match a Volume Label MatchIndex undefined

**/
BOOLEAN
EblMatchVolumeName (
  IN  CHAR8   *PathName,
  IN  UINTN   FileStart,
  OUT UINTN   *MatchIndex
  )
{
  UINTN   Index;
  UINTN   Compare;
  UINTN   VolStrLen;
  BOOLEAN Match;

  for (Index =0; Index < mFsCount; Index++) {
    if (mFsInfo[Index] == NULL) {
      // FsInfo is not valid so skip it
      continue;
    }
    VolStrLen = StrLen (mFsInfo[Index]->VolumeLabel);
    for (Compare = 0, Match = TRUE; Compare < (FileStart - 1); Compare++) {
      if (Compare > VolStrLen) {
        Match = FALSE;
        break;
      }
      if (PathName[Compare] != (CHAR8)mFsInfo[Index]->VolumeLabel[Compare]) {
        // If the VolumeLabel has a space allow a _ to match with it in addition to ' '
        if (!((PathName[Compare] == '_') && (mFsInfo[Index]->VolumeLabel[Compare] == L' '))) {
          Match = FALSE;
          break;
        }
      }
    }
    if (Match) {
      *MatchIndex = Index;
      return TRUE;
    }
  }

  return FALSE;
}


/**
Return the number of devices of the current type active in the system

@param  Type      Device type to check

@return 0         Invalid type

**/
UINTN
EfiGetDeviceCounts (
  IN  EFI_OPEN_FILE_TYPE     DeviceType
  )
{
  switch (DeviceType) {
  case EfiOpenLoadFile:
    return mLoadFileCount;
  case EfiOpenFirmwareVolume:
    return mFvCount;
  case EfiOpenFileSystem:
    return mFsCount;
  case EfiOpenBlockIo:
    return mBlkIoCount;
  default:
    return 0;
  }
}

EFI_STATUS
ConvertIpStringToEfiIp (
  IN  CHAR8           *PathName, 
  OUT EFI_IP_ADDRESS  *ServerIp
  )
{
  CHAR8     *Str;

  Str = PathName;
  ServerIp->v4.Addr[0] = (UINT8)AsciiStrDecimalToUintn (Str);

  Str = AsciiStrStr (Str, ".");
  if (Str == NULL) {
    return EFI_DEVICE_ERROR;
  }

  ServerIp->v4.Addr[1] = (UINT8)AsciiStrDecimalToUintn (++Str);

  Str = AsciiStrStr (Str, ".");
  if (Str == NULL) {
    return EFI_DEVICE_ERROR;
  }

  ServerIp->v4.Addr[2] = (UINT8)AsciiStrDecimalToUintn (++Str);

  Str = AsciiStrStr (Str, ".");
  if (Str == NULL) {
    return EFI_DEVICE_ERROR;
  }

  ServerIp->v4.Addr[3] = (UINT8)AsciiStrDecimalToUintn (++Str);

  return EFI_SUCCESS;
}


/**
Internal work function to extract a device number from a string skipping 
text. Easy way to extract numbers from strings like blk7:.

@param  Str   String to extract device number form

@return -1    Device string is not valid
@return       Device #

**/
UINTN
EblConvertDevStringToNumber (
  IN  CHAR8   *Str
  )
{
  UINTN   Max;
  UINTN   Index;


  // Find the first digit 
  Max = AsciiStrLen (Str);
  for  (Index = 0; !((*Str >= '0') && (*Str <= '9')) && (Index < Max); Index++) {
    Str++;
  }
  if (Index == Max) {
    return (UINTN)-1;
  }

  return AsciiStrDecimalToUintn (Str);
}


/**
Internal work function to fill in EFI_OPEN_FILE information for the Fs and BlkIo

@param  File        Open file handle
@param  FileName    Name of file after device stripped off


**/
EFI_STATUS
EblFileDevicePath (
  IN OUT EFI_OPEN_FILE  *File,
  IN  CHAR8             *FileName,
  IN  CONST UINT64      OpenMode
  )
{
  EFI_STATUS                        Status;
  UINTN                             Size;
  FILEPATH_DEVICE_PATH              *FilePath;
  EFI_DEVICE_PATH_PROTOCOL          *FileDevicePath;
  CHAR16                            UnicodeFileName[MAX_PATHNAME];
  EFI_BLOCK_IO_PROTOCOL             *BlkIo;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL   *Fs;
  EFI_FILE_HANDLE                   Root;


  if ( *FileName != 0 ) {
    AsciiStrToUnicodeStr (FileName, UnicodeFileName);
  } else {
    AsciiStrToUnicodeStr ("\\", UnicodeFileName);
  }

  Size = StrSize (UnicodeFileName);
  FileDevicePath = AllocatePool (Size + SIZE_OF_FILEPATH_DEVICE_PATH + sizeof (EFI_DEVICE_PATH_PROTOCOL));
  if (FileDevicePath != NULL) {
    FilePath = (FILEPATH_DEVICE_PATH *) FileDevicePath;
    FilePath->Header.Type    = MEDIA_DEVICE_PATH;
    FilePath->Header.SubType = MEDIA_FILEPATH_DP;
    CopyMem (&FilePath->PathName, UnicodeFileName, Size);
    SetDevicePathNodeLength (&FilePath->Header, Size + SIZE_OF_FILEPATH_DEVICE_PATH);
    SetDevicePathEndNode (NextDevicePathNode (&FilePath->Header));

    if (File->EfiHandle != NULL) {
      File->DevicePath = DevicePathFromHandle (File->EfiHandle);
    }

    File->DevicePath = AppendDevicePath (File->DevicePath, FileDevicePath);
    FreePool (FileDevicePath);
  }

  Status = gBS->HandleProtocol (File->EfiHandle, &gEfiBlockIoProtocolGuid, (VOID **)&BlkIo);
  if (!EFI_ERROR (Status)) {
    File->FsBlockIoMedia = BlkIo->Media;
    File->FsBlockIo = BlkIo;

    // If we are not opening the device this will get over written with file info
    File->MaxPosition = MultU64x32 (BlkIo->Media->LastBlock + 1, BlkIo->Media->BlockSize);
  }

  if (File->Type == EfiOpenFileSystem) {
    Status = gBS->HandleProtocol (File->EfiHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs);
    if (!EFI_ERROR (Status)) {
      Status = Fs->OpenVolume (Fs, &Root);
      if (!EFI_ERROR (Status)) {
        // Get information about the volume
        Size = 0;
        Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, File->FsInfo);
        if (Status == EFI_BUFFER_TOO_SMALL) {
          File->FsInfo = AllocatePool (Size);
          Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, File->FsInfo);
        }

        // Get information about the file
        Status = Root->Open (Root, &File->FsFileHandle, UnicodeFileName, OpenMode, 0);
        if (!EFI_ERROR (Status)) {
          Size = 0;
          Status = File->FsFileHandle->GetInfo (File->FsFileHandle, &gEfiFileInfoGuid, &Size, NULL);
          if (Status == EFI_BUFFER_TOO_SMALL) {
            File->FsFileInfo = AllocatePool (Size);
            Status = File->FsFileHandle->GetInfo (File->FsFileHandle, &gEfiFileInfoGuid, &Size, File->FsFileInfo);
            if (!EFI_ERROR (Status)) {
              File->Size = (UINTN)File->FsFileInfo->FileSize;
              File->MaxPosition = (UINT64)File->Size;
            }
          }
        }

        Root->Close (Root);
      }
    }
  } else if (File->Type == EfiOpenBlockIo) {
    File->Size = (UINTN)File->MaxPosition;
  }

  return Status;
}

#define ToUpper(a)  ((((a) >= 'a') && ((a) <= 'z')) ? ((a) - 'a' + 'A') : (a))

EFI_STATUS
CompareGuidToString (
  IN  EFI_GUID    *Guid,
  IN  CHAR8       *String
  )
{
  CHAR8       AsciiGuid[64];
  CHAR8       *StringPtr;
  CHAR8       *GuidPtr;

  AsciiSPrint (AsciiGuid, sizeof(AsciiGuid), "%g", Guid);

  StringPtr = String;
  GuidPtr   = AsciiGuid;

  while ((*StringPtr != '\0') && (*GuidPtr != '\0')) {
    // Skip dashes
    if (*StringPtr == '-') {
      StringPtr++;
      continue;
    }

    if (*GuidPtr == '-') {
      GuidPtr++;
      continue;
    }

    if (ToUpper(*StringPtr) != ToUpper(*GuidPtr)) {
      return EFI_NOT_FOUND;
    }

    StringPtr++;
    GuidPtr++;
  }

  return EFI_SUCCESS;
}


/**
Internal work function to fill in EFI_OPEN_FILE information for the FV

@param  File        Open file handle
@param  FileName    Name of file after device stripped off


**/
EFI_STATUS
EblFvFileDevicePath (
  IN OUT EFI_OPEN_FILE  *File,
  IN  CHAR8             *FileName,
  IN  CONST UINT64      OpenMode
  )
{
  EFI_STATUS                          Status;
  EFI_STATUS                          GetNextFileStatus;
  MEDIA_FW_VOL_FILEPATH_DEVICE_PATH   DevicePathNode;
  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  UINTN                               Key;
  UINT32                              AuthenticationStatus;
  CHAR8                               AsciiSection[MAX_PATHNAME];
  VOID                                *Section;
  UINTN                               SectionSize;
  EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL  *Fvb;
  EFI_LBA                             Lba;
  UINTN                               BlockSize;
  UINTN                               NumberOfBlocks;
  EFI_FIRMWARE_VOLUME_HEADER          *FvHeader = NULL;
  UINTN                               Index;


  Status = gBS->HandleProtocol (File->EfiHandle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&File->Fv);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  // Get FVB Info about the handle
  Status = gBS->HandleProtocol (File->EfiHandle, &gEfiFirmwareVolumeBlockProtocolGuid, (VOID **)&Fvb);
  if (!EFI_ERROR (Status)) {
    Status = Fvb->GetPhysicalAddress (Fvb, &File->FvStart);
    if (!EFI_ERROR (Status)) {
      FvHeader = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN)File->FvStart;
      File->FvHeaderSize = sizeof (EFI_FIRMWARE_VOLUME_HEADER);
      for (Index = 0; FvHeader->BlockMap[Index].Length !=0; Index++) {
        File->FvHeaderSize += sizeof (EFI_FV_BLOCK_MAP_ENTRY);
      }

      for (Lba = 0, File->FvSize = 0, NumberOfBlocks = 0; ; File->FvSize += (BlockSize * NumberOfBlocks), Lba += NumberOfBlocks) {
        Status = Fvb->GetBlockSize (Fvb, Lba, &BlockSize, &NumberOfBlocks);
        if (EFI_ERROR (Status)) {
          break;
        }
      }
    }
  }


  DevicePath = DevicePathFromHandle (File->EfiHandle);

  if (*FileName == '\0') {
    File->DevicePath = DuplicateDevicePath (DevicePath);
    File->Size = File->FvSize;
    File->MaxPosition = File->Size;
  } else {
    Key = 0;
    do {
      File->FvType = EFI_FV_FILETYPE_ALL;
      GetNextFileStatus = File->Fv->GetNextFile (
        File->Fv, 
        &Key,
        &File->FvType,  
        &File->FvNameGuid, 
        &File->FvAttributes, 
        &File->Size
        );
      if (!EFI_ERROR (GetNextFileStatus)) {
        // Compare GUID first
        Status = CompareGuidToString (&File->FvNameGuid, FileName);
        if (!EFI_ERROR(Status)) {
          break;
        }

        Section = NULL;
        Status = File->Fv->ReadSection (
          File->Fv,
          &File->FvNameGuid,
          EFI_SECTION_USER_INTERFACE,
          0,
          &Section,
          &SectionSize,
          &AuthenticationStatus
          );
        if (!EFI_ERROR (Status)) {
          UnicodeStrToAsciiStr (Section, AsciiSection);
          if (AsciiStriCmp (FileName, AsciiSection) == 0) {
            FreePool (Section);
            break;
          }
          FreePool (Section);
        }
      }
    } while (!EFI_ERROR (GetNextFileStatus));

    if (EFI_ERROR (GetNextFileStatus)) {
      return GetNextFileStatus;
    }

    if (OpenMode != EFI_SECTION_ALL) {
      // Calculate the size of the section we are targeting
      Section = NULL;
      File->Size = 0;
      Status = File->Fv->ReadSection (
        File->Fv,
        &File->FvNameGuid,
        (EFI_SECTION_TYPE)OpenMode,
        0,
        &Section,
        &File->Size,
        &AuthenticationStatus
        );
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }

    File->MaxPosition = File->Size;
    EfiInitializeFwVolDevicepathNode (&DevicePathNode, &File->FvNameGuid);
    File->DevicePath = AppendDevicePathNode (DevicePath, (EFI_DEVICE_PATH_PROTOCOL *)&DevicePathNode);
  }


  // FVB not required if FV was soft loaded...
  return EFI_SUCCESS;
}




/**
Open a device named by PathName. The PathName includes a device name and 
path seperated by a :. See file header for more details on the PathName 
syntax. There is no checking to prevent a file from being opened more than
one type. 

SectionType is only used to open an FV. Each file in an FV contains multiple
secitons and only the SectionType section is opened. 

For any file that is opened with EfiOpen() must be closed with EfiClose().

@param  PathName    Path to parse to open 
@param  OpenMode    Same as EFI_FILE.Open()
@param  SectionType Section in FV to open.

@return NULL  Open failed
@return Valid EFI_OPEN_FILE handle

**/
EFI_OPEN_FILE *
EfiOpen (
  IN        CHAR8               *PathName,
  IN  CONST UINT64              OpenMode,
  IN  CONST EFI_SECTION_TYPE    SectionType
  )
{
  EFI_STATUS                Status;
  EFI_OPEN_FILE             *File;
  EFI_OPEN_FILE             FileData;
  UINTN                     StrLen;
  UINTN                     FileStart;
  UINTN                     DevNumber = 0;
  EFI_OPEN_FILE_GUARD       *GuardFile;
  BOOLEAN                   VolumeNameMatch;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  UINTN                     Size;
  EFI_IP_ADDRESS            Ip;
  CHAR8                     *CwdPlusPathName;
  UINTN                     Index;
  EFI_SECTION_TYPE          ModifiedSectionType;

  EblUpdateDeviceLists ();

  File = &FileData;
  ZeroMem (File, sizeof (EFI_OPEN_FILE));

  StrLen = AsciiStrSize (PathName);
  if (StrLen <= 1) {
    // Smallest valid path is 1 char and a null
    return NULL;
  }

  for (FileStart = 0; FileStart < StrLen; FileStart++) {
    if (PathName[FileStart] == ':') {
      FileStart++;
      break;
    }
  }

  //
  // Matching volume name has precedence over handle based names
  //
  VolumeNameMatch = EblMatchVolumeName (PathName, FileStart, &DevNumber);
  if (!VolumeNameMatch) {
    if (FileStart == StrLen) {
      // No Volume name or device name, so try Current Working Directory
      if (gCwd == NULL) {
        // No CWD
        return NULL;
      }

      // We could add a current working diretory concept 
      CwdPlusPathName = AllocatePool (AsciiStrSize (gCwd) + AsciiStrSize (PathName));
      if (CwdPlusPathName == NULL) {
        return NULL;
      }

      if ((PathName[0] == '/') || (PathName[0] == '\\')) {
        // PathName starts in / so this means we go to the root of the device in the CWD. 
        CwdPlusPathName[0] = '\0';
        for (FileStart = 0; gCwd[FileStart] != '\0'; FileStart++) {
          CwdPlusPathName[FileStart] = gCwd[FileStart];
          if (gCwd[FileStart] == ':') {
            FileStart++;
            CwdPlusPathName[FileStart] = '\0';
            break;
          }
        }
      } else {
        AsciiStrCpy (CwdPlusPathName, gCwd);
        StrLen = AsciiStrLen (gCwd);
        if ((*PathName != '/') && (*PathName != '\\') && (gCwd[StrLen-1] != '/') && (gCwd[StrLen-1] != '\\')) {
          AsciiStrCat (CwdPlusPathName, "\\");
        }
      }

      AsciiStrCat (CwdPlusPathName, PathName);
      if (AsciiStrStr (CwdPlusPathName, ":") == NULL) {
        // Extra error check to make sure we don't recusre and blow stack
        return NULL;
      }

      File = EfiOpen (CwdPlusPathName, OpenMode, SectionType);
      FreePool (CwdPlusPathName);
      return File;
    }

    DevNumber = EblConvertDevStringToNumber ((CHAR8 *)PathName); 
  }

  File->DeviceName = AllocatePool (StrLen);
  AsciiStrCpy (File->DeviceName, PathName);
  File->DeviceName[FileStart - 1] = '\0';
  File->FileName = &File->DeviceName[FileStart];
  if (File->FileName[0] == '\0') {
    // if it is just a file name use / as root
    File->FileName = "\\";
  } 

  //
  // Use best match algorithm on the dev names so we only need to look at the
  // first few charters to match the full device name. Short name forms are 
  // legal from the caller.
  //
  Status = EFI_SUCCESS;
  if (*PathName == 'f' || *PathName == 'F' || VolumeNameMatch) {
    if (PathName[1] == 's' || PathName[1] == 'S' || VolumeNameMatch) {
      if (DevNumber >= mFsCount) {
        goto ErrorExit;
      }
      File->Type = EfiOpenFileSystem;
      File->EfiHandle = mFs[DevNumber];
      Status = EblFileDevicePath (File, &PathName[FileStart], OpenMode);

    } else if (PathName[1] == 'v' || PathName[1] == 'V') { 
      if (DevNumber >= mFvCount) {
        goto ErrorExit;
      }
      File->Type = EfiOpenFirmwareVolume;
      File->EfiHandle = mFv[DevNumber];

      if ((PathName[FileStart] == '/') || (PathName[FileStart] == '\\')) {
        // Skip leading / as its not really needed for the FV since no directories are supported
        FileStart++;
      }

      // Check for 2nd :
      ModifiedSectionType = SectionType;
      for (Index = FileStart; PathName[Index] != '\0'; Index++) {
        if (PathName[Index] == ':') {
          // Support fv0:\DxeCore:0x10
          // This means open the PE32 Section of the file 
          ModifiedSectionType = (EFI_SECTION_TYPE)AsciiStrHexToUintn (&PathName[Index + 1]);
          PathName[Index] = '\0';
        }
      }
      File->FvSectionType = ModifiedSectionType;
      Status = EblFvFileDevicePath (File, &PathName[FileStart], ModifiedSectionType);
    }
  } else if ((*PathName == 'A') || (*PathName == 'a')) {
    // Handle a:0x10000000:0x1234 address form a:ADDRESS:SIZE
    File->Type = EfiOpenMemoryBuffer;
    // 1st colon is at PathName[FileStart - 1]
    File->Buffer = (VOID *)AsciiStrHexToUintn (&PathName[FileStart]);

    // Find 2nd colon
    while ((PathName[FileStart] != ':') && (PathName[FileStart] != '\0')) {
      FileStart++;
    }

    // If we ran out of string, there's no extra data
    if (PathName[FileStart] == '\0') {
      File->Size = 0;
    } else {
      File->Size = AsciiStrHexToUintn (&PathName[FileStart + 1]);
    }

    // if there's no number after the second colon, default
    // the end of memory
    if (File->Size == 0) {
      File->Size =  (UINTN)(0 - (UINTN)File->Buffer);
    }

    File->MaxPosition = File->Size;
    File->BaseOffset = (UINTN)File->Buffer;

  } else if (*PathName== 'l' || *PathName == 'L') {
    if (DevNumber >= mLoadFileCount) {
      goto ErrorExit;
    }
    File->Type = EfiOpenLoadFile;
    File->EfiHandle = mLoadFile[DevNumber];

    Status = gBS->HandleProtocol (File->EfiHandle, &gEfiLoadFileProtocolGuid, (VOID **)&File->LoadFile);
    if (EFI_ERROR (Status)) {
      goto ErrorExit;
    }

    Status = gBS->HandleProtocol (File->EfiHandle, &gEfiDevicePathProtocolGuid, (VOID **)&DevicePath);
    if (EFI_ERROR (Status)) {
      goto ErrorExit;
    }
    File->DevicePath = DuplicateDevicePath (DevicePath);

  } else if (*PathName == 'b' || *PathName == 'B') {
    // Handle b#:0x10000000:0x1234 address form b#:ADDRESS:SIZE
    if (DevNumber >= mBlkIoCount) {
      goto ErrorExit;
    }
    File->Type = EfiOpenBlockIo;
    File->EfiHandle = mBlkIo[DevNumber];
    EblFileDevicePath (File, "", OpenMode);

    // 1st colon is at PathName[FileStart - 1]
    File->DiskOffset = AsciiStrHexToUintn (&PathName[FileStart]);

    // Find 2nd colon
    while ((PathName[FileStart] != ':') && (PathName[FileStart] != '\0')) {
      FileStart++;
    }

    // If we ran out of string, there's no extra data
    if (PathName[FileStart] == '\0') {
      Size = 0;
    } else {
      Size = AsciiStrHexToUintn (&PathName[FileStart + 1]);
    }

    // if a zero size is passed in (or the size is left out entirely),
    // go to the end of the device.
    if (Size == 0) {
      File->Size = File->Size - File->DiskOffset;
    } else {
      File->Size = Size;
    }

    File->MaxPosition = File->Size;
    File->BaseOffset = File->DiskOffset;
  } else if ((*PathName) >= '0' && (*PathName <= '9')) {

    // Get current IP address
    Status = EblGetCurrentIpAddress (&Ip);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Device IP Address is not configured.\n");
      goto ErrorExit;
    }


    // Parse X.X.X.X:Filename, only support IPv4 TFTP for now...
    File->Type = EfiOpenTftp;
    File->IsDirty = FALSE;
    File->IsBufferValid = FALSE;

    Status = ConvertIpStringToEfiIp (PathName, &File->ServerIp);
  }

  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  GuardFile = (EFI_OPEN_FILE_GUARD *)AllocateZeroPool (sizeof (EFI_OPEN_FILE_GUARD));
  if (GuardFile == NULL) {
    goto ErrorExit;
  }

  GuardFile->Header = EFI_OPEN_FILE_GUARD_HEADER;
  CopyMem (&(GuardFile->File), &FileData, sizeof (EFI_OPEN_FILE));
  GuardFile->Footer = EFI_OPEN_FILE_GUARD_FOOTER;

  return &(GuardFile->File);

ErrorExit:
  FreePool (File->DeviceName);
  return NULL;
}

#define FILE_COPY_CHUNK 0x01000000

EFI_STATUS
EfiCopyFile (
  IN        CHAR8               *DestinationFile,
  IN        CHAR8               *SourceFile
  )
{
  EFI_OPEN_FILE *Source      = NULL;
  EFI_OPEN_FILE *Destination = NULL;
  EFI_STATUS    Status       = EFI_SUCCESS;
  VOID          *Buffer      = NULL;
  UINTN         Size;
  UINTN         Offset;
  UINTN         Chunk = FILE_COPY_CHUNK;

  Source = EfiOpen (SourceFile, EFI_FILE_MODE_READ, 0);
  if (Source == NULL) {
    AsciiPrint("Source file open error.\n");
    Status = EFI_NOT_FOUND;
    goto Exit;
  }

  Destination = EfiOpen (DestinationFile, EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
  if (Destination == NULL) {
    AsciiPrint("Destination file open error.\n");
    Status = EFI_NOT_FOUND;
    goto Exit;
  }

  Buffer = AllocatePool(FILE_COPY_CHUNK);
  if (Buffer == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  Size = EfiTell(Source, NULL);

  for (Offset = 0; Offset + FILE_COPY_CHUNK <= Size; Offset += Chunk) {
    Chunk = FILE_COPY_CHUNK;

    Status = EfiRead(Source, Buffer, &Chunk);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Read file error %r\n", Status);
      goto Exit;
    }

    Status = EfiWrite(Destination, Buffer, &Chunk);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Write file error %r\n", Status);
      goto Exit;
    }    
  }

  // Any left over?
  if (Offset < Size) {
    Chunk = Size - Offset;

    Status = EfiRead(Source, Buffer, &Chunk);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Read file error\n");
      goto Exit;
    }

    Status = EfiWrite(Destination, Buffer, &Chunk);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Write file error\n");
      goto Exit;
    }    
  }

Exit:
  if (Source != NULL) {
    Status = EfiClose(Source);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Source close error");
    }
  }

  if (Destination != NULL) {
    Status = EfiClose(Destination);
    if (EFI_ERROR(Status)) {
      AsciiPrint("Destination close error");
    }
  }

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

  return Status;
}

/**
Use DeviceType and Index to form a valid PathName and try and open it.

@param  DeviceType  Device type to open
@param  Index       Device Index to use. Zero relative.

@return NULL  Open failed
@return Valid EFI_OPEN_FILE handle

**/
EFI_OPEN_FILE  *
EfiDeviceOpenByType (
  IN  EFI_OPEN_FILE_TYPE    DeviceType,
  IN  UINTN                 Index
  )
{
  CHAR8   *DevStr;
  CHAR8   Path[MAX_CMD_LINE];

  switch (DeviceType) {
  case EfiOpenLoadFile:
    DevStr = "loadfile%d:";
    break;
  case EfiOpenFirmwareVolume:
    DevStr = "fv%d:";    
    break;
  case EfiOpenFileSystem:
    DevStr = "fs%d:";    
    break;
  case EfiOpenBlockIo:
    DevStr = "blk%d:";    
    break;
  case EfiOpenMemoryBuffer:
    DevStr = "a%d:";    
    break;
  default:
    return NULL;
  }

  AsciiSPrint (Path, MAX_PATHNAME, DevStr, Index);

  return EfiOpen (Path, EFI_FILE_MODE_READ, 0);
}


/**
Close a file handle opened by EfiOpen() and free all resources allocated by
EfiOpen().

@param  Stream    Open File Handle

@return EFI_INVALID_PARAMETER  Stream is not an Open File
@return EFI_SUCCESS            Steam closed

**/
EFI_STATUS
EfiClose (
  IN  EFI_OPEN_FILE     *File
  )
{
  EFI_STATUS          Status;
  UINT64              TftpBufferSize;

  if (!FileHandleValid (File)) {
    return EFI_INVALID_PARAMETER;
  }

  //Write the buffer contents to TFTP file.
  if ((File->Type == EfiOpenTftp) && (File->IsDirty)) {

    TftpBufferSize = File->Size;
    Status = EblMtftp (
      EFI_PXE_BASE_CODE_TFTP_WRITE_FILE, 
      File->Buffer, 
      TRUE, 
      &TftpBufferSize, 
      NULL, 
      &File->ServerIp, 
      (UINT8 *)File->FileName, 
      NULL, 
      FALSE
      );
    if (EFI_ERROR(Status)) {
      AsciiPrint("TFTP error during APPLE_NSP_TFTP_WRITE_FILE: %r\n", Status);
      return Status;
    }
  }

  if ((File->Type == EfiOpenLoadFile) || 
    ((File->Type == EfiOpenTftp) && (File->IsBufferValid == TRUE)) ||
    ((File->Type == EfiOpenFirmwareVolume) && (File->IsBufferValid == TRUE))) {
    EblFreePool(File->Buffer);
  }

  EblFreePool (File->DevicePath);
  EblFreePool (File->DeviceName);
  EblFreePool (File->FsFileInfo);
  EblFreePool (File->FsInfo);

  if (File->FsFileHandle != NULL) {
    File->FsFileHandle->Close (File->FsFileHandle);
  }

  // Need to free File and it's Guard structures
  EblFreePool (BASE_CR (File, EFI_OPEN_FILE_GUARD, File));
  return EFI_SUCCESS;
}


/**
Return the size of the file represented by Stream. Also return the current 
Seek position. Opening a file will enable a valid file size to be returned.
LoadFile is an exception as a load file size is set to zero. 

@param  Stream    Open File Handle

@return 0         Stream is not an Open File or a valid LoadFile handle

**/
UINTN
EfiTell (
  IN  EFI_OPEN_FILE     *File,
  OUT EFI_LBA           *CurrentPosition    OPTIONAL
  )
{
  EFI_STATUS Status;
  UINT64     BufferSize = 0;

  if (!FileHandleValid (File)) {
    return 0;
  }

  if (CurrentPosition != NULL) {
    *CurrentPosition = File->CurrentPosition;
  }

  if (File->Type == EfiOpenLoadFile) {
    // Figure out the File->Size
    File->Buffer = NULL;
    File->Size   = 0;
    Status = File->LoadFile->LoadFile (File->LoadFile, File->DevicePath, FALSE, &File->Size, File->Buffer);
    if (Status != EFI_BUFFER_TOO_SMALL) {
      return 0;
    }

    File->MaxPosition = (UINT64)File->Size;
  } else if (File->Type == EfiOpenTftp) {

    Status = EblMtftp (
      EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
      NULL,
      FALSE,
      &BufferSize,
      NULL,
      &File->ServerIp,
      (UINT8 *)File->FileName,
      NULL,
      TRUE
      );
    if (EFI_ERROR(Status)) {
      AsciiPrint("TFTP error during APPLE_NSP_TFTP_GET_FILE_SIZE: %r\n", Status);
      return 0;
    }

    File->Size        = (UINTN)BufferSize;
    File->MaxPosition = File->Size;
  }

  return File->Size;
}


/**
Seek to the Offset locaiton in the file. LoadFile and FV device types do
not support EfiSeek(). It is not possible to grow the file size using 
EfiSeek().

SeekType defines how use Offset to calculate the new file position:
EfiSeekStart  : Position = Offset
EfiSeekCurrent: Position is Offset bytes from the current position
EfiSeekEnd    : Only supported if Offset is zero to seek to end of file.

@param  Stream    Open File Handle
@param  Offset    Offset to seek too. 
@param  SeekType  Type of seek to perform


@return EFI_INVALID_PARAMETER  Stream is not an Open File
@return EFI_UNSUPPORTED        LoadFile and FV doe not support Seek
@return EFI_NOT_FOUND          Seek past the end of the file.
@return EFI_SUCCESS            Steam closed

**/
EFI_STATUS
EfiSeek (
  IN  EFI_OPEN_FILE     *File,
  IN  EFI_LBA           Offset,
  IN  EFI_SEEK_TYPE     SeekType
  )
{
  EFI_STATUS    Status;
  UINT64        CurrentPosition;

  if (!FileHandleValid (File)) {
    return EFI_INVALID_PARAMETER;
  }

  if (File->Type == EfiOpenLoadFile) {
    // LoadFile does not support Seek
    return EFI_UNSUPPORTED;
  }

  CurrentPosition = File->CurrentPosition;
  switch (SeekType) {
  case EfiSeekStart:
    if (Offset > File->MaxPosition) {
      return EFI_NOT_FOUND;
    }
    CurrentPosition = Offset;
    break;

  case EfiSeekCurrent:
    if ((File->CurrentPosition + Offset) > File->MaxPosition) {
      return EFI_NOT_FOUND;
    }
    CurrentPosition += Offset;
    break;

  case EfiSeekEnd:
    if (Offset != 0) {
      // We don't support growing file size via seeking past end of file
      return EFI_UNSUPPORTED;
    }
    CurrentPosition = File->MaxPosition;
    break;

  default:
    return EFI_NOT_FOUND;
  }

  Status = EFI_SUCCESS;
  if (File->FsFileHandle != NULL) {
    Status = File->FsFileHandle->SetPosition (File->FsFileHandle, CurrentPosition);
  }

  if (!EFI_ERROR (Status)) {
    File->CurrentPosition = CurrentPosition;
  }

  return Status;
}

EFI_STATUS
CacheTftpFile (
  IN OUT  EFI_OPEN_FILE *File
  )
{
  EFI_STATUS          Status;
  UINT64              TftpBufferSize;

  if (File->IsBufferValid) {
    return EFI_SUCCESS;
  }

  // Make sure the file size is set.
  EfiTell (File, NULL);

  //Allocate a buffer to hold the whole file.
  File->Buffer = AllocatePool(File->Size);
  if (File->Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  TftpBufferSize = File->Size;

  Status = EblMtftp (
    EFI_PXE_BASE_CODE_TFTP_READ_FILE, 
    File->Buffer, 
    FALSE, 
    &TftpBufferSize, 
    NULL, 
    &File->ServerIp, 
    (UINT8 *)File->FileName, 
    NULL, 
    FALSE);
  if (EFI_ERROR(Status)) {
    AsciiPrint("TFTP error during APPLE_NSP_TFTP_READ_FILE: %r\n", Status);
    FreePool(File->Buffer);
    return Status;
  }

  // Set the buffer valid flag.
  File->IsBufferValid = TRUE;

  return Status;
}

/**
Read BufferSize bytes from the current locaiton in the file. For load file,
FV, and TFTP case you must read the entire file. 

@param  Stream      Open File Handle
@param  Buffer      Caller allocated buffer. 
@param  BufferSize  Size of buffer in bytes.


@return EFI_SUCCESS           Stream is not an Open File
@return EFI_END_OF_FILE Tried to read past the end of the file
@return EFI_INVALID_PARAMETER Stream is not an open file handle
@return EFI_BUFFER_TOO_SMALL  Buffer is not big enough to do the read
@return "other"               Error returned from device read

**/
EFI_STATUS
EfiRead (
  IN  EFI_OPEN_FILE       *File,
  OUT VOID                *Buffer,
  OUT UINTN               *BufferSize
  )
{
  EFI_STATUS            Status;
  UINT32                AuthenticationStatus;
  EFI_DISK_IO_PROTOCOL  *DiskIo;

  if (!FileHandleValid (File)) {
    return EFI_INVALID_PARAMETER;
  }

  // Don't read past the end of the file.
  if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) {
    return EFI_END_OF_FILE;
  }

  switch (File->Type) {
  case EfiOpenLoadFile:
    // Figure out the File->Size
    EfiTell (File, NULL);

    Status = File->LoadFile->LoadFile (File->LoadFile, File->DevicePath, FALSE, BufferSize, Buffer);
    break;

  case EfiOpenFirmwareVolume:
    if (CompareGuid (&File->FvNameGuid, &gZeroGuid)) {
      // This is the entire FV device, so treat like a memory buffer 
      CopyMem (Buffer, (VOID *)(UINTN)(File->FvStart + File->CurrentPosition), *BufferSize);
      File->CurrentPosition += *BufferSize;
      Status = EFI_SUCCESS;
    } else {
      if (File->Buffer == NULL) {
        if (File->FvSectionType == EFI_SECTION_ALL) {
          Status = File->Fv->ReadFile (
            File->Fv,
            &File->FvNameGuid,
            (VOID **)&File->Buffer,
            &File->Size,
            &File->FvType,
            &File->FvAttributes,
            &AuthenticationStatus
            );
        } else {
          Status = File->Fv->ReadSection (
            File->Fv,
            &File->FvNameGuid,
            File->FvSectionType,
            0,
            (VOID **)&File->Buffer,
            &File->Size,
            &AuthenticationStatus
            );
        }
        if (EFI_ERROR (Status)) {
          return Status;
        }
        File->IsBufferValid = TRUE;
      }
      // Operate on the cached buffer so Seek will work
      CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize);
      File->CurrentPosition += *BufferSize;
      Status = EFI_SUCCESS;
    }
    break;

  case EfiOpenMemoryBuffer:
    CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize);
    File->CurrentPosition += *BufferSize;
    Status = EFI_SUCCESS;
    break;

  case EfiOpenFileSystem:
    Status = File->FsFileHandle->Read (File->FsFileHandle, BufferSize, Buffer);
    File->CurrentPosition += *BufferSize;
    break;

  case EfiOpenBlockIo:
    Status = gBS->HandleProtocol(File->EfiHandle, &gEfiDiskIoProtocolGuid, (VOID **)&DiskIo);
    if (!EFI_ERROR(Status)) {
      Status = DiskIo->ReadDisk(DiskIo, File->FsBlockIoMedia->MediaId, File->DiskOffset + File->CurrentPosition, *BufferSize, Buffer);
    }
    File->CurrentPosition += *BufferSize;
    break;

  case EfiOpenTftp:
    // Cache the file if it hasn't been cached yet.
    if (File->IsBufferValid == FALSE) {
      Status = CacheTftpFile (File);
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }

    // Copy out the requested data
    CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize);
    File->CurrentPosition += *BufferSize;

    Status = EFI_SUCCESS;
    break;

  default:
    return EFI_INVALID_PARAMETER;
  };

  return Status;
}


/**
Read the entire file into a buffer. This routine allocates the buffer and
returns it to the user full of the read data. 

This is very useful for load flie where it's hard to know how big the buffer
must be.

@param  Stream      Open File Handle
@param  Buffer      Pointer to buffer to return. 
@param  BufferSize  Pointer to Size of buffer return..


@return EFI_SUCCESS           Stream is not an Open File
@return EFI_END_OF_FILE       Tried to read past the end of the file
@return EFI_INVALID_PARAMETER Stream is not an open file handle
@return EFI_BUFFER_TOO_SMALL  Buffer is not big enough to do the read
@return "other"               Error returned from device read

**/
EFI_STATUS
EfiReadAllocatePool (
  IN  EFI_OPEN_FILE     *File,
  OUT VOID              **Buffer,
  OUT UINTN             *BufferSize
  )
{
  if (!FileHandleValid (File)) {
    return EFI_INVALID_PARAMETER;
  }

  // Loadfile defers file size determination on Open so use tell to find it
  EfiTell (File, NULL);

  *BufferSize = File->Size;
  *Buffer = AllocatePool (*BufferSize);
  if (*Buffer == NULL) {
    return EFI_NOT_FOUND;
  }

  return EfiRead (File, *Buffer, BufferSize);
}


/**
Write data back to the file. For TFTP case you must write the entire file. 

@param  Stream      Open File Handle
@param  Buffer      Pointer to buffer to return. 
@param  BufferSize  Pointer to Size of buffer return..


@return EFI_SUCCESS           Stream is not an Open File
@return EFI_END_OF_FILE       Tried to read past the end of the file
@return EFI_INVALID_PARAMETER Stream is not an open file handle
@return EFI_BUFFER_TOO_SMALL  Buffer is not big enough to do the read
@return "other"               Error returned from device write

**/
EFI_STATUS
EfiWrite (
  IN  EFI_OPEN_FILE   *File,
  OUT VOID            *Buffer,
  OUT UINTN           *BufferSize
  )
{
  EFI_STATUS              Status;
  EFI_FV_WRITE_FILE_DATA  FileData;
  EFI_DISK_IO_PROTOCOL    *DiskIo;  

  if (!FileHandleValid (File)) {
    return EFI_INVALID_PARAMETER;
  }

  switch (File->Type) {
  case EfiOpenMemoryBuffer:
    if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) {
      return EFI_END_OF_FILE;
    }

    CopyMem (File->Buffer + File->CurrentPosition, Buffer, *BufferSize);
    File->CurrentPosition += *BufferSize;
    Status = EFI_SUCCESS;

  case EfiOpenLoadFile:
    // LoadFile device is read only be definition
    Status = EFI_UNSUPPORTED;

  case EfiOpenFirmwareVolume:
    if (File->FvSectionType != EFI_SECTION_ALL) {
      // Writes not support to a specific section. You have to update entire file
      return EFI_UNSUPPORTED;
    }

    FileData.NameGuid       = &(File->FvNameGuid);
    FileData.Type           = File->FvType;
    FileData.FileAttributes = File->FvAttributes;
    FileData.Buffer         = Buffer;
    FileData.BufferSize     = (UINT32)*BufferSize;
    Status = File->Fv->WriteFile (File->Fv, 1, EFI_FV_UNRELIABLE_WRITE, &FileData);
    break;

  case EfiOpenFileSystem:
    Status = File->FsFileHandle->Write (File->FsFileHandle, BufferSize, Buffer);
    File->CurrentPosition += *BufferSize;
    break;

  case EfiOpenBlockIo:
    if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) {
      return EFI_END_OF_FILE;
    }

    Status = gBS->HandleProtocol (File->EfiHandle, &gEfiDiskIoProtocolGuid, (VOID **)&DiskIo);
    if (!EFI_ERROR(Status)) {
      Status = DiskIo->WriteDisk (DiskIo, File->FsBlockIoMedia->MediaId, File->DiskOffset + File->CurrentPosition, *BufferSize, Buffer);
    }
    File->CurrentPosition += *BufferSize;
    break;

  case EfiOpenTftp:
    // Cache the file if it hasn't been cached yet.
    if (File->IsBufferValid == FALSE) {
      Status = CacheTftpFile(File);
      if (EFI_ERROR(Status)) {
        return Status;
      }
    }

    // Don't overwrite the buffer
    if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) {
      UINT8 *TempBuffer;

      TempBuffer = File->Buffer;

      File->Buffer = AllocatePool ((UINTN)(File->CurrentPosition + *BufferSize));
      if (File->Buffer == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      CopyMem (File->Buffer, TempBuffer, File->Size);

      FreePool (TempBuffer);

      File->Size = (UINTN)(File->CurrentPosition + *BufferSize);
      File->MaxPosition = (UINT64)File->Size;
    }

    // Copy in the requested data
    CopyMem (File->Buffer + File->CurrentPosition, Buffer, *BufferSize);
    File->CurrentPosition += *BufferSize;

    // Mark the file dirty
    File->IsDirty = TRUE;

    Status = EFI_SUCCESS;
    break;

  default:
    Status = EFI_INVALID_PARAMETER;
  };

  return Status;
}


/**
Given Cwd expand Path to remove .. and replace them with real 
directory names.

@param  Cwd     Current Working Directory
@param  Path    Path to expand

@return NULL     Cwd or Path are not valid
@return 'other'  Path with .. expanded

**/
CHAR8 *
ExpandPath (
  IN CHAR8    *Cwd,
  IN CHAR8    *Path
  )
{
  CHAR8   *NewPath;
  CHAR8   *Work, *Start, *End;
  UINTN   StrLen;
  INTN    i;

  if (Cwd == NULL || Path == NULL) {
    return NULL;
  }

  StrLen = AsciiStrSize (Cwd);
  if (StrLen <= 2) {
    // Smallest valid path is 1 char and a null
    return NULL;
  }

  StrLen = AsciiStrSize (Path);
  NewPath = AllocatePool (AsciiStrSize (Cwd) + StrLen + 1);
  if (NewPath == NULL) {
    return NULL;
  }
  AsciiStrCpy (NewPath, Cwd);

  End = Path + StrLen;
  for (Start = Path ;;) {
    Work = AsciiStrStr (Start, "..") ;
    if (Work == NULL) {
      // Remaining part of Path contains no more ..
      break;
    } 

    // append path prior to .. 
    AsciiStrnCat (NewPath, Start, Work - Start);
    StrLen = AsciiStrLen (NewPath);
    for (i = StrLen; i >= 0; i--) {
      if (NewPath[i] == ':') {
        // too many ..
        return NULL;
      }
      if (NewPath[i] == '/' || NewPath[i] == '\\') {
        if ((i > 0) && (NewPath[i-1] == ':')) {
          // leave the / before a :
          NewPath[i+1] = '\0';
        } else {
          // replace / will Null to remove trailing file/dir reference
          NewPath[i] = '\0';
        }
        break;
      }
    }

    Start = Work + 3;
  } 

  // Handle the path that remains after the ..
  AsciiStrnCat (NewPath, Start, End - Start);

  return NewPath;
}


/**
Set the Curent Working Directory (CWD). If a call is made to EfiOpen () and 
the path does not contain a device name, The CWD is prepended to the path.

@param  Cwd     Current Working Directory to set


@return EFI_SUCCESS           CWD is set
@return EFI_INVALID_PARAMETER Cwd is not a valid device:path

**/
EFI_STATUS
EfiSetCwd (
  IN  CHAR8   *Cwd
  ) 
{
  EFI_OPEN_FILE *File;
  UINTN         Len;
  CHAR8         *Path;

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

  if (AsciiStrCmp (Cwd, ".") == 0) {
    // cd . is a no-op
    return EFI_SUCCESS;
  }

  Path = Cwd;
  if (AsciiStrStr (Cwd, "..") != NULL) {
    if (gCwd == NULL) {
      // no parent 
      return EFI_SUCCESS;
    }

    Len = AsciiStrLen (gCwd);
    if ((gCwd[Len-2] == ':') && ((gCwd[Len-1] == '/') || (gCwd[Len-1] == '\\'))) {
      // parent is device so nothing to do
      return EFI_SUCCESS;
    }

    // Expand .. in Cwd, given we know current working directory
    Path = ExpandPath (gCwd, Cwd);
    if (Path == NULL) {
      return EFI_NOT_FOUND;
    }
  }

  File = EfiOpen (Path, EFI_FILE_MODE_READ, 0);
  if (File == NULL) {
    return EFI_INVALID_PARAMETER;
  }

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

  // Use the info returned from EfiOpen as it can add in CWD if needed. So Cwd could be
  // relative to the current gCwd or not.
  gCwd = AllocatePool (AsciiStrSize (File->DeviceName) + AsciiStrSize (File->FileName) + 10);
  if (gCwd == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  AsciiStrCpy (gCwd, File->DeviceName);
  if (File->FileName == NULL) {
    AsciiStrCat (gCwd, ":\\");
  } else {
    AsciiStrCat (gCwd, ":");
    AsciiStrCat (gCwd, File->FileName);
  }


  EfiClose (File);
  if (Path != Cwd) {
    FreePool (Path);
  }
  return EFI_SUCCESS;
}


/**
Set the Curent Working Directory (CWD). If a call is made to EfiOpen () and 
the path does not contain a device name, The CWD is prepended to the path.
The CWD buffer is only valid until a new call is made to EfiSetCwd(). After
a call to EfiSetCwd() it is not legal to use the pointer returned by 
this funciton.

@param  Cwd     Current Working Directory 


@return ""      No CWD set
@return 'other' Returns buffer that contains CWD.

**/
CHAR8 *
EfiGetCwd (
  VOID
  )
{
  if (gCwd == NULL) {
    return "";
  }
  return gCwd;
}


