//===- llvm/Support/Windows/MSFileSystemBasic.cpp DXComplier Impl *- C++ -*-===//
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// MSFileSystemBasic.cpp                                                     //
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
// This file is distributed under the University of Illinois Open Source     //
// License. See LICENSE.TXT for details.                                     //
//                                                                           //
// This file implements the DXCompiler specific implementation of the Path API.//
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "dxc/Support/WinIncludes.h"
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <errno.h>
#include <D3Dcommon.h>
#include <new>
#include <unordered_map>

#include "llvm/Support/MSFileSystem.h"
#include "dxc/Support/Global.h"

#include "dxc/dxcapi.internal.h"


///////////////////////////////////////////////////////////////////////////////////////////////////
// Externally visible functions.

/// <summary>Creates an implementation based on IDxcSystemAccess.</summary>
HRESULT CreateMSFileSystemForIface(_In_ IUnknown* pService, _COM_Outptr_ ::llvm::sys::fs::MSFileSystem** pResult) throw();

/// <summary>Creates an implementation with no access to system resources.</summary>
HRESULT CreateMSFileSystemBlocked(_COM_Outptr_ ::llvm::sys::fs::MSFileSystem** pResult) throw();

///////////////////////////////////////////////////////////////////////////////////////////////////
// Helper functions.

static
DWORD WIN32_FROM_HRESULT(HRESULT hr)
{
  if (SUCCEEDED(hr)) return ERROR_SUCCESS;
  if ((hr & 0xFFFF0000) == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0))
  {
    // Could have come from many values, but we choose this one
    return HRESULT_CODE(hr);
  }
  if (hr == E_OUTOFMEMORY) return ERROR_OUTOFMEMORY;
  if (hr == E_NOTIMPL) return ERROR_CALL_NOT_IMPLEMENTED;
  return ERROR_FUNCTION_FAILED;
}

static
HRESULT CopyStatStg(_In_ const STATSTG* statStg, _Out_ LPWIN32_FIND_DATAW lpFindFileData)
{
  HRESULT hr = S_OK;
  lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  lpFindFileData->ftCreationTime = statStg->ctime;
  lpFindFileData->ftLastAccessTime = statStg->atime;
  lpFindFileData->ftLastWriteTime = statStg->mtime;
  lpFindFileData->nFileSizeLow = statStg->cbSize.LowPart;
  lpFindFileData->nFileSizeHigh = statStg->cbSize.HighPart;
  if (statStg->pwcsName != nullptr)
  {
    IFC(StringCchCopyW(lpFindFileData->cFileName, _countof(lpFindFileData->cFileName), statStg->pwcsName));
  }

Cleanup:
  return hr;
}

