diff options
Diffstat (limited to 'src/md/enc/stgio.cpp')
-rw-r--r-- | src/md/enc/stgio.cpp | 1437 |
1 files changed, 1437 insertions, 0 deletions
diff --git a/src/md/enc/stgio.cpp b/src/md/enc/stgio.cpp new file mode 100644 index 0000000000..27d5ff2c5d --- /dev/null +++ b/src/md/enc/stgio.cpp @@ -0,0 +1,1437 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// StgIO.h +// + +// +// This module handles disk/memory i/o for a generic set of storage solutions, +// including: +// * File system handle (HFILE) +// * IStream +// * User supplied memory buffer (non-movable) +// +// The Read, Write, Seek, ... functions are all directed to the corresponding +// method for each type of file, allowing the consumer to use one set of api's. +// +// File system data can be paged fully into memory in two scenarios: +// read: Normal memory mapped file is created to manage paging. +// write: A custom paging system provides storage for pages as required. This +// data is invalidated when you call Rewrite on the file. +// +// Transactions and backups are handled in the existing file case only. The +// Rewrite function can make a backup of the current contents, and the Restore +// function can be used to recover the data into the current scope. The backup +// file is flushed to disk (which is slower but safer) after the copy. The +// Restore also flushed the recovered changes to disk. Worst case scenario you +// get a crash after calling Rewrite but before Restore, in which case you will +// have a foo.clb.txn file in the same directory as the source file, foo.clb in +// this example. +//<REVISIT_TODO> +// @FUTURE: issues, +// 1. For reading a .clb in an image, it would be great to memory map +// only the portion of the file with the .clb in it. +//</REVISIT_TODO> +//***************************************************************************** +#include "stdafx.h" // Standard headers. +#include "stgio.h" // Our definitions. +#include "corerror.h" +#include "posterror.h" +#include "pedecoder.h" +#include "pedecoder.inl" + +//********** Types. *********************************************************** +#if !defined(FEATURE_METADATA_STANDALONE_WINRT_RO) +#define SMALL_ALLOC_MAP_SIZE (64 * 1024) // 64 kb is the minimum size of virtual + // memory you can allocate, so anything + // less is a waste of VM resources. + +#else //FEATURE_METADATA_STANDALONE_WINRT_RO +// RoMetadata.dll is required to call CreateFileMapping on all WinMD files (even small ones) to use +// Code Intergrity checks on Win8 - see code:#EnableCodeIntegrity +#define SMALL_ALLOC_MAP_SIZE 0 + +#endif //FEATURE_METADATA_STANDALONE_WINRT_RO + +#define MIN_WRITE_CACHE_BYTES (16 * 1024) // 16 kb for a write back cache + + +//********** Locals. ********************************************************** +HRESULT MapFileError(DWORD error); +static void *AllocateMemory(int iSize); +static void FreeMemory(void *pbData); +inline HRESULT MapFileError(DWORD error) +{ + return (PostError(HRESULT_FROM_WIN32(error))); +} + +// Static to class. +int StgIO::m_iPageSize=0; // Size of an OS page. +int StgIO::m_iCacheSize=0; // Size for the write cache. + + + +//********** Code. ************************************************************ +StgIO::StgIO( + bool bAutoMap) : // Memory map for read on open? + m_bAutoMap(bAutoMap) +{ + CtorInit(); + + // If the system page size has not been queried, do so now. + if (m_iPageSize == 0) + { + SYSTEM_INFO sInfo; // Some O/S information. + + // Query the system page size. + GetSystemInfo(&sInfo); + m_iPageSize = sInfo.dwPageSize; + m_iCacheSize = ((MIN_WRITE_CACHE_BYTES - 1) & ~(m_iPageSize - 1)) + m_iPageSize; + } +} + + +void StgIO::CtorInit() +{ + m_bWriteThrough = false; + m_bRewrite = false; + m_bFreeMem = false; + m_pIStream = 0; + m_hFile = INVALID_HANDLE_VALUE; + m_hModule = NULL; + m_hMapping = 0; + m_pBaseData = 0; + m_pData = 0; + m_cbData = 0; + m_fFlags = 0; + m_iType = STGIO_NODATA; + m_cbOffset = 0; + m_rgBuff = 0; + m_cbBuff = 0; + m_rgPageMap = 0; + m_FileType = FILETYPE_UNKNOWN; + m_cRef = 1; + m_mtMappedType = MTYPE_NOMAPPING; +} + + + +StgIO::~StgIO() +{ + if (m_rgBuff) + { + FreeMemory(m_rgBuff); + m_rgBuff = 0; + } + + Close(); +} + + +//***************************************************************************** +// Open the base file on top of: (a) file, (b) memory buffer, or (c) stream. +// If create flag is specified, then this will create a new file with the +// name supplied. No data is read from an opened file. You must call +// MapFileToMem before doing direct pointer access to the contents. +//***************************************************************************** +HRESULT StgIO::Open( // Return code. + LPCWSTR szName, // Name of the storage. + int fFlags, // How to open the file. + const void *pbBuff, // Optional buffer for memory. + ULONG cbBuff, // Size of buffer. + IStream *pIStream, // Stream for input. + LPSECURITY_ATTRIBUTES pAttributes) // Security token. +{ + HRESULT hr; + + // If we were given the storage memory to begin with, then use it. + if (pbBuff && cbBuff) + { + _ASSERTE((fFlags & DBPROP_TMODEF_WRITE) == 0); + + // Save the memory address and size only. No handles. + m_pData = (void *) pbBuff; + m_cbData = cbBuff; + +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + // All access to data will be by memory provided. + if ((fFlags & DBPROP_TMODEF_SHAREDMEM) == DBPROP_TMODEF_SHAREDMEM) + { + // We're taking ownership of this memory + m_pBaseData = m_pData; + m_iType = STGIO_SHAREDMEM; + } + else +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + { + m_iType = STGIO_MEM; + } + goto ErrExit; + } + // Check for data backed by a stream pointer. + else if (pIStream) + { + // If this is for the non-create case, get the size of existing data. + if ((fFlags & DBPROP_TMODEF_CREATE) == 0) + { + LARGE_INTEGER iMove = { { 0, 0 } }; + ULARGE_INTEGER iSize; + + // Need the size of the data so we can map it into memory. + if (FAILED(hr = pIStream->Seek(iMove, STREAM_SEEK_END, &iSize))) + return (hr); + m_cbData = iSize.u.LowPart; + } + // Else there is nothing. + else + m_cbData = 0; + + // Save an addref'd copy of the stream. + m_pIStream = pIStream; + m_pIStream->AddRef(); + + // All access to data will be by memory provided. + m_iType = STGIO_STREAM; + goto ErrExit; + } + + // If not on memory, we need a file to do a create/open. + if (!szName || !*szName) + { + return (PostError(E_INVALIDARG)); + } + // Check for create of a new file. + else if (fFlags & DBPROP_TMODEF_CREATE) + { + //<REVISIT_TODO>@future: This could chose to open the file in write through + // mode, which would provide better Duribility (from ACID props), + // but would be much slower.</REVISIT_TODO> + + // Create the new file, overwriting only if caller allows it. + if ((m_hFile = WszCreateFile(szName, GENERIC_READ | GENERIC_WRITE, 0, 0, + (fFlags & DBPROP_TMODEF_FAILIFTHERE) ? CREATE_NEW : CREATE_ALWAYS, + 0, 0)) == INVALID_HANDLE_VALUE) + { + return (MapFileError(GetLastError())); + } + + // Data will come from the file. + m_iType = STGIO_HFILE; + } + // For open in read mode, need to open the file on disk. If opening a shared + // memory view, it has to be opened already, so no file open. + else if ((fFlags & DBPROP_TMODEF_WRITE) == 0) + { + // We have not opened the file nor loaded it as module + _ASSERTE(m_hFile == INVALID_HANDLE_VALUE); + _ASSERTE(m_hModule == NULL); + + // Open the file for read. Sharing is determined by caller, it can + // allow other readers or be exclusive. + DWORD dwFileSharingFlags = FILE_SHARE_DELETE; + if (!(fFlags & DBPROP_TMODEF_EXCLUSIVE)) + { + dwFileSharingFlags |= FILE_SHARE_READ; + +#if !defined(DACCESS_COMPILE) && !defined(FEATURE_PAL) + // PEDecoder is not defined in DAC + + // We prefer to use LoadLibrary if we can because it will share already loaded images (used for execution) + // which saves virtual memory. We only do this if our caller has indicated that this PE file is trusted + // and thus it is OK to do LoadLibrary (note that we still only load it as a resource, which mitigates + // most of the security risk anyway). + if ((fFlags & DBPROP_TMODEF_TRYLOADLIBRARY) != 0) + { + m_hModule = WszLoadLibraryEx(szName, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE); + if (m_hModule != NULL) + { + m_iType = STGIO_HMODULE; + + m_mtMappedType = MTYPE_IMAGE; + + // LoadLibraryEx returns 2 lowest bits indicating how the module was loaded + m_pBaseData = m_pData = (void *)(((INT_PTR)m_hModule) & ~(INT_PTR)0x3); + + PEDecoder peDecoder; + if (SUCCEEDED(peDecoder.Init( + m_pBaseData, + false)) && // relocated + peDecoder.CheckNTHeaders()) + { + m_cbData = peDecoder.GetNTHeaders32()->OptionalHeader.SizeOfImage; + } + else + { + // PEDecoder failed on loaded library, let's backout all our changes to this object + // and fall back to file mapping + m_iType = STGIO_NODATA; + m_mtMappedType = MTYPE_NOMAPPING; + m_pBaseData = m_pData = NULL; + + FreeLibrary(m_hModule); + m_hModule = NULL; + } + } + } +#endif //!DACCESS_COMPILE && !FEATURE_PAL + } + + if (m_hModule == NULL) + { // We didn't get the loaded module (we either didn't want to or it failed) + HandleHolder hFile(WszCreateFile(szName, + GENERIC_READ, + dwFileSharingFlags, + 0, + OPEN_EXISTING, + 0, + 0)); + + if (hFile == INVALID_HANDLE_VALUE) + return (MapFileError(GetLastError())); + + // Get size of file. + m_cbData = ::SetFilePointer(hFile, 0, 0, FILE_END); + + // Can't read anything from an empty file. + if (m_cbData == 0) + return (PostError(CLDB_E_NO_DATA)); + + // Data will come from the file. + m_hFile = hFile.Extract(); + + m_iType = STGIO_HFILE; + } + } + +ErrExit: + + // If we will ever write, then we need the buffer cache. + if (fFlags & DBPROP_TMODEF_WRITE) + { + // Allocate a cache buffer for writing. + if ((m_rgBuff = (BYTE *) AllocateMemory(m_iCacheSize)) == NULL) + { + Close(); + return PostError(OutOfMemory()); + } + m_cbBuff = 0; + } + + // Save flags for later. + m_fFlags = fFlags; + if ((szName != NULL) && (*szName != 0)) + { + WCHAR rcExt[_MAX_PATH]; + SplitPath(szName, NULL, 0, NULL, 0, NULL, 0, rcExt, _MAX_PATH); + if (SString::_wcsicmp(rcExt, W(".obj")) == 0) + { + m_FileType = FILETYPE_NTOBJ; + } + else if (SString::_wcsicmp(rcExt, W(".tlb")) == 0) + { + m_FileType = FILETYPE_TLB; + } + } + + // For auto map case, map the view of the file as part of open. + if (m_bAutoMap && + (m_iType == STGIO_HFILE || m_iType == STGIO_STREAM) && + !(fFlags & DBPROP_TMODEF_CREATE)) + { + void * ptr; + ULONG cb; + + if (FAILED(hr = MapFileToMem(ptr, &cb, pAttributes))) + { + Close(); + return hr; + } + } + return S_OK; +} // StgIO::Open + + +//***************************************************************************** +// Shut down the file handles and allocated objects. +//***************************************************************************** +void StgIO::Close() +{ + switch (m_iType) + { +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + // Free any allocated memory. + case STGIO_SHAREDMEM: + if (m_pBaseData != NULL) + { + CoTaskMemFree(m_pBaseData); + m_pBaseData = NULL; + break; + } +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + + case STGIO_MEM: + case STGIO_HFILEMEM: + if (m_bFreeMem && m_pBaseData) + { + FreeMemory(m_pBaseData); + m_pBaseData = m_pData = 0; + } + // Intentional fall through to file case, if we kept handle open. + + case STGIO_HFILE: + { + // Free the file handle. + if (m_hFile != INVALID_HANDLE_VALUE) + CloseHandle(m_hFile); + + // If we allocated space for in memory paging, then free it. + } + break; + + case STGIO_HMODULE: + { + if (m_hModule != NULL) + FreeLibrary(m_hModule); + m_hModule = NULL; + break; + } + + // Free the stream pointer. + case STGIO_STREAM: + { + if (m_pIStream != NULL) + m_pIStream->Release(); + } + break; + + // Weird to shut down what you didn't open, isn't it? Allow for + // error case where dtor shuts down as an afterthought. + case STGIO_NODATA: + default: + return; + } + + // Free any page map and base data. + FreePageMap(); + + // Reset state values so we don't get confused. + CtorInit(); +} + +//***************************************************************************** +// Called to read the data into allocated memory and release the backing store. +// Only available on read-only data. +//***************************************************************************** +HRESULT +StgIO::LoadFileToMemory() +{ + HRESULT hr; + void *pData; // Allocated buffer for file. + ULONG cbData; // Size of the data. + ULONG cbRead = 0; // Data actually read. + + // Make sure it is a read-only file. + if (m_fFlags & DBPROP_TMODEF_WRITE) + return E_INVALIDARG; + + // Try to allocate the buffer. + cbData = m_cbData; + pData = AllocateMemory(cbData); + IfNullGo(pData); + + // Try to read the file into the buffer. + IfFailGo(Read(pData, cbData, &cbRead)); + if (cbData != cbRead) + { + _ASSERTE_MSG(FALSE, "Read didn't succeed."); + IfFailGo(CLDB_E_FILE_CORRUPT); + } + + // Done with the old data. + Close(); + + // Open with new data. + hr = Open(NULL /* szName */, STGIO_READ, pData, cbData, NULL /* IStream* */, NULL /* lpSecurityAttributes */); + _ASSERTE(SUCCEEDED(hr)); // should not be a failure code path with open on buffer. + + // Mark the new memory so that it will be freed later. + m_pBaseData = m_pData; + m_bFreeMem = true; + +ErrExit: + if (FAILED(hr) && pData) + FreeMemory(pData); + + return hr; +} // StgIO::LoadFileToMemory + + +//***************************************************************************** +// Read data from the storage source. This will handle all types of backing +// storage from mmf, streams, and file handles. No read ahead or MRU +// caching is done. +//***************************************************************************** +HRESULT StgIO::Read( // Return code. + void *pbBuff, // Write buffer here. + ULONG cbBuff, // How much to read. + ULONG *pcbRead) // How much read. +{ + ULONG cbCopy; // For boundary checks. + void *pbData; // Data buffer for mem read. + HRESULT hr = S_OK; + + // Validate arguments, don't call if you don't need to. + _ASSERTE(pbBuff != 0); + _ASSERTE(cbBuff > 0); + + // Get the data based on type. + switch (m_iType) + { + // For data on file, there are two possiblities: + // (1) We have an in memory backing store we should use, or + // (2) We just need to read from the file. + case STGIO_HFILE: + case STGIO_HMODULE: + { + _ASSERTE((m_hFile != INVALID_HANDLE_VALUE) || (m_hModule != NULL)); + + // Backing store does its own paging. + if (IsBackingStore() || IsMemoryMapped()) + { + // Force the data into memory. + if (FAILED(hr = GetPtrForMem(GetCurrentOffset(), cbBuff, pbData))) + goto ErrExit; + + // Copy it back for the user and save the size. + memcpy(pbBuff, pbData, cbBuff); + if (pcbRead) + *pcbRead = cbBuff; + } + // If there is no backing store, this is just a read operation. + else + { + _ASSERTE((m_iType == STGIO_HFILE) && (m_hFile != INVALID_HANDLE_VALUE)); + _ASSERTE(m_hModule == NULL); + + ULONG cbTemp = 0; + if (!pcbRead) + pcbRead = &cbTemp; + hr = ReadFromDisk(pbBuff, cbBuff, pcbRead); + m_cbOffset += *pcbRead; + } + } + break; + + // Data in a stream is always just read. + case STGIO_STREAM: + { + _ASSERTE((IStream *) m_pIStream); + if (!pcbRead) + pcbRead = &cbCopy; + *pcbRead = 0; + hr = m_pIStream->Read(pbBuff, cbBuff, pcbRead); + if (SUCCEEDED(hr)) + m_cbOffset += *pcbRead; + } + break; + + // Simply copy the data from our data. + case STGIO_MEM: +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + case STGIO_SHAREDMEM: +#endif + case STGIO_HFILEMEM: + { + _ASSERTE(m_pData && m_cbData); + + // Check for read past end of buffer and adjust. + if (GetCurrentOffset() + cbBuff > m_cbData) + cbCopy = m_cbData - GetCurrentOffset(); + else + cbCopy = cbBuff; + + // Copy the data into the callers buffer. + memcpy(pbBuff, (void *) ((DWORD_PTR)m_pData + GetCurrentOffset()), cbCopy); + if (pcbRead) + *pcbRead = cbCopy; + + // Save a logical offset. + m_cbOffset += cbCopy; + } + break; + + case STGIO_NODATA: + default: + _ASSERTE(0); + break; + } + +ErrExit: + return (hr); +} + + +//***************************************************************************** +// Write to disk. This function will cache up to a page of data in a buffer +// and peridocially flush it on overflow and explicit request. This makes it +// safe to do lots of small writes without too much performance overhead. +//***************************************************************************** +HRESULT StgIO::Write( // true/false. + const void *pbBuff, // Data to write. + ULONG cbWrite, // How much data to write. + ULONG *pcbWritten) // How much did get written. +{ + ULONG cbWriteIn=cbWrite; // Track amount written. + ULONG cbCopy; + HRESULT hr = S_OK; + + _ASSERTE(m_rgBuff != 0); + _ASSERTE(cbWrite); + + while (cbWrite) + { + // In the case where the buffer is already huge, write the whole thing + // and avoid the cache. + if (m_cbBuff == 0 && cbWrite >= (ULONG) m_iPageSize) + { + if (SUCCEEDED(hr = WriteToDisk(pbBuff, cbWrite, pcbWritten))) + m_cbOffset += cbWrite; + break; + } + // Otherwise cache as much as we can and flush. + else + { + // Determine how much data goes into the cache buffer. + cbCopy = m_iPageSize - m_cbBuff; + cbCopy = min(cbCopy, cbWrite); + + // Copy the data into the cache and adjust counts. + memcpy(&m_rgBuff[m_cbBuff], pbBuff, cbCopy); + pbBuff = (void *) ((DWORD_PTR)pbBuff + cbCopy); + m_cbBuff += cbCopy; + m_cbOffset += cbCopy; + cbWrite -= cbCopy; + + // If there is enough data, then flush it to disk and reset count. + if (m_cbBuff >= (ULONG) m_iPageSize) + { + if (FAILED(hr = FlushCache())) + break; + } + } + } + + // Return value for caller. + if (SUCCEEDED(hr) && pcbWritten) + *pcbWritten = cbWriteIn; + return (hr); +} + + +//***************************************************************************** +// Moves the file pointer to the new location. This handles the different +// types of storage systems. +//***************************************************************************** +HRESULT StgIO::Seek( // New offset. + int lVal, // How much to move. + ULONG fMoveType) // Direction, use Win32 FILE_xxxx. +{ + ULONG cbRtn = 0; + HRESULT hr = NOERROR; + + _ASSERTE(fMoveType >= FILE_BEGIN && fMoveType <= FILE_END); + + // Action taken depends on type of storage. + switch (m_iType) + { + case STGIO_HFILE: + { + // Use the file system's move. + _ASSERTE(m_hFile != INVALID_HANDLE_VALUE); + cbRtn = ::SetFilePointer(m_hFile, lVal, 0, fMoveType); + + // Save the location redundantly. + if (cbRtn != 0xffffffff) + { + // make sure that m_cbOffset will stay within range + if (cbRtn > m_cbData || cbRtn < 0) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = cbRtn; + } + } + break; + + case STGIO_STREAM: + { + LARGE_INTEGER iMove; + ULARGE_INTEGER iNewLoc; + + // Need a 64-bit int. + iMove.QuadPart = lVal; + + // The move types are named differently, but have same value. + if (FAILED(hr = m_pIStream->Seek(iMove, fMoveType, &iNewLoc))) + return (hr); + + // make sure that m_cbOffset will stay within range + if (iNewLoc.u.LowPart > m_cbData || iNewLoc.u.LowPart < 0) + IfFailGo(STG_E_INVALIDFUNCTION); + + // Save off only out location. + m_cbOffset = iNewLoc.u.LowPart; + } + break; + + case STGIO_MEM: +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + case STGIO_SHAREDMEM: +#endif + case STGIO_HFILEMEM: + case STGIO_HMODULE: + { + // We own the offset, so change our value. + switch (fMoveType) + { + case FILE_BEGIN: + + // make sure that m_cbOffset will stay within range + if ((ULONG) lVal > m_cbData || lVal < 0) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = lVal; + break; + + case FILE_CURRENT: + + // make sure that m_cbOffset will stay within range + if (m_cbOffset + lVal > m_cbData) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = m_cbOffset + lVal; + break; + + case FILE_END: + _ASSERTE(lVal < (LONG) m_cbData); + // make sure that m_cbOffset will stay within range + if (m_cbData + lVal > m_cbData) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = m_cbData + lVal; + break; + } + + cbRtn = m_cbOffset; + } + break; + + // Weird to seek with no data. + case STGIO_NODATA: + default: + _ASSERTE(0); + break; + } + +ErrExit: + return hr; +} + + +//***************************************************************************** +// Retrieves the current offset for the storage being used. This value is +// tracked based on Read, Write, and Seek operations. +//***************************************************************************** +ULONG StgIO::GetCurrentOffset() // Current offset. +{ + return (m_cbOffset); +} + + +//***************************************************************************** +// Map the file contents to a memory mapped file and return a pointer to the +// data. For read/write with a backing store, map the file using an internal +// paging system. +//***************************************************************************** +HRESULT StgIO::MapFileToMem( // Return code. + void *&ptr, // Return pointer to file data. + ULONG *pcbSize, // Return size of data. + LPSECURITY_ATTRIBUTES pAttributes) // Security token. +{ + char rcShared[MAXSHMEM]; // ANSI version of shared name. + HRESULT hr = S_OK; + + // Don't penalize for multiple calls. Also, allow calls for mem type so + // callers don't need to do so much checking. + if (IsBackingStore() || + IsMemoryMapped() || + (m_iType == STGIO_MEM) || +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + (m_iType == STGIO_SHAREDMEM) || +#endif + (m_iType == STGIO_HFILEMEM)) + { + ptr = m_pData; + if (pcbSize) + *pcbSize = m_cbData; + return (S_OK); + } + + //#CopySmallFiles + // Check the size of the data we want to map. If it is small enough, then + // simply allocate a chunk of memory from a finer grained heap. This saves + // virtual memory space, page table entries, and should reduce overall working set. + // Also, open for read/write needs a full backing store. + if ((m_cbData <= SMALL_ALLOC_MAP_SIZE) && (SMALL_ALLOC_MAP_SIZE > 0)) + { + DWORD cbRead = m_cbData; + _ASSERTE(m_pData == 0); + + // Just malloc a chunk of data to use. + m_pBaseData = m_pData = AllocateMemory(m_cbData); + if (!m_pData) + { + hr = OutOfMemory(); + goto ErrExit; + } + + // Read all of the file contents into this piece of memory. + IfFailGo( Seek(0, FILE_BEGIN) ); + if (FAILED(hr = Read(m_pData, cbRead, &cbRead))) + { + FreeMemory(m_pData); + m_pData = 0; + goto ErrExit; + } + _ASSERTE(cbRead == m_cbData); + + // If the file isn't being opened for exclusive mode, then free it. + // If it is for exclusive, then we need to keep the handle open so the + // file is locked, preventing other readers. Also leave it open if + // in read/write mode so we can truncate and rewrite. + if (m_hFile == INVALID_HANDLE_VALUE || + ((m_fFlags & DBPROP_TMODEF_EXCLUSIVE) == 0 && (m_fFlags & DBPROP_TMODEF_WRITE) == 0)) + { + // If there was a handle open, then free it. + if (m_hFile != INVALID_HANDLE_VALUE) + { + VERIFY(CloseHandle(m_hFile)); + m_hFile = INVALID_HANDLE_VALUE; + } + // Free the stream pointer. + else + if (m_pIStream != 0) + { + m_pIStream->Release(); + m_pIStream = 0; + } + + // Switch the type to memory only access. + m_iType = STGIO_MEM; + } + else + m_iType = STGIO_HFILEMEM; + + // Free the memory when we shut down. + m_bFreeMem = true; + } + // Finally, a real mapping file must be created. + else + { + // Now we will map, so better have it right. + _ASSERTE(m_hFile != INVALID_HANDLE_VALUE || m_iType == STGIO_STREAM); + _ASSERTE(m_rgPageMap == 0); + + // For read mode, use a memory mapped file since the size will never + // change for the life of the handle. + if ((m_fFlags & DBPROP_TMODEF_WRITE) == 0 && m_iType != STGIO_STREAM) + { + // Create a mapping object for the file. + _ASSERTE(m_hMapping == 0); + + DWORD dwProtectionFlags = PAGE_READONLY; +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + //#EnableCodeIntegrity + // RoMetadata.dll is required to always map (WinMD) files with SEC_IMAGE to enable Code Integrity checkes on Win8 + // Note: MidlRtMd.dll cannot do the same, because it runs on pre-Win8 OS versions where SEC_IMAGE-mapping will likely + // refuse WinMD files (they are Win8+ only in PE headers) + dwProtectionFlags |= SEC_IMAGE; +#endif + + if ((m_hMapping = WszCreateFileMapping(m_hFile, pAttributes, dwProtectionFlags, + 0, 0, nullptr)) == 0) + { + return (MapFileError(GetLastError())); + } +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + m_mtMappedType = MTYPE_IMAGE; +#else // FEATURE_METADATA_STANDALONE_WINRT_RO + m_mtMappedType = MTYPE_FLAT; +#endif // FEATURE_METADATA_STANDALONE_WINRT_RO + // Check to see if the memory already exists, in which case we have + // no guarantees it is the right piece of data. + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + hr = PostError(CLDB_E_SMDUPLICATE, rcShared); + goto ErrExit; + } + + // Now map the file into memory so we can read from pointer access. + // <REVISIT_TODO>Note: Added a check for IsBadReadPtr per the Services team which + // indicates that under some conditions this API can give you back + // a totally bogus pointer.</REVISIT_TODO> + if ((m_pBaseData = m_pData = MapViewOfFile(m_hMapping, FILE_MAP_READ, + 0, 0, 0)) == 0) + { + hr = MapFileError(GetLastError()); + if (SUCCEEDED(hr)) + { + _ASSERTE_MSG(FALSE, "Error code doesn't indicate error."); + hr = PostError(CLDB_E_FILE_CORRUPT); + } + + // In case we got back a bogus pointer. + m_pBaseData = m_pData = NULL; + goto ErrExit; + } + } + // In write mode, we need the hybrid combination of being able to back up + // the data in memory via cache, but then later rewrite the contents and + // throw away our cached copy. Memory mapped files are not good for this + // case due to poor write characteristics. + else + { + ULONG iMaxSize; // How much memory required for file. + + // Figure out how many pages we'll require, round up actual data + // size to page size. + iMaxSize = (((m_cbData - 1) & ~(m_iPageSize - 1)) + m_iPageSize); + // Check integer overflow in previous statement + if (iMaxSize < m_cbData) + { + IfFailGo(PostError(COR_E_OVERFLOW)); + } + + // Allocate a bit vector to track loaded pages. + if ((m_rgPageMap = new (nothrow) BYTE[iMaxSize / m_iPageSize]) == 0) + return (PostError(OutOfMemory())); + memset(m_rgPageMap, 0, sizeof(BYTE) * (iMaxSize / m_iPageSize)); + + // Allocate space for the file contents. + if ((m_pBaseData = m_pData = ::ClrVirtualAlloc(0, iMaxSize, MEM_RESERVE, PAGE_NOACCESS)) == 0) + { + hr = PostError(OutOfMemory()); + goto ErrExit; + } + } + } + + // Reset any changes made by mapping. + IfFailGo( Seek(0, FILE_BEGIN) ); + +ErrExit: + + // Check for errors and clean up. + if (FAILED(hr)) + { + if (m_hMapping) + CloseHandle(m_hMapping); + m_hMapping = 0; + m_pBaseData = m_pData = 0; + m_cbData = 0; + } + ptr = m_pData; + if (pcbSize) + *pcbSize = m_cbData; + return (hr); +} + + +//***************************************************************************** +// Free the mapping object for shared memory but keep the rest of the internal +// state intact. +//***************************************************************************** +HRESULT StgIO::ReleaseMappingObject() // Return code. +{ + // Check type first. +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + if (m_iType != STGIO_SHAREDMEM) + { + _ASSERTE(FALSE); + return S_OK; + } + + // Must have an allocated handle. + _ASSERTE(m_hMapping != 0); + + // Freeing the mapping object doesn't do any good if you still have the file. + _ASSERTE(m_hFile == INVALID_HANDLE_VALUE); + + // Unmap the memory we allocated before freeing the handle. But keep the + // memory address intact. + if (m_pData) + VERIFY(UnmapViewOfFile(m_pData)); + + // Free the handle. + if (m_hMapping != 0) + { + VERIFY(CloseHandle(m_hMapping)); + m_hMapping = 0; + } +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + return S_OK; +} + + + +//***************************************************************************** +// Resets the logical base address and size to the value given. This is for +// cases like finding a section embedded in another format, like the .clb inside +// of an image. GetPtrForMem, Read, and Seek will then behave as though only +// data from pbStart to cbSize is valid. +//***************************************************************************** +HRESULT StgIO::SetBaseRange( // Return code. + void *pbStart, // Start of file data. + ULONG cbSize) // How big is the range. +{ +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + if (m_iType == STGIO_SHAREDMEM) + { + // The base range must be inside of the current range. + _ASSERTE((m_pBaseData != NULL) && (m_cbData != 0)); + _ASSERTE(((LONG_PTR) pbStart >= (LONG_PTR) m_pBaseData)); + _ASSERTE(((LONG_PTR) pbStart + cbSize <= (LONG_PTR) m_pBaseData + m_cbData)); + } +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + + // Save the base range per user request. + m_pData = pbStart; + m_cbData = cbSize; + return S_OK; +} + + +//***************************************************************************** +// Caller wants a pointer to a chunk of the file. This function will make sure +// that the memory for that chunk has been committed and will load from the +// file if required. This algorithm attempts to load no more data from disk +// than is necessary. It walks the required pages from lowest to highest, +// and for each block of unloaded pages, the memory is committed and the data +// is read from disk. If all pages are unloaded, all of them are loaded at +// once to speed throughput from disk. +//***************************************************************************** +HRESULT StgIO::GetPtrForMem( // Return code. + ULONG cbStart, // Where to start getting memory. + ULONG cbSize, // How much data. + void *&ptr) // Return pointer to memory here. +{ + int iFirst, iLast; // First and last page required. + ULONG iOffset, iSize; // For committing ranges of memory. + int i, j; // Loop control. + HRESULT hr; + + // We need either memory (mmf or user supplied) or a backing store to + // return a pointer. Call Read if you don't have these. + if (!IsBackingStore() && m_pData == 0) + return (PostError(BadError(E_UNEXPECTED))); + + // Validate the caller isn't asking for a data value out of range. + if (!(ClrSafeInt<ULONG>::addition(cbStart, cbSize, iOffset) + && (iOffset <= m_cbData))) + return (PostError(E_INVALIDARG)); + + // This code will check for pages that need to be paged from disk in + // order for us to return a pointer to that memory. + if (IsBackingStore()) + { + // Backing store is bogus when in rewrite mode. + if (m_bRewrite) + return (PostError(BadError(E_UNEXPECTED))); + + // Must have the page map to continue. + _ASSERTE(m_rgPageMap && m_iPageSize && m_pData); + + // Figure out the first and last page that are required for commit. + iFirst = cbStart / m_iPageSize; + iLast = (cbStart + cbSize - 1) / m_iPageSize; + + // Avoid confusion. + ptr = 0; + + // Do a smart load of every page required. Do not reload pages that have + // already been brought in from disk. + //<REVISIT_TODO>@FUTURE: add an optimization so that when all pages have been faulted, we no + // longer to a page by page search.</REVISIT_TODO> + for (i=iFirst; i<=iLast; ) + { + // Find the first page that hasn't already been loaded. + while (GetBit(m_rgPageMap, i) && i<=iLast) + ++i; + if (i > iLast) + break; + + // Offset for first thing to load. + iOffset = i * m_iPageSize; + iSize = 0; + + // See how many in a row have not been loaded. + for (j=i; i<=iLast && !GetBit(m_rgPageMap, i); i++) + { + // Safe: iSize += m_iPageSize; + if (!(ClrSafeInt<ULONG>::addition(iSize, m_iPageSize, iSize))) + { + return PostError(E_INVALIDARG); + } + } + + // First commit the memory for this part of the file. + if (::ClrVirtualAlloc((void *) ((DWORD_PTR) m_pData + iOffset), + iSize, MEM_COMMIT, PAGE_READWRITE) == 0) + return (PostError(OutOfMemory())); + + // Now load that portion of the file from disk. + if (FAILED(hr = Seek(iOffset, FILE_BEGIN)) || + FAILED(hr = ReadFromDisk((void *) ((DWORD_PTR) m_pData + iOffset), iSize, 0))) + { + return (hr); + } + + // Change the memory to read only to avoid any modifications. Any faults + // that occur indicate a bug whereby the engine is trying to write to + // protected memory. + _ASSERTE(::ClrVirtualAlloc((void *) ((DWORD_PTR) m_pData + iOffset), + iSize, MEM_COMMIT, PAGE_READONLY) != 0); + + // Record each new loaded page. + for (; j<i; j++) + SetBit(m_rgPageMap, j, true); + } + + // Everything was brought into memory, so now return pointer to caller. + ptr = (void *) ((DWORD_PTR) m_pData + cbStart); + } + // Memory version or memory mapped file work the same way. + else if (IsMemoryMapped() || + (m_iType == STGIO_MEM) || +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + (m_iType == STGIO_SHAREDMEM) || +#endif + (m_iType == STGIO_HFILEMEM)) + { + if (!(cbStart <= m_cbData)) + return (PostError(E_INVALIDARG)); + + ptr = (void *) ((DWORD_PTR) m_pData + cbStart); + } + // What's left?! Add some defense. + else + { + _ASSERTE(0); + ptr = 0; + return (PostError(BadError(E_UNEXPECTED))); + } + return (S_OK); +} + + +//***************************************************************************** +// For cached writes, flush the cache to the data store. +//***************************************************************************** +HRESULT StgIO::FlushCache() +{ + ULONG cbWritten; + HRESULT hr; + + if (m_cbBuff) + { + if (FAILED(hr = WriteToDisk(m_rgBuff, m_cbBuff, &cbWritten))) + return (hr); + m_cbBuff = 0; + } + return (S_OK); +} + +//***************************************************************************** +// Tells the file system to flush any cached data it may have. This is +// expensive, but if successful guarantees you won't lose writes short of +// a disk failure. +//***************************************************************************** +HRESULT StgIO::FlushFileBuffers() +{ + _ASSERTE(!IsReadOnly()); + + if (m_hFile != INVALID_HANDLE_VALUE) + { + if (::FlushFileBuffers(m_hFile)) + return (S_OK); + else + return (MapFileError(GetLastError())); + } + return (S_OK); +} + + +//***************************************************************************** +// Called after a successful rewrite of an existing file. The in memory +// backing store is no longer valid because all new data is in memory and +// on disk. This is essentially the same state as created, so free up some +// working set and remember this state. +//***************************************************************************** +HRESULT StgIO::ResetBackingStore() // Return code. +{ + // Don't be calling this function for read only data. + _ASSERTE(!IsReadOnly()); + + // Free up any backing store data we no longer need now that everything + // is in memory. + FreePageMap(); + return (S_OK); +} + + +// +// Private. +// + + + +//***************************************************************************** +// This version will force the data in cache out to disk for real. The code +// can handle the different types of storage we might be sitting on based on +// the open type. +//***************************************************************************** +HRESULT StgIO::WriteToDisk( // Return code. + const void *pbBuff, // Buffer to write. + ULONG cbWrite, // How much. + ULONG *pcbWritten) // Return how much written. +{ + ULONG cbWritten; // Buffer for write funcs. + HRESULT hr = S_OK; + + // Pretty obvious. + _ASSERTE(!IsReadOnly()); + + // Always need a buffer to write this data to. + if (!pcbWritten) + pcbWritten = &cbWritten; + + // Action taken depends on type of storage. + switch (m_iType) + { + case STGIO_HFILE: + case STGIO_HFILEMEM: + { + // Use the file system's move. + _ASSERTE(m_hFile != INVALID_HANDLE_VALUE); + + // Do the write to disk. + if (!::WriteFile(m_hFile, pbBuff, cbWrite, pcbWritten, 0)) + hr = MapFileError(GetLastError()); + } + break; + + // Free the stream pointer. + case STGIO_STREAM: + { + // Delegate write to stream code. + hr = m_pIStream->Write(pbBuff, cbWrite, pcbWritten); + } + break; + + // We cannot write to fixed read/only memory or LoadLibrary module. + case STGIO_HMODULE: + case STGIO_MEM: +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + case STGIO_SHAREDMEM: +#endif + _ASSERTE(0); + hr = BadError(E_UNEXPECTED); + break; + + // Weird to seek with no data. + case STGIO_NODATA: + default: + _ASSERTE(0); + break; + } + return (hr); +} + + +//***************************************************************************** +// This version only reads from disk. +//***************************************************************************** +HRESULT StgIO::ReadFromDisk( // Return code. + void *pbBuff, // Write buffer here. + ULONG cbBuff, // How much to read. + ULONG *pcbRead) // How much read. +{ + ULONG cbRead; + + _ASSERTE(m_iType == STGIO_HFILE || m_iType == STGIO_STREAM); + + // Need to have a buffer. + if (!pcbRead) + pcbRead = &cbRead; + + // Read only from file to avoid recursive logic. + if (m_iType == STGIO_HFILE || m_iType == STGIO_HFILEMEM) + { + if (::ReadFile(m_hFile, pbBuff, cbBuff, pcbRead, 0)) + return (S_OK); + return (MapFileError(GetLastError())); + } + // Read directly from stream. + else + { + return (m_pIStream->Read(pbBuff, cbBuff, pcbRead)); + } +} + + +//***************************************************************************** +// Copy the contents of the file for this storage to the target path. +//***************************************************************************** +HRESULT StgIO::CopyFileInternal( // Return code. + LPCWSTR szTo, // Target save path for file. + int bFailIfThere, // true to fail if target exists. + int bWriteThrough) // Should copy be written through OS cache. +{ + DWORD iCurrent; // Save original location. + DWORD cbRead; // Byte count for buffer. + DWORD cbWrite; // Check write of bytes. + const DWORD cbBuff = 4096; // Size of buffer for copy (in bytes). + BYTE *pBuff = (BYTE*)alloca(cbBuff); // Buffer for copy. + HANDLE hFile; // Target file. + HRESULT hr = S_OK; + + // Create target file. + if ((hFile = ::WszCreateFile(szTo, GENERIC_WRITE, 0, 0, + (bFailIfThere) ? CREATE_NEW : CREATE_ALWAYS, + (bWriteThrough) ? FILE_FLAG_WRITE_THROUGH : 0, + 0)) == INVALID_HANDLE_VALUE) + { + return (MapFileError(GetLastError())); + } + + // Save current location and reset it later. + iCurrent = ::SetFilePointer(m_hFile, 0, 0, FILE_CURRENT); + ::SetFilePointer(m_hFile, 0, 0, FILE_BEGIN); + + // Copy while there are bytes. + while (::ReadFile(m_hFile, pBuff, cbBuff, &cbRead, 0) && cbRead) + { + if (!::WriteFile(hFile, pBuff, cbRead, &cbWrite, 0) || cbWrite != cbRead) + { + hr = STG_E_WRITEFAULT; + break; + } + } + + // Reset file offset. + ::SetFilePointer(m_hFile, iCurrent, 0, FILE_BEGIN); + + // Close target. + if (!bWriteThrough) + VERIFY(::FlushFileBuffers(hFile)); + ::CloseHandle(hFile); + return (hr); +} + + +//***************************************************************************** +// Free the data used for backing store from disk in read/write scenario. +//***************************************************************************** +void StgIO::FreePageMap() +{ + // If a small file was allocated, then free that memory. + if (m_bFreeMem && m_pBaseData) + FreeMemory(m_pBaseData); + // For mmf, close handles and free resources. + else if (m_hMapping && m_pBaseData) + { + VERIFY(UnmapViewOfFile(m_pBaseData)); + VERIFY(CloseHandle(m_hMapping)); + } + // For our own system, free memory. + else if (m_rgPageMap && m_pBaseData) + { + delete [] m_rgPageMap; + m_rgPageMap = 0; + VERIFY(::ClrVirtualFree(m_pBaseData, (((m_cbData - 1) & ~(m_iPageSize - 1)) + m_iPageSize), MEM_DECOMMIT)); + VERIFY(::ClrVirtualFree(m_pBaseData, 0, MEM_RELEASE)); + m_pBaseData = 0; + m_cbData = 0; + } + + m_pBaseData = 0; + m_hMapping = 0; + m_cbData = 0; +} + + +//***************************************************************************** +// Check the given pointer and ensure it is aligned correct. Return true +// if it is aligned, false if it is not. +//***************************************************************************** +int StgIO::IsAlignedPtr(ULONG_PTR Value, int iAlignment) +{ + HRESULT hr; + void *ptrStart = NULL; + + if ((m_iType == STGIO_STREAM) || +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + (m_iType == STGIO_SHAREDMEM) || +#endif + (m_iType == STGIO_MEM)) + { + return ((Value - (ULONG_PTR) m_pData) % iAlignment == 0); + } + else + { + hr = GetPtrForMem(0, 1, ptrStart); + _ASSERTE(hr == S_OK && "GetPtrForMem failed"); + _ASSERTE(Value > (ULONG_PTR) ptrStart); + return (((Value - (ULONG_PTR) ptrStart) % iAlignment) == 0); + } +} // int StgIO::IsAlignedPtr() + + + + + +//***************************************************************************** +// These helper functions are used to allocate fairly large pieces of memory, +// more than should be taken from the runtime heap, but less that would require +// virtual memory overhead. +//***************************************************************************** +// #define _TRACE_MEM_ 1 + +void *AllocateMemory(int iSize) +{ + void * ptr; + ptr = new (nothrow) BYTE[iSize]; + +#if defined(_DEBUG) && defined(_TRACE_MEM_) + static int i=0; + DbgWriteEx(W("AllocateMemory: (%d) 0x%08x, size %d\n"), ++i, ptr, iSize); +#endif + return (ptr); +} + + +void FreeMemory(void *pbData) +{ +#if defined(_DEBUG) && defined(_TRACE_MEM_) + static int i=0; + DbgWriteEx(W("FreeMemory: (%d) 0x%08x\n"), ++i, pbData); +#endif + + _ASSERTE(pbData); + delete [] (BYTE *) pbData; +} + |