/*++ @file
  Support OS native directory access.

Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent


**/

#include "WinHost.h"

#define WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_SIGNATURE  SIGNATURE_32 ('N', 'T', 'f', 's')

typedef struct {
  UINTN                              Signature;
  EMU_IO_THUNK_PROTOCOL              *Thunk;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL    SimpleFileSystem;
  CHAR16                             *FilePath;
  CHAR16                             *VolumeLabel;
} WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE;

#define WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_DATA_FROM_THIS(a) \
  CR (a, \
      WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE, \
      SimpleFileSystem, \
      WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_SIGNATURE \
      )

#define WIN_NT_EFI_FILE_PRIVATE_SIGNATURE  SIGNATURE_32 ('l', 'o', 'f', 's')

typedef struct {
  UINTN                              Signature;
  EMU_IO_THUNK_PROTOCOL              *Thunk;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL    *SimpleFileSystem;
  EFI_FILE_PROTOCOL                  EfiFile;
  HANDLE                             LHandle;
  HANDLE                             DirHandle;
  BOOLEAN                            IsRootDirectory;
  BOOLEAN                            IsDirectoryPath;
  BOOLEAN                            IsOpenedByRead;
  CHAR16                             *FilePath;
  WCHAR                              *FileName;
  BOOLEAN                            IsValidFindBuf;
  WIN32_FIND_DATA                    FindBuf;
} WIN_NT_EFI_FILE_PRIVATE;

#define WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS(a) \
  CR (a, \
      WIN_NT_EFI_FILE_PRIVATE, \
      EfiFile, \
      WIN_NT_EFI_FILE_PRIVATE_SIGNATURE \
      )

extern EFI_FILE_PROTOCOL                gWinNtFileProtocol;
extern EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  gWinNtFileSystemProtocol;

EFI_STATUS
WinNtFileGetInfo (
  IN EFI_FILE_PROTOCOL  *This,
  IN EFI_GUID           *InformationType,
  IN OUT UINTN          *BufferSize,
  OUT VOID              *Buffer
  );

EFI_STATUS
WinNtFileSetInfo (
  IN EFI_FILE_PROTOCOL  *This,
  IN EFI_GUID           *InformationType,
  IN UINTN              BufferSize,
  IN VOID               *Buffer
  );

CHAR16 *
EfiStrChr (
  IN CHAR16  *Str,
  IN CHAR16  Chr
  )

/*++

Routine Description:

  Locate the first occurance of a character in a string.

Arguments:

  Str - Pointer to NULL terminated unicode string.
  Chr - Character to locate.

Returns:

  If Str is NULL, then NULL is returned.
  If Chr is not contained in Str, then NULL is returned.
  If Chr is contained in Str, then a pointer to the first occurance of Chr in Str is returned.

--*/
{
  if (Str == NULL) {
    return Str;
  }

  while (*Str != '\0' && *Str != Chr) {
    ++Str;
  }

  return (*Str == Chr) ? Str : NULL;
}

BOOLEAN
IsZero (
  IN VOID   *Buffer,
  IN UINTN  Length
  )
{
  if ((Buffer == NULL) || (Length == 0)) {
    return FALSE;
  }

  if (*(UINT8 *)Buffer != 0) {
    return FALSE;
  }

  if (Length > 1) {
    if (!CompareMem (Buffer, (UINT8 *)Buffer + 1, Length - 1)) {
      return FALSE;
    }
  }

  return TRUE;
}

VOID
CutPrefix (
  IN  CHAR16  *Str,
  IN  UINTN   Count
  )
{
  CHAR16  *Pointer;

  if (StrLen (Str) < Count) {
    ASSERT (0);
  }

  if (Count != 0) {
    for (Pointer = Str; *(Pointer + Count); Pointer++) {
      *Pointer = *(Pointer + Count);
    }

    *Pointer = *(Pointer + Count);
  }
}