static
void ClearStatStg(_Inout_ STATSTG* statStg)
{
  DXASSERT_NOMSG(statStg != nullptr);
  if (statStg->pwcsName != nullptr)
  {
    CoTaskMemFree(statStg->pwcsName);
    statStg->pwcsName = nullptr;
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// IDxcSystemAccess-based MSFileSystem implementation.

static const int FirstAllocFD = 10;
static const int LastAllocFD = 8 * 1024;
static const HANDLE FirstAllocHandle = (HANDLE)(uintptr_t)FirstAllocFD;
static const HANDLE LastAllocHandle = (HANDLE)(uintptr_t)LastAllocFD;

struct MSFileSystemHandle
{
  enum MSFileSystemHandleKind
  {
    MSFileSystemHandleKind_FindHandle,
    MSFileSystemHandleKind_FileHandle,
    MSFileSystemHandleKind_FileMappingHandle
  };

  MSFileSystemHandleKind kind;
  CComPtr<IUnknown> storage;// For a file handle, the stream or directory handle.
                            // For a find handle, the IEnumSTATSTG associated.
  CComPtr<IStream> stream;  // For a file or console file handle, the stream interface.
  int fd;                   // For a file handle, its file descriptor.

  MSFileSystemHandle(int knownFD)
    : fd(knownFD)
    , kind(MSFileSystemHandleKind_FileHandle)
  {
  }

  MSFileSystemHandle(IUnknown* pMapping)
    : storage(pMapping)
    , kind(MSFileSystemHandleKind_FileMappingHandle)
    , fd(0)
  {
  }

  MSFileSystemHandle(IUnknown* pStorage, IStream* pStream)
    : storage(pStorage)
    , stream(pStream)
    , kind(MSFileSystemHandleKind_FileHandle)
    , fd(0)
  {
  }

  MSFileSystemHandle(IEnumSTATSTG* pEnumSTATG) : storage(pEnumSTATG), kind(MSFileSystemHandleKind_FindHandle)
  {
  }

  MSFileSystemHandle(MSFileSystemHandle&& other)
  {
    kind = other.kind;
    storage.p = other.storage.Detach();
    stream.p = other.stream.Detach();
  }
  
  HANDLE GetHandle() const { return (HANDLE)this; }

  IEnumSTATSTG* GetEnumStatStg()
  {
    DXASSERT(kind == MSFileSystemHandleKind_FindHandle, "otherwise caller didn't check");
    return (IEnumSTATSTG*)storage.p;
  }
};

namespace llvm {
namespace sys  {
namespace fs {

class MSFileSystemForIface : public MSFileSystem
{
private:
  CComPtr<IDxcSystemAccess> m_system;
  typedef std::unordered_multimap<LPCVOID, ID3D10Blob*> TViewMap;
  TViewMap m_mappingViews;
  MSFileSystemHandle m_knownHandle0;
  MSFileSystemHandle m_knownHandle1;
  MSFileSystemHandle m_knownHandle2;

  HRESULT AddFindHandle(_In_ IEnumSTATSTG* enumStatStg, _Out_ HANDLE* pResult) throw();
  HRESULT AddFileHandle(_In_ IUnknown* storage, _In_ IStream* stream, _Out_ HANDLE* pResult) throw();
  HRESULT AddMappingHandle(_In_ IUnknown* mapping, _Out_ HANDLE* pResult) throw();
  HRESULT AddMappingView(_In_ ID3D10Blob* blob) throw();
  HRESULT EnsureFDAvailable(int fd);
  HANDLE GetHandleForFD(int fd) throw();
  void GetFindHandle(HANDLE findHandle, _Outptr_ IEnumSTATSTG** enumStatStg) throw();
  int GetHandleFD(HANDLE fileHandle) throw();
  void GetHandleMapping(HANDLE fileHandle, _Outptr_ IUnknown** pResult) throw();
  void GetHandleStorage(HANDLE fileHandle, _Outptr_ IUnknown** pResult) throw();
  void GetHandleStream(HANDLE fileHandle, _Outptr_ IStream** pResult) throw();
  void CloseInternalHandle(HANDLE findHandle) throw();
  void RemoveMappingView(_In_ LPCVOID address) throw();

public:
  MSFileSystemForIface(_In_ IDxcSystemAccess* access);

  virtual BOOL FindNextFileW(_In_ HANDLE hFindFile, _Out_ LPWIN32_FIND_DATAW lpFindFileData) throw() override;
  virtual HANDLE FindFirstFileW(_In_ LPCWSTR lpFileName, _Out_ LPWIN32_FIND_DATAW lpFindFileData) throw() override;
  virtual void FindClose(HANDLE findHandle) throw() override;
  virtual HANDLE CreateFileW(_In_ LPCWSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes) throw() override;
  virtual BOOL SetFileTime(_In_ HANDLE hFile, _In_opt_ const FILETIME *lpCreationTime, _In_opt_ const FILETIME *lpLastAccessTime, _In_opt_ const FILETIME *lpLastWriteTime) throw() override;
  virtual BOOL GetFileInformationByHandle(_In_ HANDLE hFile, _Out_ LPBY_HANDLE_FILE_INFORMATION lpFileInformation) throw() override;
  virtual DWORD GetFileType(_In_ HANDLE hFile) throw() override;
  virtual BOOL CreateHardLinkW(_In_ LPCWSTR lpFileName, _In_ LPCWSTR lpExistingFileName) throw() override;
  virtual BOOL MoveFileExW(_In_ LPCWSTR lpExistingFileName, _In_opt_ LPCWSTR lpNewFileName, _In_ DWORD dwFlags) throw() override;
  virtual DWORD GetFileAttributesW(_In_ LPCWSTR lpFileName) throw() override;
  virtual BOOL CloseHandle(_In_ HANDLE hObject) throw() override;
  virtual BOOL DeleteFileW(_In_ LPCWSTR lpFileName) throw() override;
  virtual BOOL RemoveDirectoryW(_In_ LPCWSTR lpFileName) throw() override;
  virtual BOOL CreateDirectoryW(_In_ LPCWSTR lpPathName) throw() override;
  _Success_(return != 0 && return < nBufferLength)
  virtual DWORD GetCurrentDirectoryW(_In_ DWORD nBufferLength, _Out_writes_to_opt_(nBufferLength, return +1) LPWSTR lpBuffer) throw() override;
  _Success_(return != 0 && return < nSize)
  virtual DWORD GetMainModuleFileNameW(__out_ecount_part(nSize, return +1) LPWSTR lpFilename, DWORD nSize) throw() override;
  virtual DWORD GetTempPathW(DWORD nBufferLength, _Out_writes_to_opt_(nBufferLength, return +1) LPWSTR lpBuffer) throw() override;
  virtual BOOLEAN CreateSymbolicLinkW(_In_ LPCWSTR lpSymlinkFileName, _In_ LPCWSTR lpTargetFileName, DWORD dwFlags) throw() override;
  virtual bool SupportsCreateSymbolicLink() throw() override;
  virtual BOOL ReadFile(_In_ HANDLE hFile, _Out_ LPVOID lpBuffer, _In_ DWORD nNumberOfBytesToRead, _Out_opt_ LPDWORD lpNumberOfBytesRead) throw() override;
  virtual HANDLE CreateFileMappingW(_In_ HANDLE hFile, _In_ DWORD flProtect, _In_ DWORD dwMaximumSizeHigh, _In_ DWORD dwMaximumSizeLow) throw() override;
  virtual LPVOID MapViewOfFile(_In_ HANDLE hFileMappingObject, _In_ DWORD dwDesiredAccess, _In_ DWORD dwFileOffsetHigh, _In_ DWORD dwFileOffsetLow, _In_ SIZE_T dwNumberOfBytesToMap) throw() override;
  virtual BOOL UnmapViewOfFile(_In_ LPCVOID lpBaseAddress) throw() override;
  
  // Console APIs.
  virtual bool FileDescriptorIsDisplayed(int fd) throw() override;
  virtual unsigned GetColumnCount(DWORD nStdHandle) throw() override;
  virtual unsigned GetConsoleOutputTextAttributes() throw() override;
  virtual void SetConsoleOutputTextAttributes(unsigned attributes) throw() override;
  virtual void ResetConsoleOutputTextAttributes() throw() override;

  // CRT APIs.
  virtual int open_osfhandle(intptr_t osfhandle, int flags) throw() override;
  virtual intptr_t get_osfhandle(int fd) throw() override;
  virtual int close(int fd) throw() override;
  virtual long lseek(int fd, long offset, int origin) throw() override;
  virtual int setmode(int fd, int mode) throw() override;
  virtual errno_t resize_file(_In_ LPCWSTR path, uint64_t size) throw() override;
  virtual int Read(int fd, _Out_bytecap_(count) void* buffer, unsigned int count) throw() override;
  virtual int Write(int fd, _In_bytecount_(count) const void* buffer, unsigned int count) throw() override;
};

_Use_decl_annotations_
MSFileSystemForIface::MSFileSystemForIface(IDxcSystemAccess* systemAccess)
  : m_system(systemAccess)
  , m_knownHandle0(0)
  , m_knownHandle1(1)
  , m_knownHandle2(2)
{
}

_Use_decl_annotations_
HRESULT MSFileSystemForIface::AddMappingHandle(IUnknown* mapping, HANDLE* pResult)
{
  DXASSERT_NOMSG(mapping != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);

  HRESULT hr = S_OK;
  MSFileSystemHandle* handle = nullptr;
  *pResult = INVALID_HANDLE_VALUE;
  handle = new (std::nothrow)MSFileSystemHandle(mapping);
  IFCOOM(handle);
  *pResult = handle->GetHandle();

Cleanup:
  return hr;
}

_Use_decl_annotations_
HRESULT MSFileSystemForIface::AddMappingView(ID3D10Blob* blob)
{
  DXASSERT_NOMSG(blob != nullptr);
  LPVOID address = blob->GetBufferPointer();
  try
  {
    m_mappingViews.insert(std::pair<LPVOID, ID3D10Blob*>(address, blob));
  }
  catch (std::bad_alloc&)
  {
    return E_OUTOFMEMORY;
  }
  blob->AddRef();
  return S_OK;
}

_Use_decl_annotations_
HRESULT MSFileSystemForIface::AddFindHandle(IEnumSTATSTG* enumStatStg, HANDLE* pResult)
{
  DXASSERT_NOMSG(enumStatStg != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);

  HRESULT hr = S_OK;
  MSFileSystemHandle* handle = nullptr;
  *pResult = INVALID_HANDLE_VALUE;
  handle = new (std::nothrow)MSFileSystemHandle(enumStatStg);
  IFCOOM(handle);
  *pResult = handle->GetHandle();

Cleanup:
  return hr;
}

_Use_decl_annotations_
HRESULT MSFileSystemForIface::AddFileHandle(IUnknown* storage, IStream* stream, HANDLE* pResult)
{
  DXASSERT_NOMSG(storage != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);

  HRESULT hr = S_OK;
  MSFileSystemHandle* handle = nullptr;
  *pResult = INVALID_HANDLE_VALUE;
  handle = new (std::nothrow)MSFileSystemHandle(storage, stream);
  IFCOOM(handle);
  *pResult = handle->GetHandle();

Cleanup:
  return hr;
}

void MSFileSystemForIface::CloseInternalHandle(HANDLE handle)
{
  DXASSERT_NOMSG(handle != nullptr);
  DXASSERT_NOMSG(handle != INVALID_HANDLE_VALUE);
  MSFileSystemHandle* fsHandle = reinterpret_cast<MSFileSystemHandle*>(handle);
  if (fsHandle == &m_knownHandle0 || fsHandle == &m_knownHandle1 || fsHandle == &m_knownHandle2)
  {
    fsHandle->stream.Release();
    fsHandle->storage.Release();
  }
  else
  {
    delete fsHandle;
  }
}

_Use_decl_annotations_
void MSFileSystemForIface::RemoveMappingView(LPCVOID address)
{
  TViewMap::iterator i = m_mappingViews.find(address);
  DXASSERT(i != m_mappingViews.end(), "otherwise pointer to view isn't in map");
  DXASSERT(i->second != nullptr, "otherwise blob is null and should not have been added");
  i->second->Release();
  m_mappingViews.erase(i);
}

_Use_decl_annotations_
void MSFileSystemForIface::GetFindHandle(HANDLE findHandle, IEnumSTATSTG** enumStatStg)
{
  DXASSERT_NOMSG(findHandle != nullptr);
  DXASSERT_NOMSG(enumStatStg != nullptr);
  
  MSFileSystemHandle* fsHandle = reinterpret_cast<MSFileSystemHandle*>(findHandle);
  DXASSERT(fsHandle->kind == MSFileSystemHandle::MSFileSystemHandleKind_FindHandle, "otherwise caller is passing wrong handle to API");

  *enumStatStg = fsHandle->GetEnumStatStg();
  DXASSERT(*enumStatStg != nullptr, "otherwise it should not have been added to handle entry");

  (*enumStatStg)->AddRef();
}

int MSFileSystemForIface::GetHandleFD(HANDLE fileHandle)
{
  DXASSERT_NOMSG(fileHandle != nullptr);

  MSFileSystemHandle* fsHandle = reinterpret_cast<MSFileSystemHandle*>(fileHandle);
  DXASSERT(fsHandle->kind == MSFileSystemHandle::MSFileSystemHandleKind_FileHandle, "otherwise caller is passing wrong handle to API");

  return fsHandle->fd;
}

_Use_decl_annotations_
void MSFileSystemForIface::GetHandleMapping(HANDLE mapping, _Outptr_ IUnknown** pResult)
{
  DXASSERT_NOMSG(mapping != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);

  MSFileSystemHandle* fsHandle = reinterpret_cast<MSFileSystemHandle*>(mapping);
  DXASSERT(fsHandle->kind == MSFileSystemHandle::MSFileSystemHandleKind_FileMappingHandle, "otherwise caller is passing wrong handle to API");

  *pResult = fsHandle->storage.p;
  DXASSERT(*pResult != nullptr, "otherwise it should not be requested through GetHandleMapping");

  (*pResult)->AddRef();
}

_Use_decl_annotations_
void MSFileSystemForIface::GetHandleStorage(HANDLE fileHandle, _Outptr_ IUnknown** pResult)
{
  DXASSERT_NOMSG(fileHandle != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);

  MSFileSystemHandle* fsHandle = reinterpret_cast<MSFileSystemHandle*>(fileHandle);
  DXASSERT(fsHandle->kind == MSFileSystemHandle::MSFileSystemHandleKind_FileHandle, "otherwise caller is passing wrong handle to API");

  *pResult = fsHandle->storage.p;
  DXASSERT(*pResult != nullptr, "otherwise it should not be requested through GetHandleStorage");

  (*pResult)->AddRef();
}

_Use_decl_annotations_
void MSFileSystemForIface::GetHandleStream(HANDLE fileHandle, _Outptr_ IStream** pResult)
{
  DXASSERT_NOMSG(fileHandle != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);

  MSFileSystemHandle* fsHandle = reinterpret_cast<MSFileSystemHandle*>(fileHandle);
  DXASSERT(fsHandle->kind == MSFileSystemHandle::MSFileSystemHandleKind_FileHandle, "otherwise caller is passing wrong handle to API");

  *pResult = fsHandle->stream.p;
  DXASSERT(*pResult != nullptr, "otherwise it should not be requested through GetHandleStream");

  (*pResult)->AddRef();
}


_Use_decl_annotations_
HANDLE MSFileSystemForIface::FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData)
{
  HRESULT hr = S_OK;
  CComPtr<IEnumSTATSTG> enumStatStg;
  HANDLE resultValue = INVALID_HANDLE_VALUE;
  STATSTG elt;
  ULONG fetched;

  ZeroMemory(&elt, sizeof(elt));
  ZeroMemory(lpFindFileData, sizeof(*lpFindFileData));
  fetched = 0;

  IFC(m_system->EnumFiles(lpFileName, &enumStatStg));
  IFC(enumStatStg->Next(1, &elt, &fetched));
  if (fetched == 0)
  {
    IFC(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
  }
  else
  {
    IFC(CopyStatStg(&elt, lpFindFileData));
    IFC(AddFindHandle(enumStatStg, &resultValue));
  }

Cleanup:
  ClearStatStg(&elt);
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return INVALID_HANDLE_VALUE;
  }

  DXASSERT(resultValue != INVALID_HANDLE_VALUE, "otherwise AddFindHandle failed to return a valid handle");
  return resultValue;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::FindNextFileW(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData)
{
  HRESULT hr = S_OK;
  CComPtr<IEnumSTATSTG> enumStatStg;
  BOOL resultValue = FALSE;
  STATSTG elt;
  ULONG fetched;

  ZeroMemory(&elt, sizeof(elt));
  ZeroMemory(lpFindFileData, sizeof(*lpFindFileData));
  fetched = 0;

  GetFindHandle(hFindFile, &enumStatStg);
  IFC(enumStatStg->Next(1, &elt, &fetched));
  if (fetched == 0)
  {
    IFC(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
  }
  else
  {
    IFC(CopyStatStg(&elt, lpFindFileData));
    resultValue = TRUE;
  }

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return FALSE;
  }

  return TRUE;
}

void MSFileSystemForIface::FindClose(HANDLE findHandle)
{
  CloseInternalHandle(findHandle);
}

_Use_decl_annotations_
HANDLE MSFileSystemForIface::CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes)
{
  HRESULT hr = S_OK;
  CComPtr<IUnknown> storage;
  CComPtr<IStream> stream;
  HANDLE resultHandle = INVALID_HANDLE_VALUE;
  
  IFC(m_system->OpenStorage(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, dwFlagsAndAttributes, &storage));
  IFC(storage.QueryInterface(&stream));
  IFC(AddFileHandle(storage, stream, &resultHandle));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return INVALID_HANDLE_VALUE;
  }

  return resultHandle;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::SetFileTime(HANDLE hFile, _In_opt_ const FILETIME *lpCreationTime, _In_opt_ const FILETIME *lpLastAccessTime, _In_opt_ const FILETIME *lpLastWriteTime)
{
  HRESULT hr = S_OK;
  CComPtr<IUnknown> storage;

  GetHandleStorage(hFile, &storage);
  IFC(m_system->SetStorageTime(storage, lpCreationTime, lpLastAccessTime, lpLastWriteTime));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return FALSE;
  }

  return TRUE;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::GetFileInformationByHandle(HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation)
{
  HRESULT hr = S_OK;
  CComPtr<IUnknown> storage;

  GetHandleStorage(hFile, &storage);
  IFC(m_system->GetFileInformationForStorage(storage, lpFileInformation));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return FALSE;
  }

  return TRUE;
}

_Use_decl_annotations_
DWORD MSFileSystemForIface::GetFileType(HANDLE hFile)
{
  HRESULT hr = S_OK;
  CComPtr<IUnknown> storage;
  DWORD fileType;

  GetHandleStorage(hFile, &storage);
  IFC(m_system->GetFileTypeForStorage(storage, &fileType));
  if (fileType == FILE_TYPE_UNKNOWN)
  {
    SetLastError(NO_ERROR);
  }

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    fileType = FILE_TYPE_UNKNOWN;
  }

  return fileType;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::CreateHardLinkW(LPCWSTR lpFileName, LPCWSTR lpExistingFileName)
{
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::MoveFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, DWORD dwFlags)
{
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

_Use_decl_annotations_
DWORD MSFileSystemForIface::GetFileAttributesW(LPCWSTR lpFileName)
{
  HRESULT hr = S_OK;
  DWORD attributes;

  IFC(m_system->GetFileAttributesForStorage(lpFileName, &attributes));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    attributes = INVALID_FILE_ATTRIBUTES;
  }

  return attributes;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::CloseHandle(HANDLE hObject)
{
  this->CloseInternalHandle(hObject);
  return TRUE;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::DeleteFileW(LPCWSTR lpFileName)
{
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::RemoveDirectoryW(LPCWSTR lpFileName)
{
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::CreateDirectoryW(LPCWSTR lpPathName)
{
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

_Use_decl_annotations_
DWORD MSFileSystemForIface::GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer)
{
  DWORD written = 0;
  HRESULT hr = S_OK;

  IFC(m_system->GetCurrentDirectoryForStorage(nBufferLength, lpBuffer, &written));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return 0;
  }

  return written;
}

_Use_decl_annotations_
DWORD MSFileSystemForIface::GetMainModuleFileNameW(LPWSTR lpFilename, DWORD nSize)
{
  DWORD written = 0;
  HRESULT hr = S_OK;

  IFC(m_system->GetMainModuleFileNameW(nSize, lpFilename, &written));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return 0;
  }

  return written;
}

_Use_decl_annotations_
DWORD MSFileSystemForIface::GetTempPathW(DWORD nBufferLength, LPWSTR lpBuffer)
{
  DWORD written = 0;
  HRESULT hr = S_OK;

  IFC(m_system->GetTempStoragePath(nBufferLength, lpBuffer, &written));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return 0;
  }

  return written;
}

_Use_decl_annotations_
BOOLEAN MSFileSystemForIface::CreateSymbolicLinkW(LPCWSTR lpSymlinkFileName, LPCWSTR lpTargetFileName, DWORD dwFlags)
{
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

bool MSFileSystemForIface::SupportsCreateSymbolicLink()
{
  return false;
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, _Out_opt_ LPDWORD lpNumberOfBytesRead)
{
  HRESULT hr = S_OK;
  CComPtr<IStream> stream;
  GetHandleStream(hFile, &stream);
  ULONG cbRead;
  IFC(stream->Read(lpBuffer, nNumberOfBytesToRead, &cbRead));
  if (lpNumberOfBytesRead != nullptr)
  {
    *lpNumberOfBytesRead = cbRead;
  }

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return FALSE;
  }

  return TRUE;
}

_Use_decl_annotations_
HANDLE MSFileSystemForIface::CreateFileMappingW(HANDLE hFile, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow)
{
  HRESULT hr = S_OK;
  HANDLE result = INVALID_HANDLE_VALUE;
  CComPtr<IUnknown> storage;
  CComPtr<IUnknown> mapping;

  GetHandleStorage(hFile, &storage);
  IFC(m_system->CreateStorageMapping(storage, flProtect, dwMaximumSizeHigh, dwMaximumSizeLow, &mapping));
  IFC(AddMappingHandle(mapping, &result));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return INVALID_HANDLE_VALUE;
  }

  return result;
}

_Use_decl_annotations_
LPVOID MSFileSystemForIface::MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap)
{
  HRESULT hr = S_OK;
  CComPtr<IUnknown> mapping;
  CComPtr<ID3D10Blob> blob;

  GetHandleMapping(hFileMappingObject, &mapping);
  IFC(m_system->MapViewOfFile(mapping, dwDesiredAccess, dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap, &blob));
  IFC(AddMappingView(blob));

Cleanup:
  if (FAILED(hr))
  {
    SetLastError(WIN32_FROM_HRESULT(hr));
    return INVALID_HANDLE_VALUE;
  }

  return blob->GetBufferPointer();
}

_Use_decl_annotations_
BOOL MSFileSystemForIface::UnmapViewOfFile(LPCVOID lpBaseAddress) throw()
{
  RemoveMappingView(lpBaseAddress);
  return TRUE;
}

bool MSFileSystemForIface::FileDescriptorIsDisplayed(int fd)
{
  return false;
}

unsigned MSFileSystemForIface::GetColumnCount(DWORD nStdHandle)
{
  return 0;
}

unsigned MSFileSystemForIface::GetConsoleOutputTextAttributes() throw()
{
  return 0;
}

void MSFileSystemForIface::SetConsoleOutputTextAttributes(unsigned attributes)
{
  return;
}

void MSFileSystemForIface::ResetConsoleOutputTextAttributes()
{
}

int MSFileSystemForIface::open_osfhandle(intptr_t osfhandle, int flags)
{
  return GetHandleFD((HANDLE)osfhandle);
}

HRESULT MSFileSystemForIface::EnsureFDAvailable(int fd)
{
  MSFileSystemHandle* ptr;
  switch (fd)
  {
  case 0: ptr = &m_knownHandle0; break;
  case 1: ptr = &m_knownHandle1; break;
  case 2: ptr = &m_knownHandle2; break;
  default:
    return S_OK;
  }

  HRESULT hr = S_OK;
  if (ptr->storage == nullptr)
  {
    CComPtr<IUnknown> storage;
    CComPtr<IStream> stream;

    IFC(m_system->OpenStdStorage(fd, &storage));
    IFC(storage.QueryInterface(&stream));

    ptr->storage = storage;
    ptr->stream = stream;
  }

  DXASSERT(ptr->storage != nullptr, "otherwise we should have failed to initialize");
  DXASSERT(ptr->stream != nullptr, "otherwise we should have failed to initialize - input/output/error should support streams");

Cleanup:
  return hr;
}

HANDLE MSFileSystemForIface::GetHandleForFD(int fd)
{
  MSFileSystemHandle* ptr;
  switch (fd)
  {
  case 0:  ptr = &m_knownHandle0; break;
  case 1:  ptr = &m_knownHandle1; break;
  case 2:  ptr = &m_knownHandle2; break;
  default: ptr = (MSFileSystemHandle *)(uintptr_t)fd; break;
  }
  return ptr->GetHandle();
}

intptr_t MSFileSystemForIface::get_osfhandle(int fd) {
  if (FAILED(EnsureFDAvailable(fd))) {
    errno = EBADF;
    return -1;
  }

  return (intptr_t)GetHandleForFD(fd);
}

int MSFileSystemForIface::close(int fd)
{
  HANDLE h = GetHandleForFD(fd);
  this->CloseInternalHandle(h);
  return 0;
}

long MSFileSystemForIface::lseek(int fd, long offset, int origin)
{
  HRESULT hr = S_OK;
  CComPtr<IStream> stream;
  LARGE_INTEGER li;
  ULARGE_INTEGER uli;
  if (FAILED(EnsureFDAvailable(fd)))
  {
    errno = EBADF;
    return -1;
  }

  GetHandleStream(GetHandleForFD(fd), &stream);
  li.HighPart = 0;
  li.LowPart = offset;
  IFC(stream->Seek(li, origin, &uli));

Cleanup:
  if (FAILED(hr))
  {
    errno = EINVAL;
    return -1;
  }

  if (uli.HighPart > 0)
  {
    errno = EOVERFLOW;
    return -1;
  }

  return uli.LowPart;
}

int MSFileSystemForIface::setmode(int fd, int mode)
{
  return 0;
}

_Use_decl_annotations_
errno_t MSFileSystemForIface::resize_file(LPCWSTR path, uint64_t size)
{
  return EBADF;
}

_Use_decl_annotations_
int MSFileSystemForIface::Read(int fd, void* buffer, unsigned int count)
{
  HRESULT hr = S_OK;
  CComPtr<IStream> stream;
  ULONG cbRead = 0;
  if (FAILED(EnsureFDAvailable(fd)))
  {
    errno = EBADF;
    return -1;
  }

  GetHandleStream(GetHandleForFD(fd), &stream);
  IFC(stream->Read(buffer, count, &cbRead));

Cleanup:
  if (FAILED(hr))
  {
    errno = EINVAL;
    return -1;
  }

  return (int)cbRead;
}

_Use_decl_annotations_
int MSFileSystemForIface::Write(int fd, const void* buffer, unsigned int count)
{
  HRESULT hr = S_OK;
  CComPtr<IStream> stream;
  ULONG cbWritten = 0;
  if (FAILED(EnsureFDAvailable(fd)))
  {
    errno = EBADF;
    return -1;
  }

  GetHandleStream(GetHandleForFD(fd), &stream);
  IFC(stream->Write(buffer, count, &cbWritten));

Cleanup:
  if (FAILED(hr))
  {
    errno = EINVAL;
    return -1;
  }

  return (int)cbWritten;
}

} // end namespace fs
} // end namespace sys
} // end namespace llvm

