// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license 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. // // @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. // //***************************************************************************** #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) { //@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. // 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. // 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. 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::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. //@FUTURE: add an optimization so that when all pages have been faulted, we no // longer to a page by page search. 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::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 (; jWrite(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; }