/**
  Open the root directory on a volume.

  @param  This Protocol instance pointer.
  @param  Root Returns an Open file handle for the root directory

  @retval EFI_SUCCESS          The device was opened.
  @retval EFI_UNSUPPORTED      This volume does not support the file system.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_ACCESS_DENIED    The service denied access to the file.
  @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of resources.

**/
EFI_STATUS
WinNtOpenVolume (
  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
  OUT EFI_FILE_PROTOCOL               **Root
  )
{
  EFI_STATUS                         Status;
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE  *Private;
  WIN_NT_EFI_FILE_PRIVATE            *PrivateFile;
  CHAR16                             *TempFileName;
  UINTN                              Size;

  if ((This == NULL) || (Root == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Private = WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_DATA_FROM_THIS (This);

  PrivateFile = AllocatePool (sizeof (WIN_NT_EFI_FILE_PRIVATE));
  if (PrivateFile == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  PrivateFile->FileName = AllocatePool (StrSize (Private->FilePath));
  if (PrivateFile->FileName == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  PrivateFile->FilePath = AllocatePool (StrSize (Private->FilePath));
  if (PrivateFile->FilePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  StrCpyS (
    PrivateFile->FilePath,
    StrSize (Private->FilePath) / sizeof (CHAR16),
    Private->FilePath
    );
  StrCpyS (
    PrivateFile->FileName,
    StrSize (Private->FilePath) / sizeof (CHAR16),
    PrivateFile->FilePath
    );
  PrivateFile->Signature        = WIN_NT_EFI_FILE_PRIVATE_SIGNATURE;
  PrivateFile->Thunk            = Private->Thunk;
  PrivateFile->SimpleFileSystem = This;
  PrivateFile->IsRootDirectory  = TRUE;
  PrivateFile->IsDirectoryPath  = TRUE;
  PrivateFile->IsOpenedByRead   = TRUE;
  CopyMem (&PrivateFile->EfiFile, &gWinNtFileProtocol, sizeof (gWinNtFileProtocol));
  PrivateFile->IsValidFindBuf = FALSE;

  //
  // Set DirHandle
  //
  PrivateFile->DirHandle = CreateFile (
                             PrivateFile->FilePath,
                             GENERIC_READ,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             NULL,
                             OPEN_EXISTING,
                             FILE_FLAG_BACKUP_SEMANTICS,
                             NULL
                             );

  if (PrivateFile->DirHandle == INVALID_HANDLE_VALUE) {
    Status = EFI_NOT_FOUND;
    goto Done;
  }

  //
  // Find the first file under it
  //
  Size         = StrSize (PrivateFile->FilePath);
  Size        += StrSize (L"\\*");
  TempFileName = AllocatePool (Size);
  if (TempFileName == NULL) {
    goto Done;
  }

  StrCpyS (TempFileName, Size / sizeof (CHAR16), PrivateFile->FilePath);
  StrCatS (TempFileName, Size / sizeof (CHAR16), L"\\*");

  PrivateFile->LHandle = FindFirstFile (TempFileName, &PrivateFile->FindBuf);
  FreePool (TempFileName);

  if (PrivateFile->LHandle == INVALID_HANDLE_VALUE) {
    PrivateFile->IsValidFindBuf = FALSE;
  } else {
    PrivateFile->IsValidFindBuf = TRUE;
  }

  *Root = &PrivateFile->EfiFile;

  Status = EFI_SUCCESS;

Done:
  if (EFI_ERROR (Status)) {
    if (PrivateFile) {
      if (PrivateFile->FileName) {
        FreePool (PrivateFile->FileName);
      }

      if (PrivateFile->FilePath) {
        FreePool (PrivateFile->FilePath);
      }

      FreePool (PrivateFile);
    }
  }

  return Status;
}

/**
  Count the number of Leading Dot in FileNameToken.

  @param FileNameToken  A string representing a token in the path name.

  @return  UINTN             The number of leading dot in the name.

**/
UINTN
CountLeadingDots (
  IN CONST CHAR16  *FileNameToken
  )
{
  UINTN  Num;

  Num = 0;
  while (*FileNameToken == L'.') {
    Num++;
    FileNameToken++;
  }

  return Num;
}

BOOLEAN
IsFileNameTokenValid (
  IN CONST CHAR16  *FileNameToken
  )
{
  UINTN  Num;

  if (StrStr (FileNameToken, L"/") != NULL) {
    //
    // No L'/' in file name.
    //
    return FALSE;
  } else {
    //
    // If Token has all dot, the number should not exceed 2
    //
    Num = CountLeadingDots (FileNameToken);

    if (Num == StrLen (FileNameToken)) {
      //
      // If the FileNameToken only contains a number of L'.'.
      //
      if (Num > 2) {
        return FALSE;
      }
    }
  }

  return TRUE;
}

/**
  Return the first string token found in the indirect pointer a String named by FileName.

  On input, FileName is a indirect pointer pointing to a String.
  On output, FileName is a updated to point to the next character after the first
  found L"\" or NULL if there is no L"\" found.

  @param FileName  A indirect pointer pointing to a FileName.

  @return  Token      The first string token found before a L"\".

**/
CHAR16 *
GetNextFileNameToken (
  IN OUT CONST CHAR16  **FileName
  )
{
  CHAR16  *SlashPos;
  CHAR16  *Token;
  UINTN   Offset;

  ASSERT (**FileName != L'\\');
  ASSERT (**FileName != L'\0');

  SlashPos = StrStr (*FileName, L"\\");
  if (SlashPos == NULL) {
    Token     = AllocateCopyPool (StrSize (*FileName), *FileName);
    *FileName = NULL;
  } else {
    Offset = SlashPos - *FileName;
    Token  = AllocateZeroPool ((Offset + 1) * sizeof (CHAR16));
    StrnCpyS (Token, Offset + 1, *FileName, Offset);
    //
    // Point *FileName to the next character after L'\'.
    //
    *FileName = *FileName + Offset + 1;
    //
    // If *FileName is an empty string, then set *FileName to NULL
    //
    if (**FileName == L'\0') {
      *FileName = NULL;
    }
  }

  return Token;
}

/**
  Check if a FileName contains only Valid Characters.

  If FileName contains only a single L'\', return TRUE.
  If FileName contains two adjacent L'\', return FALSE.
  If FileName conatins L'/' , return FALSE.
  If FileName contains more than two dots separated with other FileName characters
  by L'\', return FALSE. For example, L'.\...\filename.txt' is invalid path name. But L'..TwoDots\filename.txt' is valid path name.

  @param FileName  The File Name String to check.

  @return  TRUE        FileName only contains valid characters.
  @return  FALSE       FileName contains at least one invalid character.

**/
BOOLEAN
IsFileNameValid (
  IN CONST CHAR16  *FileName
  )
{
  CHAR16   *Token;
  BOOLEAN  Valid;

  //
  // If FileName is just L'\', then it is a valid pathname.
  //
  if (StrCmp (FileName, L"\\") == 0) {
    return TRUE;
  }

  //
  // We don't support two or more adjacent L'\'.
  //
  if (StrStr (FileName, L"\\\\") != NULL) {
    return FALSE;
  }

  //
  // Is FileName has a leading L"\", skip to next character.
  //
  if (FileName[0] == L'\\') {
    FileName++;
  }

  do {
    Token = GetNextFileNameToken (&FileName);
    Valid = IsFileNameTokenValid (Token);
    FreePool (Token);

    if (!Valid) {
      return FALSE;
    }
  } while (FileName != NULL);

  return TRUE;
}

/**
  Opens a new file relative to the source file's location.

  @param  This       The protocol instance pointer.
  @param  NewHandle  Returns File Handle for FileName.
  @param  FileName   Null terminated string. "\", ".", and ".." are supported.
  @param  OpenMode   Open mode for file.
  @param  Attributes Only used for EFI_FILE_MODE_CREATE.

  @retval EFI_SUCCESS          The device was opened.
  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_MEDIA_CHANGED    The media has changed.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_ACCESS_DENIED    The service denied access to the file.
  @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of resources.
  @retval EFI_VOLUME_FULL      The volume is full.

**/
EFI_STATUS
WinNtFileOpen (
  IN EFI_FILE_PROTOCOL   *This,
  OUT EFI_FILE_PROTOCOL  **NewHandle,
  IN CHAR16              *FileName,
  IN UINT64              OpenMode,
  IN UINT64              Attributes
  )
{
  WIN_NT_EFI_FILE_PRIVATE            *PrivateFile;
  WIN_NT_EFI_FILE_PRIVATE            *NewPrivateFile;
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE  *PrivateRoot;
  EFI_STATUS                         Status;
  CHAR16                             *RealFileName;
  CHAR16                             *TempFileName;
  CHAR16                             *ParseFileName;
  CHAR16                             *GuardPointer;
  CHAR16                             TempChar;
  DWORD                              LastError;
  UINTN                              Count;
  BOOLEAN                            LoopFinish;
  UINTN                              InfoSize;
  EFI_FILE_INFO                      *Info;
  UINTN                              Size;

  //
  // Init local variables
  //
  PrivateFile    = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);
  PrivateRoot    = WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_DATA_FROM_THIS (PrivateFile->SimpleFileSystem);
  NewPrivateFile = NULL;

  //
  // Allocate buffer for FileName as the passed in FileName may be read only
  //
  TempFileName = AllocatePool (StrSize (FileName));
  if (TempFileName == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  StrCpyS (TempFileName, StrSize (FileName) / sizeof (CHAR16), FileName);
  FileName = TempFileName;

  if (FileName[StrLen (FileName) - 1] == L'\\') {
    FileName[StrLen (FileName) - 1] = 0;
  }

  //
  // If file name does not equal to "." or ".." and not trailed with "\..",
  // then we trim the leading/trailing blanks and trailing dots
  //
  if ((StrCmp (FileName, L".") != 0) && (StrCmp (FileName, L"..") != 0) &&
      ((StrLen (FileName) >= 3) ? (StrCmp (&FileName[StrLen (FileName) - 3], L"\\..") != 0) : TRUE))
  {
    //
    // Trim leading blanks
    //
    Count = 0;
    for (TempFileName = FileName;
         *TempFileName != 0 && *TempFileName == L' ';
         TempFileName++)
    {
      Count++;
    }

    CutPrefix (FileName, Count);
    //
    // Trim trailing blanks
    //
    for (TempFileName = FileName + StrLen (FileName) - 1;
         TempFileName >= FileName && (*TempFileName == L' ');
         TempFileName--)
    {
    }

    *(TempFileName + 1) = 0;
  }

  //
  // Attempt to open the file
  //
  NewPrivateFile = AllocatePool (sizeof (WIN_NT_EFI_FILE_PRIVATE));
  if (NewPrivateFile == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  CopyMem (NewPrivateFile, PrivateFile, sizeof (WIN_NT_EFI_FILE_PRIVATE));

  NewPrivateFile->FilePath = AllocatePool (StrSize (PrivateFile->FileName));
  if (NewPrivateFile->FilePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  if (PrivateFile->IsDirectoryPath) {
    StrCpyS (
      NewPrivateFile->FilePath,
      StrSize (PrivateFile->FileName) / sizeof (CHAR16),
      PrivateFile->FileName
      );
  } else {
    StrCpyS (
      NewPrivateFile->FilePath,
      StrSize (PrivateFile->FileName) / sizeof (CHAR16),
      PrivateFile->FilePath
      );
  }

  Size                     = StrSize (NewPrivateFile->FilePath);
  Size                    += StrSize (L"\\");
  Size                    += StrSize (FileName);
  NewPrivateFile->FileName = AllocatePool (Size);
  if (NewPrivateFile->FileName == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  if (*FileName == L'\\') {
    StrCpyS (NewPrivateFile->FileName, Size / sizeof (CHAR16), PrivateRoot->FilePath);
    StrCatS (NewPrivateFile->FileName, Size / sizeof (CHAR16), L"\\");
    StrCatS (NewPrivateFile->FileName, Size / sizeof (CHAR16), FileName + 1);
  } else {
    StrCpyS (NewPrivateFile->FileName, Size / sizeof (CHAR16), NewPrivateFile->FilePath);
    if (StrCmp (FileName, L"") != 0) {
      //
      // In case the filename becomes empty, especially after trimming dots and blanks
      //
      StrCatS (NewPrivateFile->FileName, Size / sizeof (CHAR16), L"\\");
      StrCatS (NewPrivateFile->FileName, Size / sizeof (CHAR16), FileName);
    }
  }

  if (!IsFileNameValid (NewPrivateFile->FileName)) {
    Status = EFI_NOT_FOUND;
    goto Done;
  }

  //
  // Get rid of . and .., except leading . or ..
  //

  //
  // GuardPointer protect simplefilesystem root path not be destroyed
  //
  GuardPointer = NewPrivateFile->FileName + StrLen (PrivateRoot->FilePath);

  LoopFinish = FALSE;

  while (!LoopFinish) {
    LoopFinish = TRUE;

    for (ParseFileName = GuardPointer; *ParseFileName; ParseFileName++) {
      if ((*ParseFileName == L'.') &&
          ((*(ParseFileName + 1) == 0) || (*(ParseFileName + 1) == L'\\')) &&
          (*(ParseFileName - 1) == L'\\')
          )
      {
        //
        // cut \.
        //
        CutPrefix (ParseFileName - 1, 2);
        LoopFinish = FALSE;
        break;
      }

      if ((*ParseFileName == L'.') &&
          (*(ParseFileName + 1) == L'.') &&
          ((*(ParseFileName + 2) == 0) || (*(ParseFileName + 2) == L'\\')) &&
          (*(ParseFileName - 1) == L'\\')
          )
      {
        ParseFileName--;
        Count = 3;

        while (ParseFileName != GuardPointer) {
          ParseFileName--;
          Count++;
          if (*ParseFileName == L'\\') {
            break;
          }
        }

        //
        // cut \.. and its left directory
        //
        CutPrefix (ParseFileName, Count);
        LoopFinish = FALSE;
        break;
      }
    }
  }

  RealFileName = NewPrivateFile->FileName;
  while (EfiStrChr (RealFileName, L'\\') != NULL) {
    RealFileName = EfiStrChr (RealFileName, L'\\') + 1;
  }

  TempChar = 0;
  if (RealFileName != NewPrivateFile->FileName) {
    TempChar            = *(RealFileName - 1);
    *(RealFileName - 1) = 0;
  }

  FreePool (NewPrivateFile->FilePath);
  NewPrivateFile->FilePath = NULL;
  NewPrivateFile->FilePath = AllocatePool (StrSize (NewPrivateFile->FileName));
  if (NewPrivateFile->FilePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  StrCpyS (
    NewPrivateFile->FilePath,
    StrSize (NewPrivateFile->FileName) / sizeof (CHAR16),
    NewPrivateFile->FileName
    );
  if (TempChar != 0) {
    *(RealFileName - 1) = TempChar;
  }

  NewPrivateFile->IsRootDirectory = FALSE;

  //
  // Test whether file or directory
  //
  if (OpenMode & EFI_FILE_MODE_CREATE) {
    if (Attributes & EFI_FILE_DIRECTORY) {
      NewPrivateFile->IsDirectoryPath = TRUE;
    } else {
      NewPrivateFile->IsDirectoryPath = FALSE;
    }
  } else {
    NewPrivateFile->LHandle = CreateFile (
                                NewPrivateFile->FileName,
                                GENERIC_READ,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL
                                );

    if (NewPrivateFile->LHandle != INVALID_HANDLE_VALUE) {
      NewPrivateFile->IsDirectoryPath = FALSE;
      CloseHandle (NewPrivateFile->LHandle);
    } else {
      NewPrivateFile->IsDirectoryPath = TRUE;
    }

    NewPrivateFile->LHandle = INVALID_HANDLE_VALUE;
  }

  if (OpenMode & EFI_FILE_MODE_WRITE) {
    NewPrivateFile->IsOpenedByRead = FALSE;
  } else {
    NewPrivateFile->IsOpenedByRead = TRUE;
  }

  Status = EFI_SUCCESS;

  //
  // deal with directory
  //
  if (NewPrivateFile->IsDirectoryPath) {
    Size         = StrSize (NewPrivateFile->FileName);
    Size        += StrSize (L"\\*");
    TempFileName = AllocatePool (Size);
    if (TempFileName == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    StrCpyS (TempFileName, Size / sizeof (CHAR16), NewPrivateFile->FileName);

    if ((OpenMode & EFI_FILE_MODE_CREATE)) {
      //
      // Create a directory
      //
      if (!CreateDirectory (TempFileName, NULL)) {
        LastError = GetLastError ();
        if (LastError != ERROR_ALREADY_EXISTS) {
          FreePool (TempFileName);
          Status = EFI_ACCESS_DENIED;
          goto Done;
        }
      }
    }

    NewPrivateFile->DirHandle = CreateFile (
                                  TempFileName,
                                  NewPrivateFile->IsOpenedByRead ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE),
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  OPEN_EXISTING,
                                  FILE_FLAG_BACKUP_SEMANTICS,
                                  NULL
                                  );

    if (NewPrivateFile->DirHandle == INVALID_HANDLE_VALUE) {
      NewPrivateFile->DirHandle = CreateFile (
                                    TempFileName,
                                    GENERIC_READ,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                                    NULL,
                                    OPEN_EXISTING,
                                    FILE_FLAG_BACKUP_SEMANTICS,
                                    NULL
                                    );

      if (NewPrivateFile->DirHandle != INVALID_HANDLE_VALUE) {
        CloseHandle (NewPrivateFile->DirHandle);
        NewPrivateFile->DirHandle = INVALID_HANDLE_VALUE;
        Status                    = EFI_ACCESS_DENIED;
      } else {
        Status = EFI_NOT_FOUND;
      }

      FreePool (TempFileName);
      goto Done;
    }

    //
    // Find the first file under it
    //
    StrCatS (TempFileName, Size / sizeof (CHAR16), L"\\*");
    NewPrivateFile->LHandle = FindFirstFile (TempFileName, &NewPrivateFile->FindBuf);
    FreePool (TempFileName);

    if (NewPrivateFile->LHandle == INVALID_HANDLE_VALUE) {
      NewPrivateFile->IsValidFindBuf = FALSE;
    } else {
      NewPrivateFile->IsValidFindBuf = TRUE;
    }
  } else {
    //
    // deal with file
    //
    if (!NewPrivateFile->IsOpenedByRead) {
      NewPrivateFile->LHandle = CreateFile (
                                  NewPrivateFile->FileName,
                                  GENERIC_READ | GENERIC_WRITE,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  (OpenMode & EFI_FILE_MODE_CREATE) ? OPEN_ALWAYS : OPEN_EXISTING,
                                  0,
                                  NULL
                                  );

      if (NewPrivateFile->LHandle == INVALID_HANDLE_VALUE) {
        NewPrivateFile->LHandle = CreateFile (
                                    NewPrivateFile->FileName,
                                    GENERIC_READ,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                                    NULL,
                                    OPEN_EXISTING,
                                    0,
                                    NULL
                                    );

        if (NewPrivateFile->LHandle == INVALID_HANDLE_VALUE) {
          Status = EFI_NOT_FOUND;
        } else {
          Status = EFI_ACCESS_DENIED;
          CloseHandle (NewPrivateFile->LHandle);
          NewPrivateFile->LHandle = INVALID_HANDLE_VALUE;
        }
      }
    } else {
      NewPrivateFile->LHandle = CreateFile (
                                  NewPrivateFile->FileName,
                                  GENERIC_READ,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  OPEN_EXISTING,
                                  0,
                                  NULL
                                  );

      if (NewPrivateFile->LHandle == INVALID_HANDLE_VALUE) {
        Status = EFI_NOT_FOUND;
      }
    }
  }

  if ((OpenMode & EFI_FILE_MODE_CREATE) && (Status == EFI_SUCCESS)) {
    //
    // Set the attribute
    //
    InfoSize = 0;
    Info     = NULL;

    Status = WinNtFileGetInfo (&NewPrivateFile->EfiFile, &gEfiFileInfoGuid, &InfoSize, Info);

    if (Status != EFI_BUFFER_TOO_SMALL) {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }

    Info = AllocatePool (InfoSize);
    if (Info == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    Status = WinNtFileGetInfo (&NewPrivateFile->EfiFile, &gEfiFileInfoGuid, &InfoSize, Info);

    if (EFI_ERROR (Status)) {
      FreePool (Info);
      goto Done;
    }

    Info->Attribute = Attributes;

    WinNtFileSetInfo (&NewPrivateFile->EfiFile, &gEfiFileInfoGuid, InfoSize, Info);
    FreePool (Info);
  }

Done:
  FreePool (FileName);

  if (EFI_ERROR (Status)) {
    if (NewPrivateFile) {
      if (NewPrivateFile->FileName) {
        FreePool (NewPrivateFile->FileName);
      }

      if (NewPrivateFile->FilePath) {
        FreePool (NewPrivateFile->FilePath);
      }

      FreePool (NewPrivateFile);
    }
  } else {
    *NewHandle = &NewPrivateFile->EfiFile;
    if (StrCmp (NewPrivateFile->FileName, PrivateRoot->FilePath) == 0) {
      NewPrivateFile->IsRootDirectory = TRUE;
    }
  }

  return Status;
}

/**
  Close the file handle

  @param  This          Protocol instance pointer.

  @retval EFI_SUCCESS   The device was opened.

**/
EFI_STATUS
WinNtFileClose (
  IN EFI_FILE_PROTOCOL  *This
  )
{
  WIN_NT_EFI_FILE_PRIVATE  *PrivateFile;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  if (PrivateFile->LHandle != INVALID_HANDLE_VALUE) {
    if (PrivateFile->IsDirectoryPath) {
      FindClose (PrivateFile->LHandle);
    } else {
      CloseHandle (PrivateFile->LHandle);
    }

    PrivateFile->LHandle = INVALID_HANDLE_VALUE;
  }

  if (PrivateFile->IsDirectoryPath && (PrivateFile->DirHandle != INVALID_HANDLE_VALUE)) {
    CloseHandle (PrivateFile->DirHandle);
    PrivateFile->DirHandle = INVALID_HANDLE_VALUE;
  }

  if (PrivateFile->FileName) {
    FreePool (PrivateFile->FileName);
  }

  if (PrivateFile->FilePath) {
    FreePool (PrivateFile->FilePath);
  }

  FreePool (PrivateFile);

  return EFI_SUCCESS;
}

/**
  Close and delete the file handle.

  @param  This                     Protocol instance pointer.

  @retval EFI_SUCCESS              The device was opened.
  @retval EFI_WARN_DELETE_FAILURE  The handle was closed but the file was not deleted.

**/
EFI_STATUS
WinNtFileDelete (
  IN EFI_FILE_PROTOCOL  *This
  )
{
  EFI_STATUS               Status;
  WIN_NT_EFI_FILE_PRIVATE  *PrivateFile;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  Status = EFI_WARN_DELETE_FAILURE;

  if (PrivateFile->IsDirectoryPath) {
    if (PrivateFile->LHandle != INVALID_HANDLE_VALUE) {
      FindClose (PrivateFile->LHandle);
    }

    if (PrivateFile->DirHandle != INVALID_HANDLE_VALUE) {
      CloseHandle (PrivateFile->DirHandle);
      PrivateFile->DirHandle = INVALID_HANDLE_VALUE;
    }

    if (RemoveDirectory (PrivateFile->FileName)) {
      Status = EFI_SUCCESS;
    }
  } else {
    CloseHandle (PrivateFile->LHandle);
    PrivateFile->LHandle = INVALID_HANDLE_VALUE;

    if (!PrivateFile->IsOpenedByRead) {
      if (DeleteFile (PrivateFile->FileName)) {
        Status = EFI_SUCCESS;
      }
    }
  }

  FreePool (PrivateFile->FileName);
  FreePool (PrivateFile->FilePath);
  FreePool (PrivateFile);

  return Status;
}

VOID
WinNtSystemTimeToEfiTime (
  IN SYSTEMTIME             *SystemTime,
  IN TIME_ZONE_INFORMATION  *TimeZone,
  OUT EFI_TIME              *Time
  )

/*++

Routine Description:

  TODO: Add function description

Arguments:

  SystemTime  - TODO: add argument description
  TimeZone    - TODO: add argument description
  Time        - TODO: add argument description

Returns:

  TODO: add return values

--*/
{
  Time->Year       = (UINT16)SystemTime->wYear;
  Time->Month      = (UINT8)SystemTime->wMonth;
  Time->Day        = (UINT8)SystemTime->wDay;
  Time->Hour       = (UINT8)SystemTime->wHour;
  Time->Minute     = (UINT8)SystemTime->wMinute;
  Time->Second     = (UINT8)SystemTime->wSecond;
  Time->Nanosecond = (UINT32)SystemTime->wMilliseconds * 1000000;
  Time->TimeZone   = (INT16)TimeZone->Bias;

  if (TimeZone->StandardDate.wMonth) {
    Time->Daylight = EFI_TIME_ADJUST_DAYLIGHT;
  }
}

/**
  Convert the FileTime to EfiTime.

  @param PrivateFile  Pointer to WIN_NT_EFI_FILE_PRIVATE.
  @param TimeZone     Pointer to the current time zone.
  @param FileTime     Pointer to file time.
  @param EfiTime      Pointer to EFI time.
**/
VOID
WinNtFileTimeToEfiTime (
  IN CONST WIN_NT_EFI_FILE_PRIVATE  *PrivateFile,
  IN       TIME_ZONE_INFORMATION    *TimeZone,
  IN CONST FILETIME                 *FileTime,
  OUT      EFI_TIME                 *EfiTime
  )
{
  FILETIME    TempFileTime;
  SYSTEMTIME  SystemTime;

  FileTimeToLocalFileTime (FileTime, &TempFileTime);
  FileTimeToSystemTime (&TempFileTime, &SystemTime);
  WinNtSystemTimeToEfiTime (&SystemTime, TimeZone, EfiTime);
}

/**
  Read data from the file.

  @param  This       Protocol instance pointer.
  @param  BufferSize On input size of buffer, on output amount of data in buffer.
  @param  Buffer     The buffer in which data is read.

  @retval EFI_SUCCESS          Data was read.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_BUFFER_TO_SMALL  BufferSize is too small. BufferSize contains required size.

**/
EFI_STATUS
WinNtFileRead (
  IN EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN          *BufferSize,
  OUT VOID              *Buffer
  )
{
  WIN_NT_EFI_FILE_PRIVATE  *PrivateFile;
  EFI_STATUS               Status;
  UINTN                    Size;
  UINTN                    NameSize;
  UINTN                    ResultSize;
  UINTN                    Index;
  EFI_FILE_INFO            *Info;
  WCHAR                    *pw;
  TIME_ZONE_INFORMATION    TimeZone;
  EFI_FILE_INFO            *FileInfo;
  UINT64                   Pos;
  UINT64                   FileSize;
  UINTN                    FileInfoSize;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  if (PrivateFile->LHandle == INVALID_HANDLE_VALUE) {
    Status = EFI_DEVICE_ERROR;
    goto Done;
  }

  if (!PrivateFile->IsDirectoryPath) {
    if (This->GetPosition (This, &Pos) != EFI_SUCCESS) {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }

    FileInfoSize = SIZE_OF_EFI_FILE_SYSTEM_INFO;
    FileInfo     = AllocatePool (FileInfoSize);

    Status = This->GetInfo (
                     This,
                     &gEfiFileInfoGuid,
                     &FileInfoSize,
                     FileInfo
                     );

    if (Status == EFI_BUFFER_TOO_SMALL) {
      FreePool (FileInfo);
      FileInfo = AllocatePool (FileInfoSize);
      Status   = This->GetInfo (
                         This,
                         &gEfiFileInfoGuid,
                         &FileInfoSize,
                         FileInfo
                         );
    }

    if (EFI_ERROR (Status)) {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }

    FileSize = FileInfo->FileSize;

    FreePool (FileInfo);

    if (Pos >= FileSize) {
      *BufferSize = 0;
      if (Pos == FileSize) {
        Status = EFI_SUCCESS;
        goto Done;
      } else {
        Status = EFI_DEVICE_ERROR;
        goto Done;
      }
    }

    Status = ReadFile (
               PrivateFile->LHandle,
               Buffer,
               (DWORD)*BufferSize,
               (LPDWORD)BufferSize,
               NULL
               ) ? EFI_SUCCESS : EFI_DEVICE_ERROR;
    goto Done;
  }

  //
  // Read on a directory.  Perform a find next
  //
  if (!PrivateFile->IsValidFindBuf) {
    *BufferSize = 0;
    Status      = EFI_SUCCESS;
    goto Done;
  }

  Size = SIZE_OF_EFI_FILE_INFO;

  NameSize = StrSize (PrivateFile->FindBuf.cFileName);

  ResultSize = Size + NameSize;

  Status = EFI_BUFFER_TOO_SMALL;

  if (*BufferSize >= ResultSize) {
    Status = EFI_SUCCESS;

    Info = Buffer;
    ZeroMem (Info, ResultSize);

    Info->Size = ResultSize;

    GetTimeZoneInformation (&TimeZone);
    WinNtFileTimeToEfiTime (PrivateFile, &TimeZone, &PrivateFile->FindBuf.ftCreationTime, &Info->CreateTime);
    WinNtFileTimeToEfiTime (PrivateFile, &TimeZone, &PrivateFile->FindBuf.ftLastAccessTime, &Info->LastAccessTime);
    WinNtFileTimeToEfiTime (PrivateFile, &TimeZone, &PrivateFile->FindBuf.ftLastWriteTime, &Info->ModificationTime);

    Info->FileSize = PrivateFile->FindBuf.nFileSizeLow;

    Info->PhysicalSize = PrivateFile->FindBuf.nFileSizeLow;

    if (PrivateFile->FindBuf.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
      Info->Attribute |= EFI_FILE_ARCHIVE;
    }

    if (PrivateFile->FindBuf.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
      Info->Attribute |= EFI_FILE_HIDDEN;
    }

    if (PrivateFile->FindBuf.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
      Info->Attribute |= EFI_FILE_SYSTEM;
    }

    if (PrivateFile->FindBuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
      Info->Attribute |= EFI_FILE_READ_ONLY;
    }

    if (PrivateFile->FindBuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
      Info->Attribute |= EFI_FILE_DIRECTORY;
    }

    NameSize = NameSize / sizeof (WCHAR);

    pw = (WCHAR *)(((CHAR8 *)Buffer) + Size);

    for (Index = 0; Index < NameSize; Index++) {
      pw[Index] = PrivateFile->FindBuf.cFileName[Index];
    }

    if (FindNextFile (PrivateFile->LHandle, &PrivateFile->FindBuf)) {
      PrivateFile->IsValidFindBuf = TRUE;
    } else {
      PrivateFile->IsValidFindBuf = FALSE;
    }
  }

  *BufferSize = ResultSize;

Done:
  return Status;
}

/**
  Write data to a file.

  @param  This       Protocol instance pointer.
  @param  BufferSize On input size of buffer, on output amount of data in buffer.
  @param  Buffer     The buffer in which data to write.

  @retval EFI_SUCCESS          Data was written.
  @retval EFI_UNSUPPORTED      Writes to Open directory are not supported.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_DEVICE_ERROR     An attempt was made to write to a deleted file.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_WRITE_PROTECTED  The device is write protected.
  @retval EFI_ACCESS_DENIED    The file was open for read only.
  @retval EFI_VOLUME_FULL      The volume is full.

**/
EFI_STATUS
WinNtFileWrite (
  IN EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN          *BufferSize,
  IN VOID               *Buffer
  )
{
  WIN_NT_EFI_FILE_PRIVATE  *PrivateFile;
  EFI_STATUS               Status;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  if (PrivateFile->LHandle == INVALID_HANDLE_VALUE) {
    Status = EFI_DEVICE_ERROR;
    goto Done;
  }

  if (PrivateFile->IsDirectoryPath) {
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

  if (PrivateFile->IsOpenedByRead) {
    Status = EFI_ACCESS_DENIED;
    goto Done;
  }

  Status = WriteFile (
             PrivateFile->LHandle,
             Buffer,
             (DWORD)*BufferSize,
             (LPDWORD)BufferSize,
             NULL
             ) ? EFI_SUCCESS : EFI_DEVICE_ERROR;

Done:
  return Status;

  //
  // bugbug: need to access windows error reporting
  //
}

/**
  Set a files current position

  @param  This            Protocol instance pointer.
  @param  Position        Byte position from the start of the file.

  @retval EFI_SUCCESS     Data was written.
  @retval EFI_UNSUPPORTED Seek request for non-zero is not valid on open.

**/
EFI_STATUS
WinNtFileSetPossition (
  IN EFI_FILE_PROTOCOL  *This,
  IN UINT64             Position
  )
{
  EFI_STATUS               Status;
  WIN_NT_EFI_FILE_PRIVATE  *PrivateFile;
  UINT32                   PosLow;
  UINT32                   PosHigh;
  CHAR16                   *FileName;
  UINTN                    Size;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  if (PrivateFile->IsDirectoryPath) {
    if (Position != 0) {
      Status = EFI_UNSUPPORTED;
      goto Done;
    }

    Size     = StrSize (PrivateFile->FileName);
    Size    += StrSize (L"\\*");
    FileName = AllocatePool (Size);
    if (FileName == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    StrCpyS (FileName, Size / sizeof (CHAR16), PrivateFile->FileName);
    StrCatS (FileName, Size / sizeof (CHAR16), L"\\*");

    if (PrivateFile->LHandle != INVALID_HANDLE_VALUE) {
      FindClose (PrivateFile->LHandle);
    }

    PrivateFile->LHandle = FindFirstFile (FileName, &PrivateFile->FindBuf);

    if (PrivateFile->LHandle == INVALID_HANDLE_VALUE) {
      PrivateFile->IsValidFindBuf = FALSE;
    } else {
      PrivateFile->IsValidFindBuf = TRUE;
    }

    FreePool (FileName);

    Status = (PrivateFile->LHandle == INVALID_HANDLE_VALUE) ? EFI_DEVICE_ERROR : EFI_SUCCESS;
  } else {
    if (Position == (UINT64)-1) {
      PosLow = SetFilePointer (PrivateFile->LHandle, (ULONG)0, NULL, FILE_END);
    } else {
      PosHigh = (UINT32)RShiftU64 (Position, 32);

      PosLow = SetFilePointer (PrivateFile->LHandle, (ULONG)Position, (PLONG)&PosHigh, FILE_BEGIN);
    }

    Status = (PosLow == 0xFFFFFFFF) ? EFI_DEVICE_ERROR : EFI_SUCCESS;
  }

Done:
  return Status;
}

/**
  Get a file's current position

  @param  This            Protocol instance pointer.
  @param  Position        Byte position from the start of the file.

  @retval EFI_SUCCESS     Data was written.
  @retval EFI_UNSUPPORTED Seek request for non-zero is not valid on open..

**/
EFI_STATUS
WinNtFileGetPossition (
  IN EFI_FILE_PROTOCOL  *This,
  OUT UINT64            *Position
  )
{
  EFI_STATUS               Status;
  WIN_NT_EFI_FILE_PRIVATE  *PrivateFile;
  INT32                    PositionHigh;
  UINT64                   PosHigh64;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  PositionHigh = 0;
  PosHigh64    = 0;

  if (PrivateFile->IsDirectoryPath) {
    Status = EFI_UNSUPPORTED;
    goto Done;
  } else {
    PositionHigh = 0;
    *Position    = SetFilePointer (
                     PrivateFile->LHandle,
                     0,
                     (PLONG)&PositionHigh,
                     FILE_CURRENT
                     );

    Status = *Position == 0xffffffff ? EFI_DEVICE_ERROR : EFI_SUCCESS;
    if (EFI_ERROR (Status)) {
      goto Done;
    }

    PosHigh64  = PositionHigh;
    *Position += LShiftU64 (PosHigh64, 32);
  }

Done:
  return Status;
}

EFI_STATUS
WinNtSimpleFileSystemFileInfo (
  IN     WIN_NT_EFI_FILE_PRIVATE  *PrivateFile,
  IN OUT UINTN                    *BufferSize,
  OUT    VOID                     *Buffer
  )

/*++

Routine Description:

  TODO: Add function description

Arguments:

  PrivateFile - TODO: add argument description
  BufferSize  - TODO: add argument description
  Buffer      - TODO: add argument description

Returns:

  TODO: add return values

--*/
{
  EFI_STATUS                  Status;
  UINTN                       Size;
  UINTN                       NameSize;
  UINTN                       ResultSize;
  EFI_FILE_INFO               *Info;
  BY_HANDLE_FILE_INFORMATION  FileInfo;
  CHAR16                      *RealFileName;
  CHAR16                      *TempPointer;
  TIME_ZONE_INFORMATION       TimeZone;

  Size = SIZE_OF_EFI_FILE_INFO;

  RealFileName = PrivateFile->FileName;
  TempPointer  = RealFileName;
  while (*TempPointer) {
    if (*TempPointer == '\\') {
      RealFileName = TempPointer + 1;
    }

    TempPointer++;
  }

  NameSize = StrSize (RealFileName);

  ResultSize = Size + NameSize;

  Status = EFI_BUFFER_TOO_SMALL;
  if (*BufferSize >= ResultSize) {
    Status = EFI_SUCCESS;

    Info = Buffer;
    ZeroMem (Info, ResultSize);

    Info->Size = ResultSize;
    GetFileInformationByHandle (
      PrivateFile->IsDirectoryPath ? PrivateFile->DirHandle : PrivateFile->LHandle,
      &FileInfo
      );
    Info->FileSize     = FileInfo.nFileSizeLow;
    Info->PhysicalSize = Info->FileSize;

    GetTimeZoneInformation (&TimeZone);
    WinNtFileTimeToEfiTime (PrivateFile, &TimeZone, &FileInfo.ftCreationTime, &Info->CreateTime);
    WinNtFileTimeToEfiTime (PrivateFile, &TimeZone, &FileInfo.ftLastAccessTime, &Info->LastAccessTime);
    WinNtFileTimeToEfiTime (PrivateFile, &TimeZone, &FileInfo.ftLastWriteTime, &Info->ModificationTime);

    if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
      Info->Attribute |= EFI_FILE_ARCHIVE;
    }

    if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
      Info->Attribute |= EFI_FILE_HIDDEN;
    }

    if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
      Info->Attribute |= EFI_FILE_READ_ONLY;
    }

    if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
      Info->Attribute |= EFI_FILE_SYSTEM;
    }

    if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
      Info->Attribute |= EFI_FILE_DIRECTORY;
    }

    if (PrivateFile->IsDirectoryPath) {
      Info->Attribute |= EFI_FILE_DIRECTORY;
    }

    if (PrivateFile->IsRootDirectory) {
      *((CHAR8 *)Buffer + Size) = 0;
    } else {
      CopyMem ((CHAR8 *)Buffer + Size, RealFileName, NameSize);
    }
  }

  *BufferSize = ResultSize;
  return Status;
}

/**
  Get information about a file.

  @param  This            Protocol instance pointer.
  @param  InformationType Type of information to return in Buffer.
  @param  BufferSize      On input size of buffer, on output amount of data in buffer.
  @param  Buffer          The buffer to return data.

  @retval EFI_SUCCESS          Data was returned.
  @retval EFI_UNSUPPORTED      InformationType is not supported.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_WRITE_PROTECTED  The device is write protected.
  @retval EFI_ACCESS_DENIED    The file was open for read only.
  @retval EFI_BUFFER_TOO_SMALL Buffer was too small; required size returned in BufferSize.

**/
EFI_STATUS
WinNtFileGetInfo (
  IN EFI_FILE_PROTOCOL  *This,
  IN EFI_GUID           *InformationType,
  IN OUT UINTN          *BufferSize,
  OUT VOID              *Buffer
  )
{
  EFI_STATUS                         Status;
  WIN_NT_EFI_FILE_PRIVATE            *PrivateFile;
  EFI_FILE_SYSTEM_INFO               *FileSystemInfoBuffer;
  UINT32                             SectorsPerCluster;
  UINT32                             BytesPerSector;
  UINT32                             FreeClusters;
  UINT32                             TotalClusters;
  UINT32                             BytesPerCluster;
  CHAR16                             *DriveName;
  BOOLEAN                            DriveNameFound;
  BOOL                               NtStatus;
  UINTN                              Index;
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE  *PrivateRoot;

  if ((This == NULL) || (InformationType == NULL) || (BufferSize == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);
  PrivateRoot = WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_DATA_FROM_THIS (PrivateFile->SimpleFileSystem);

  Status = EFI_UNSUPPORTED;

  if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
    Status = WinNtSimpleFileSystemFileInfo (PrivateFile, BufferSize, Buffer);
  }

  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
    if (*BufferSize < SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (PrivateRoot->VolumeLabel)) {
      *BufferSize = SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (PrivateRoot->VolumeLabel);
      Status      = EFI_BUFFER_TOO_SMALL;
      goto Done;
    }

    FileSystemInfoBuffer           = (EFI_FILE_SYSTEM_INFO *)Buffer;
    FileSystemInfoBuffer->Size     = SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (PrivateRoot->VolumeLabel);
    FileSystemInfoBuffer->ReadOnly = FALSE;

    //
    // Try to get the drive name
    //
    DriveNameFound = FALSE;
    DriveName      = AllocatePool (StrSize (PrivateFile->FilePath) + 1);
    if (DriveName == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    StrCpyS (
      DriveName,
      (StrSize (PrivateFile->FilePath) + 1) / sizeof (CHAR16),
      PrivateFile->FilePath
      );
    for (Index = 0; DriveName[Index] != 0 && DriveName[Index] != ':'; Index++) {
    }

    if (DriveName[Index] == ':') {
      DriveName[Index + 1] = '\\';
      DriveName[Index + 2] = 0;
      DriveNameFound       = TRUE;
    } else if ((DriveName[0] == '\\') && (DriveName[1] == '\\')) {
      for (Index = 2; DriveName[Index] != 0 && DriveName[Index] != '\\'; Index++) {
      }

      if (DriveName[Index] == '\\') {
        DriveNameFound = TRUE;
        for (Index++; DriveName[Index] != 0 && DriveName[Index] != '\\'; Index++) {
        }

        DriveName[Index]     = '\\';
        DriveName[Index + 1] = 0;
      }
    }

    //
    // Try GetDiskFreeSpace first
    //
    NtStatus = GetDiskFreeSpace (
                 DriveNameFound ? DriveName : NULL,
                 (LPDWORD)&SectorsPerCluster,
                 (LPDWORD)&BytesPerSector,
                 (LPDWORD)&FreeClusters,
                 (LPDWORD)&TotalClusters
                 );
    if (DriveName) {
      FreePool (DriveName);
    }

    if (NtStatus) {
      //
      // Succeeded
      //
      BytesPerCluster                  = BytesPerSector * SectorsPerCluster;
      FileSystemInfoBuffer->VolumeSize = MultU64x32 (TotalClusters, BytesPerCluster);
      FileSystemInfoBuffer->FreeSpace  = MultU64x32 (FreeClusters, BytesPerCluster);
      FileSystemInfoBuffer->BlockSize  = BytesPerCluster;
    } else {
      //
      // try GetDiskFreeSpaceEx then
      //
      FileSystemInfoBuffer->BlockSize = 0;
      NtStatus                        = GetDiskFreeSpaceEx (
                                          PrivateFile->FilePath,
                                          (PULARGE_INTEGER)(&FileSystemInfoBuffer->FreeSpace),
                                          (PULARGE_INTEGER)(&FileSystemInfoBuffer->VolumeSize),
                                          NULL
                                          );
      if (!NtStatus) {
        Status = EFI_DEVICE_ERROR;
        goto Done;
      }
    }

    StrCpyS (
      (CHAR16 *)FileSystemInfoBuffer->VolumeLabel,
      (*BufferSize - SIZE_OF_EFI_FILE_SYSTEM_INFO) / sizeof (CHAR16),
      PrivateRoot->VolumeLabel
      );
    *BufferSize = SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (PrivateRoot->VolumeLabel);
    Status      = EFI_SUCCESS;
  }

  if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
    if (*BufferSize < StrSize (PrivateRoot->VolumeLabel)) {
      *BufferSize = StrSize (PrivateRoot->VolumeLabel);
      Status      = EFI_BUFFER_TOO_SMALL;
      goto Done;
    }

    StrCpyS (
      (CHAR16 *)Buffer,
      *BufferSize / sizeof (CHAR16),
      PrivateRoot->VolumeLabel
      );
    *BufferSize = StrSize (PrivateRoot->VolumeLabel);
    Status      = EFI_SUCCESS;
  }

Done:
  return Status;
}

/**
  Set information about a file

  @param  File            Protocol instance pointer.
  @param  InformationType Type of information in Buffer.
  @param  BufferSize      Size of buffer.
  @param  Buffer          The data to write.

  @retval EFI_SUCCESS          Data was returned.
  @retval EFI_UNSUPPORTED      InformationType is not supported.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_WRITE_PROTECTED  The device is write protected.
  @retval EFI_ACCESS_DENIED    The file was open for read only.

**/
EFI_STATUS
WinNtFileSetInfo (
  IN EFI_FILE_PROTOCOL  *This,
  IN EFI_GUID           *InformationType,
  IN UINTN              BufferSize,
  IN VOID               *Buffer
  )
{
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE  *PrivateRoot;
  WIN_NT_EFI_FILE_PRIVATE            *PrivateFile;
  EFI_FILE_INFO                      *OldFileInfo;
  EFI_FILE_INFO                      *NewFileInfo;
  EFI_STATUS                         Status;
  UINTN                              OldInfoSize;
  INTN                               NtStatus;
  UINT32                             NewAttr;
  UINT32                             OldAttr;
  CHAR16                             *OldFileName;
  CHAR16                             *NewFileName;
  CHAR16                             *TempFileName;
  CHAR16                             *CharPointer;
  BOOLEAN                            AttrChangeFlag;
  BOOLEAN                            NameChangeFlag;
  BOOLEAN                            SizeChangeFlag;
  BOOLEAN                            TimeChangeFlag;
  UINT64                             CurPos;
  SYSTEMTIME                         NewCreationSystemTime;
  SYSTEMTIME                         NewLastAccessSystemTime;
  SYSTEMTIME                         NewLastWriteSystemTime;
  FILETIME                           NewCreationFileTime;
  FILETIME                           NewLastAccessFileTime;
  FILETIME                           NewLastWriteFileTime;
  WIN32_FIND_DATA                    FindBuf;
  EFI_FILE_SYSTEM_INFO               *NewFileSystemInfo;
  UINTN                              Size;

  //
  // Initialise locals.
  //
  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);
  PrivateRoot = WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_DATA_FROM_THIS (PrivateFile->SimpleFileSystem);

  Status         = EFI_UNSUPPORTED;
  OldFileInfo    = NewFileInfo = NULL;
  OldFileName    = NewFileName = NULL;
  AttrChangeFlag = NameChangeFlag = SizeChangeFlag = TimeChangeFlag = FALSE;

  //
  // Set file system information.
  //
  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
    NewFileSystemInfo = (EFI_FILE_SYSTEM_INFO *)Buffer;
    if (BufferSize < SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (NewFileSystemInfo->VolumeLabel)) {
      Status = EFI_BAD_BUFFER_SIZE;
      goto Done;
    }

    FreePool (PrivateRoot->VolumeLabel);
    PrivateRoot->VolumeLabel = AllocatePool (StrSize (NewFileSystemInfo->VolumeLabel));
    if (PrivateRoot->VolumeLabel == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    StrCpyS (
      PrivateRoot->VolumeLabel,
      StrSize (NewFileSystemInfo->VolumeLabel) / sizeof (CHAR16),
      NewFileSystemInfo->VolumeLabel
      );

    Status = EFI_SUCCESS;
    goto Done;
  }

  //
  // Set volume label information.
  //
  if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
    if (BufferSize < StrSize (PrivateRoot->VolumeLabel)) {
      Status = EFI_BAD_BUFFER_SIZE;
      goto Done;
    }

    StrCpyS (
      PrivateRoot->VolumeLabel,
      StrSize (PrivateRoot->VolumeLabel) / sizeof (CHAR16),
      (CHAR16 *)Buffer
      );

    Status = EFI_SUCCESS;
    goto Done;
  }

  if (!CompareGuid (InformationType, &gEfiFileInfoGuid)) {
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

  if (BufferSize < SIZE_OF_EFI_FILE_INFO) {
    Status = EFI_BAD_BUFFER_SIZE;
    goto Done;
  }

  //
  // Set file/directory information.
  //

  //
  // Check for invalid set file information parameters.
  //
  NewFileInfo = (EFI_FILE_INFO *)Buffer;

  if ((NewFileInfo->Size <= SIZE_OF_EFI_FILE_INFO) ||
      (NewFileInfo->Attribute &~(EFI_FILE_VALID_ATTR)) ||
      ((sizeof (UINTN) == 4) && (NewFileInfo->Size > 0xFFFFFFFF))
      )
  {
    Status = EFI_INVALID_PARAMETER;
    goto Done;
  }

  //
  // bugbug: - This is not safe.  We need something like EfiStrMaxSize()
  // that would have an additional parameter that would be the size
  // of the string array just in case there are no NULL characters in
  // the string array.
  //
  //
  // Get current file information so we can determine what kind
  // of change request this is.
  //
  OldInfoSize = 0;
  Status      = WinNtSimpleFileSystemFileInfo (PrivateFile, &OldInfoSize, NULL);

  if (Status != EFI_BUFFER_TOO_SMALL) {
    Status = EFI_DEVICE_ERROR;
    goto Done;
  }

  OldFileInfo = AllocatePool (OldInfoSize);
  if (OldFileInfo == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  Status = WinNtSimpleFileSystemFileInfo (PrivateFile, &OldInfoSize, OldFileInfo);

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

  OldFileName = AllocatePool (StrSize (PrivateFile->FileName));
  if (OldFileName == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  StrCpyS (
    OldFileName,
    StrSize (PrivateFile->FileName) / sizeof (CHAR16),
    PrivateFile->FileName
    );

  //
  // Make full pathname from new filename and rootpath.
  //
  if (NewFileInfo->FileName[0] == '\\') {
    Size        = StrSize (PrivateRoot->FilePath);
    Size       += StrSize (L"\\");
    Size       += StrSize (NewFileInfo->FileName);
    NewFileName = AllocatePool (Size);
    if (NewFileName == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    StrCpyS (NewFileName, Size / sizeof (CHAR16), PrivateRoot->FilePath);
    StrCatS (NewFileName, Size / sizeof (CHAR16), L"\\");
    StrCatS (NewFileName, Size / sizeof (CHAR16), NewFileInfo->FileName + 1);
  } else {
    Size        = StrSize (PrivateFile->FilePath);
    Size       += StrSize (L"\\");
    Size       += StrSize (NewFileInfo->FileName);
    NewFileName = AllocatePool (Size);
    if (NewFileName == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    StrCpyS (NewFileName, Size / sizeof (CHAR16), PrivateFile->FilePath);
    StrCatS (NewFileName, Size / sizeof (CHAR16), L"\\");
    StrCatS (NewFileName, Size / sizeof (CHAR16), NewFileInfo->FileName);
  }

  //
  // Is there an attribute change request?
  //
  if (NewFileInfo->Attribute != OldFileInfo->Attribute) {
    if ((NewFileInfo->Attribute & EFI_FILE_DIRECTORY) != (OldFileInfo->Attribute & EFI_FILE_DIRECTORY)) {
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }

    AttrChangeFlag = TRUE;
  }

  //
  // Is there a name change request?
  // bugbug: - Need EfiStrCaseCmp()
  //
  if (StrCmp (NewFileInfo->FileName, OldFileInfo->FileName)) {
    NameChangeFlag = TRUE;
  }

  //
  // Is there a size change request?
  //
  if (NewFileInfo->FileSize != OldFileInfo->FileSize) {
    SizeChangeFlag = TRUE;
  }

  //
  // Is there a time stamp change request?
  //
  if (!IsZero (&NewFileInfo->CreateTime, sizeof (EFI_TIME)) &&
      CompareMem (&NewFileInfo->CreateTime, &OldFileInfo->CreateTime, sizeof (EFI_TIME))
      )
  {
    TimeChangeFlag = TRUE;
  } else if (!IsZero (&NewFileInfo->LastAccessTime, sizeof (EFI_TIME)) &&
             CompareMem (&NewFileInfo->LastAccessTime, &OldFileInfo->LastAccessTime, sizeof (EFI_TIME))
             )
  {
    TimeChangeFlag = TRUE;
  } else if (!IsZero (&NewFileInfo->ModificationTime, sizeof (EFI_TIME)) &&
             CompareMem (&NewFileInfo->ModificationTime, &OldFileInfo->ModificationTime, sizeof (EFI_TIME))
             )
  {
    TimeChangeFlag = TRUE;
  }

  //
  // All done if there are no change requests being made.
  //
  if (!(AttrChangeFlag || NameChangeFlag || SizeChangeFlag || TimeChangeFlag)) {
    Status = EFI_SUCCESS;
    goto Done;
  }

  //
  // Set file or directory information.
  //
  OldAttr = GetFileAttributes (OldFileName);

  //
  // Name change.
  //
  if (NameChangeFlag) {
    //
    // Close the handles first
    //
    if (PrivateFile->IsOpenedByRead) {
      Status = EFI_ACCESS_DENIED;
      goto Done;
    }

    for (CharPointer = NewFileName; *CharPointer != 0 && *CharPointer != L'/'; CharPointer++) {
    }

    if (*CharPointer != 0) {
      Status = EFI_ACCESS_DENIED;
      goto Done;
    }

    if (PrivateFile->LHandle != INVALID_HANDLE_VALUE) {
      if (PrivateFile->IsDirectoryPath) {
        FindClose (PrivateFile->LHandle);
      } else {
        CloseHandle (PrivateFile->LHandle);
        PrivateFile->LHandle = INVALID_HANDLE_VALUE;
      }
    }

    if (PrivateFile->IsDirectoryPath && (PrivateFile->DirHandle != INVALID_HANDLE_VALUE)) {
      CloseHandle (PrivateFile->DirHandle);
      PrivateFile->DirHandle = INVALID_HANDLE_VALUE;
    }

    NtStatus = MoveFile (OldFileName, NewFileName);

    if (NtStatus) {
      //
      // modify file name
      //
      FreePool (PrivateFile->FileName);

      PrivateFile->FileName = AllocatePool (StrSize (NewFileName));
      if (PrivateFile->FileName == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Done;
      }

      StrCpyS (PrivateFile->FileName, StrSize (NewFileName) / sizeof (CHAR16), NewFileName);

      Size         = StrSize (NewFileName);
      Size        += StrSize (L"\\*");
      TempFileName = AllocatePool (Size);

      StrCpyS (TempFileName, Size / sizeof (CHAR16), NewFileName);

      if (!PrivateFile->IsDirectoryPath) {
        PrivateFile->LHandle = CreateFile (
                                 TempFileName,
                                 PrivateFile->IsOpenedByRead ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 NULL,
                                 OPEN_EXISTING,
                                 0,
                                 NULL
                                 );

        FreePool (TempFileName);

        //
        //  Flush buffers just in case
        //
        if (FlushFileBuffers (PrivateFile->LHandle) == 0) {
          Status = EFI_DEVICE_ERROR;
          goto Done;
        }
      } else {
        PrivateFile->DirHandle = CreateFile (
                                   TempFileName,
                                   PrivateFile->IsOpenedByRead ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_FLAG_BACKUP_SEMANTICS,
                                   NULL
                                   );

        StrCatS (TempFileName, Size / sizeof (CHAR16), L"\\*");
        PrivateFile->LHandle = FindFirstFile (TempFileName, &FindBuf);

        FreePool (TempFileName);
      }
    } else {
      Status = EFI_ACCESS_DENIED;
Reopen:;

      NtStatus = SetFileAttributes (OldFileName, OldAttr);

      if (!NtStatus) {
        goto Done;
      }

      Size         = StrSize (OldFileName);
      Size        += StrSize (L"\\*");
      TempFileName = AllocatePool (Size);

      StrCpyS (TempFileName, Size / sizeof (CHAR16), OldFileName);

      if (!PrivateFile->IsDirectoryPath) {
        PrivateFile->LHandle = CreateFile (
                                 TempFileName,
                                 PrivateFile->IsOpenedByRead ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 NULL,
                                 OPEN_EXISTING,
                                 0,
                                 NULL
                                 );
      } else {
        PrivateFile->DirHandle = CreateFile (
                                   TempFileName,
                                   PrivateFile->IsOpenedByRead ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_FLAG_BACKUP_SEMANTICS,
                                   NULL
                                   );

        StrCatS (TempFileName, Size / sizeof (CHAR16), L"\\*");
        PrivateFile->LHandle = FindFirstFile (TempFileName, &FindBuf);
      }

      FreePool (TempFileName);

      goto Done;
    }
  }

  //
  //  Size change
  //
  if (SizeChangeFlag) {
    if (PrivateFile->IsDirectoryPath) {
      Status = EFI_UNSUPPORTED;
      goto Done;
    }

    if (PrivateFile->IsOpenedByRead || OldFileInfo->Attribute & EFI_FILE_READ_ONLY) {
      Status = EFI_ACCESS_DENIED;
      goto Done;
    }

    Status = This->GetPosition (This, &CurPos);
    if (EFI_ERROR (Status)) {
      goto Done;
    }

    Status = This->SetPosition (This, NewFileInfo->FileSize);
    if (EFI_ERROR (Status)) {
      goto Done;
    }

    if (SetEndOfFile (PrivateFile->LHandle) == 0) {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }

    Status = This->SetPosition (This, CurPos);
    if (EFI_ERROR (Status)) {
      goto Done;
    }
  }

  //
  // Time change
  //
  if (TimeChangeFlag) {
    NewCreationSystemTime.wYear         = NewFileInfo->CreateTime.Year;
    NewCreationSystemTime.wMonth        = NewFileInfo->CreateTime.Month;
    NewCreationSystemTime.wDay          = NewFileInfo->CreateTime.Day;
    NewCreationSystemTime.wHour         = NewFileInfo->CreateTime.Hour;
    NewCreationSystemTime.wMinute       = NewFileInfo->CreateTime.Minute;
    NewCreationSystemTime.wSecond       = NewFileInfo->CreateTime.Second;
    NewCreationSystemTime.wMilliseconds = 0;

    if (!SystemTimeToFileTime (
           &NewCreationSystemTime,
           &NewCreationFileTime
           ))
    {
      goto Done;
    }

    if (!LocalFileTimeToFileTime (
           &NewCreationFileTime,
           &NewCreationFileTime
           ))
    {
      goto Done;
    }

    NewLastAccessSystemTime.wYear         = NewFileInfo->LastAccessTime.Year;
    NewLastAccessSystemTime.wMonth        = NewFileInfo->LastAccessTime.Month;
    NewLastAccessSystemTime.wDay          = NewFileInfo->LastAccessTime.Day;
    NewLastAccessSystemTime.wHour         = NewFileInfo->LastAccessTime.Hour;
    NewLastAccessSystemTime.wMinute       = NewFileInfo->LastAccessTime.Minute;
    NewLastAccessSystemTime.wSecond       = NewFileInfo->LastAccessTime.Second;
    NewLastAccessSystemTime.wMilliseconds = 0;

    if (!SystemTimeToFileTime (
           &NewLastAccessSystemTime,
           &NewLastAccessFileTime
           ))
    {
      goto Done;
    }

    if (!LocalFileTimeToFileTime (
           &NewLastAccessFileTime,
           &NewLastAccessFileTime
           ))
    {
      goto Done;
    }

    NewLastWriteSystemTime.wYear         = NewFileInfo->ModificationTime.Year;
    NewLastWriteSystemTime.wMonth        = NewFileInfo->ModificationTime.Month;
    NewLastWriteSystemTime.wDay          = NewFileInfo->ModificationTime.Day;
    NewLastWriteSystemTime.wHour         = NewFileInfo->ModificationTime.Hour;
    NewLastWriteSystemTime.wMinute       = NewFileInfo->ModificationTime.Minute;
    NewLastWriteSystemTime.wSecond       = NewFileInfo->ModificationTime.Second;
    NewLastWriteSystemTime.wMilliseconds = 0;

    if (!SystemTimeToFileTime (
           &NewLastWriteSystemTime,
           &NewLastWriteFileTime
           ))
    {
      goto Done;
    }

    if (!LocalFileTimeToFileTime (
           &NewLastWriteFileTime,
           &NewLastWriteFileTime
           ))
    {
      goto Done;
    }

    if (!SetFileTime (
           PrivateFile->IsDirectoryPath ? PrivateFile->DirHandle : PrivateFile->LHandle,
           &NewCreationFileTime,
           &NewLastAccessFileTime,
           &NewLastWriteFileTime
           ))
    {
      Status = EFI_DEVICE_ERROR;
      goto Done;
    }
  }

  //
  // No matter about AttrChangeFlag, Attribute must be set.
  // Because operation before may cause attribute change.
  //
  NewAttr = OldAttr;

  if (NewFileInfo->Attribute & EFI_FILE_ARCHIVE) {
    NewAttr |= FILE_ATTRIBUTE_ARCHIVE;
  } else {
    NewAttr &= ~FILE_ATTRIBUTE_ARCHIVE;
  }

  if (NewFileInfo->Attribute & EFI_FILE_HIDDEN) {
    NewAttr |= FILE_ATTRIBUTE_HIDDEN;
  } else {
    NewAttr &= ~FILE_ATTRIBUTE_HIDDEN;
  }

  if (NewFileInfo->Attribute & EFI_FILE_SYSTEM) {
    NewAttr |= FILE_ATTRIBUTE_SYSTEM;
  } else {
    NewAttr &= ~FILE_ATTRIBUTE_SYSTEM;
  }

  if (NewFileInfo->Attribute & EFI_FILE_READ_ONLY) {
    NewAttr |= FILE_ATTRIBUTE_READONLY;
  } else {
    NewAttr &= ~FILE_ATTRIBUTE_READONLY;
  }

  NtStatus = SetFileAttributes (NewFileName, NewAttr);

  if (!NtStatus) {
    Status = EFI_DEVICE_ERROR;
    goto Reopen;
  }

Done:
  if (OldFileInfo != NULL) {
    FreePool (OldFileInfo);
  }

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

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

  return Status;
}

/**
  Flush data back for the file handle.

  @param  This Protocol instance pointer.

  @retval EFI_SUCCESS          Data was written.
  @retval EFI_UNSUPPORTED      Writes to Open directory are not supported.
  @retval EFI_NO_MEDIA         The device has no media.
  @retval EFI_DEVICE_ERROR     The device reported an error.
  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
  @retval EFI_WRITE_PROTECTED  The device is write protected.
  @retval EFI_ACCESS_DENIED    The file was open for read only.
  @retval EFI_VOLUME_FULL      The volume is full.

**/
EFI_STATUS
WinNtFileFlush (
  IN EFI_FILE_PROTOCOL  *This
  )
{
  BY_HANDLE_FILE_INFORMATION  FileInfo;
  WIN_NT_EFI_FILE_PRIVATE     *PrivateFile;
  EFI_STATUS                  Status;

  PrivateFile = WIN_NT_EFI_FILE_PRIVATE_DATA_FROM_THIS (This);

  if (PrivateFile->LHandle == INVALID_HANDLE_VALUE) {
    Status = EFI_DEVICE_ERROR;
    goto Done;
  }

  if (PrivateFile->IsDirectoryPath) {
    Status = EFI_SUCCESS;
    goto Done;
  }

  if (PrivateFile->IsOpenedByRead) {
    Status = EFI_ACCESS_DENIED;
    goto Done;
  }

  GetFileInformationByHandle (PrivateFile->LHandle, &FileInfo);

  if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
    Status = EFI_ACCESS_DENIED;
    goto Done;
  }

  Status = FlushFileBuffers (PrivateFile->LHandle) ? EFI_SUCCESS : EFI_DEVICE_ERROR;

Done:
  return Status;
  //
  // bugbug: - Use Windows error reporting.
  //
}

EFI_STATUS
WinNtFileSystmeThunkOpen (
  IN  EMU_IO_THUNK_PROTOCOL  *This
  )
{
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE  *Private;

  Private = AllocateZeroPool (sizeof (*Private));
  if (Private == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Private->FilePath = AllocateCopyPool (StrSize (This->ConfigString), This->ConfigString);
  if (Private->FilePath == NULL) {
    FreePool (Private);
    return EFI_OUT_OF_RESOURCES;
  }

  Private->VolumeLabel = AllocateCopyPool (StrSize (L"EFI_EMULATED"), L"EFI_EMULATED");
  if (Private->VolumeLabel == NULL) {
    FreePool (Private->FilePath);
    FreePool (Private);
    return EFI_OUT_OF_RESOURCES;
  }

  Private->Signature = WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_SIGNATURE;
  Private->Thunk     = This;
  CopyMem (&Private->SimpleFileSystem, &gWinNtFileSystemProtocol, sizeof (Private->SimpleFileSystem));

  This->Interface = &Private->SimpleFileSystem;
  This->Private   = Private;
  return EFI_SUCCESS;
}

EFI_STATUS
WinNtFileSystmeThunkClose (
  IN  EMU_IO_THUNK_PROTOCOL  *This
  )
{
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE  *Private;

  Private = This->Private;
  ASSERT (Private != NULL);

  if (Private->VolumeLabel != NULL) {
    FreePool (Private->VolumeLabel);
  }

  if (Private->FilePath != NULL) {
    FreePool (Private->FilePath);
  }

  FreePool (Private);
  return EFI_SUCCESS;
}

EFI_FILE_PROTOCOL  gWinNtFileProtocol = {
  EFI_FILE_REVISION,
  WinNtFileOpen,
  WinNtFileClose,
  WinNtFileDelete,
  WinNtFileRead,
  WinNtFileWrite,
  WinNtFileGetPossition,
  WinNtFileSetPossition,
  WinNtFileGetInfo,
  WinNtFileSetInfo,
  WinNtFileFlush
};

EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  gWinNtFileSystemProtocol = {
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION,
  WinNtOpenVolume
};

EMU_IO_THUNK_PROTOCOL  mWinNtFileSystemThunkIo = {
  &gEfiSimpleFileSystemProtocolGuid,
  NULL,
  NULL,
  0,
  WinNtFileSystmeThunkOpen,
  WinNtFileSystmeThunkClose,
  NULL
};