///////////////////////////////////////////////////////////////////////////////////////////////////
// Blocked MSFileSystem implementation.

#ifdef DBG
static void MSFileSystemBlockedCalled() { DebugBreak(); }
#else
static void MSFileSystemBlockedCalled() { }
#endif

static BOOL MSFileSystemBlockedErrWin32()
{
  MSFileSystemBlockedCalled();
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return FALSE;
}

static HANDLE MSFileSystemBlockedHandle()
{
  MSFileSystemBlockedCalled();
  SetLastError(ERROR_FUNCTION_NOT_CALLED);
  return INVALID_HANDLE_VALUE;
}

static int MSFileSystemBlockedErrno()
{
  MSFileSystemBlockedCalled();
  errno = EBADF;
  return -1;
}

static int MSFileSystemBlockedErrnoT()
{
  MSFileSystemBlockedCalled();
  return EBADF;
}

namespace llvm {
namespace sys  {
namespace fs {

class MSFileSystemBlocked : public MSFileSystem
{
private:
public:
  MSFileSystemBlocked();

  virtual BOOL FindNextFileW(_In_ HANDLE , _Out_ LPWIN32_FIND_DATAW ) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual HANDLE FindFirstFileW(_In_ LPCWSTR lpFileName, _Out_ LPWIN32_FIND_DATAW lpFindFileData) throw() override
  { return MSFileSystemBlockedHandle(); }

  virtual void FindClose(HANDLE findHandle) throw() override
  { MSFileSystemBlockedCalled(); }

  virtual HANDLE CreateFileW(_In_ LPCWSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes) throw() override
  { return MSFileSystemBlockedHandle(); }

  virtual BOOL SetFileTime(_In_ HANDLE hFile, _In_opt_ const FILETIME *lpCreationTime, _In_opt_ const FILETIME *lpLastAccessTime, _In_opt_ const FILETIME *lpLastWriteTime) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual BOOL GetFileInformationByHandle(_In_ HANDLE hFile, _Out_ LPBY_HANDLE_FILE_INFORMATION lpFileInformation) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual DWORD GetFileType(_In_ HANDLE hFile) throw() override
  { MSFileSystemBlockedErrWin32(); return FILE_TYPE_UNKNOWN; }

  virtual BOOL CreateHardLinkW(_In_ LPCWSTR lpFileName, _In_ LPCWSTR lpExistingFileName) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual BOOL MoveFileExW(_In_ LPCWSTR lpExistingFileName, _In_opt_ LPCWSTR lpNewFileName, _In_ DWORD dwFlags) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual DWORD GetFileAttributesW(_In_ LPCWSTR lpFileName) throw() override
  { MSFileSystemBlockedErrWin32(); return 0; }

  virtual BOOL CloseHandle(_In_ HANDLE hObject) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual BOOL DeleteFileW(_In_ LPCWSTR lpFileName) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual BOOL RemoveDirectoryW(LPCWSTR lpFileName) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual BOOL CreateDirectoryW(_In_ LPCWSTR lpPathName) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  _Success_(return != 0 && return < nBufferLength)
  virtual DWORD GetCurrentDirectoryW(_In_ DWORD nBufferLength, _Out_writes_to_opt_(nBufferLength, return +1) LPWSTR lpBuffer) throw() override;
  virtual DWORD GetMainModuleFileNameW(__out_ecount_part(nSize, return +1) LPWSTR lpFilename, DWORD nSize) throw() override;
  _Success_(return != 0 && return < nBufferLength)
  virtual DWORD GetTempPathW(DWORD nBufferLength, _Out_writes_to_opt_(nBufferLength, return +1) LPWSTR lpBuffer) throw() override;

  virtual BOOLEAN CreateSymbolicLinkW(_In_ LPCWSTR lpSymlinkFileName, _In_ LPCWSTR lpTargetFileName, DWORD dwFlags) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual bool SupportsCreateSymbolicLink() throw() override
  { MSFileSystemBlockedErrWin32(); return false; }

  virtual BOOL ReadFile(_In_ HANDLE hFile, _Out_ LPVOID lpBuffer, _In_ DWORD nNumberOfBytesToRead, _Out_opt_ LPDWORD lpNumberOfBytesRead) throw() override
  { return MSFileSystemBlockedErrWin32(); }

  virtual HANDLE CreateFileMappingW(_In_ HANDLE hFile, _In_ DWORD flProtect, _In_ DWORD dwMaximumSizeHigh, _In_ DWORD dwMaximumSizeLow) throw() override
  { return MSFileSystemBlockedHandle(); }

  virtual LPVOID MapViewOfFile(_In_ HANDLE hFileMappingObject, _In_ DWORD dwDesiredAccess, _In_ DWORD dwFileOffsetHigh, _In_ DWORD dwFileOffsetLow, _In_ SIZE_T dwNumberOfBytesToMap) throw() override
  { MSFileSystemBlockedErrWin32(); return nullptr; }

  virtual BOOL UnmapViewOfFile(_In_ LPCVOID lpBaseAddress) throw() override
  { return MSFileSystemBlockedErrWin32(); }
  
  // Console APIs.
  virtual bool FileDescriptorIsDisplayed(int fd) throw() override
  { MSFileSystemBlockedCalled(); return false; }

  virtual unsigned GetColumnCount(DWORD nStdHandle) throw() override
  { MSFileSystemBlockedCalled(); return 80; }
  
  virtual unsigned GetConsoleOutputTextAttributes() throw() override
  { MSFileSystemBlockedCalled(); return 0; }

  virtual void SetConsoleOutputTextAttributes(unsigned attributes) throw() override
  { MSFileSystemBlockedCalled(); }
  
  virtual void ResetConsoleOutputTextAttributes() throw() override
  { MSFileSystemBlockedCalled(); }

  // CRT APIs.
  virtual int open_osfhandle(intptr_t osfhandle, int flags) throw() override
  { return MSFileSystemBlockedErrno(); }

  virtual intptr_t get_osfhandle(int fd) throw() override
  { MSFileSystemBlockedErrno(); return 0; }

  virtual int close(int fd) throw() override
  { return MSFileSystemBlockedErrno(); }

  virtual long lseek(int fd, long offset, int origin) throw() override
  { return MSFileSystemBlockedErrno(); }

  virtual int setmode(int fd, int mode) throw() override
  { return MSFileSystemBlockedErrno(); }

  virtual errno_t resize_file(_In_ LPCWSTR path, uint64_t size) throw() override
  { return MSFileSystemBlockedErrnoT(); }

  virtual int Read(int fd, void* buffer, unsigned int count) throw() override
  { return MSFileSystemBlockedErrno(); }

  virtual int Write(int fd, const void* buffer, unsigned int count) throw() override
  { return MSFileSystemBlockedErrno(); }

};

MSFileSystemBlocked::MSFileSystemBlocked()
{
}

_Use_decl_annotations_
DWORD MSFileSystemBlocked::GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer)
{
  if (nBufferLength > 1)
  {
    lpBuffer[0] = L'.';
    lpBuffer[1] = L'\0';
  }
  return 1;
}

_Use_decl_annotations_
DWORD MSFileSystemBlocked::GetMainModuleFileNameW(LPWSTR lpFilename, DWORD nSize)
{
  SetLastError(NO_ERROR);
  return 0;
}

_Use_decl_annotations_
DWORD MSFileSystemBlocked::GetTempPathW(DWORD nBufferLength, LPWSTR lpBuffer)
{
  if (nBufferLength > 1)
  {
    lpBuffer[0] = L'.';
    lpBuffer[1] = L'\0';
  }
  return 1;
}

} // end namespace fs
} // end namespace sys
} // end namespace llvm


///////////////////////////////////////////////////////////////////////////////////////////////////
// Externally visible functions.

_Use_decl_annotations_
HRESULT CreateMSFileSystemForIface(IUnknown* pService, ::llvm::sys::fs::MSFileSystem** pResult) throw()
{
  DXASSERT_NOMSG(pService != nullptr);
  DXASSERT_NOMSG(pResult != nullptr);
  CComPtr<IDxcSystemAccess> systemAccess;

  HRESULT hr = pService->QueryInterface(__uuidof(IDxcSystemAccess), (void**)&systemAccess);
  if (FAILED(hr)) return hr;

  *pResult = new (std::nothrow) ::llvm::sys::fs::MSFileSystemForIface(systemAccess);
  return (*pResult != nullptr) ? S_OK : E_OUTOFMEMORY;
}

_Use_decl_annotations_
HRESULT CreateMSFileSystemBlocked(::llvm::sys::fs::MSFileSystem** pResult) throw()
{
  DXASSERT_NOMSG(pResult != nullptr);
  *pResult = new (std::nothrow) ::llvm::sys::fs::MSFileSystemBlocked();
  return (*pResult != nullptr) ? S_OK : E_OUTOFMEMORY;
}
