//+============================================================================ // // File: nffmstm.cxx // // This file provides the NFF (NTFS Flat File) IMappedStream implementation. // // History: // 5/6/98 MikeHill // - Misc dbg cleanup. // //+============================================================================ #include CNFFMappedStream::~CNFFMappedStream() { HRESULT hr = S_OK; // If the update stream has the latest data, rename it over the original // stream. Ordinarily this replace call will create a new update stream. // But since we're going away, tell it not to bother. // Errors are ignored here because there's no way to return them. // If the caller wishes to avoid this, they should call Flush first. if( NULL != _pstmUpdate ) { ReplaceOriginalWithUpdate( DONT_CREATE_NEW_UPDATE_STREAM ); DfpVerify( 0 == RELEASE_INTERFACE(_pstmUpdate) ); } // Just to be safe, free the mapping buffer (it should have // already been freed). DfpAssert( NULL == _pbMappedStream ); CoTaskMemFree( _pbMappedStream ); // If we've got the global reserved buffer locked, // free it now. if (_fLowMem) g_ReservedMemory.UnlockMemory(); } HRESULT CNFFMappedStream::QueryInterface( REFIID riid, void**ppvObject ) { return( _pnffstm->QueryInterface( riid, ppvObject )); } ULONG CNFFMappedStream::AddRef() { return( _pnffstm->AddRef() ); } ULONG CNFFMappedStream::Release() { return( _pnffstm->Release() ); } //+---------------------------------------------------------------------------- // // Method: CNFFMappedStream::Open (IMappedStream) // //+---------------------------------------------------------------------------- VOID CNFFMappedStream::Open( IN VOID *powner, OUT LONG *phr ) { nffITrace( "CNFFMappedStream::Open" ); VOID *pv = NULL; HRESULT sc=S_OK; BOOL fUsingLatestStream = FALSE; DfpAssert(!_fLowMem); _pnffstm->Lock( INFINITE ); nffChk( _pnffstm->CheckReverted() ); // If the previous open crashed during a flush, roll forward to the // updated copy. If we're only open for read access, then this will // just set _fUpdateStreamHasLatest so that we'll know to process // reads from that stream. nffChk( RollForwardIfNecessary() ); BeginUsingLatestStream(); fUsingLatestStream = TRUE; // If given a pointer to the owner of this mapped stream, // save it. This could be NULL (i.e., when called from // ReOpen). if( NULL != powner ) _pMappedStreamOwner = powner; // If we haven't already read the stream, read it now. if( NULL == _pbMappedStream ) { BY_HANDLE_FILE_INFORMATION fileinfo; DfpAssert( INVALID_HANDLE_VALUE != _pnffstm->GetFileHandle() ); DfpAssert( 0 == _cbMappedStream ); DfpAssert( 0 == _cbMappedStreamActual); // Get and validate the size of the file if( !GetFileInformationByHandle( _pnffstm->GetFileHandle(), &fileinfo )) { nffErr( EH_Err, LAST_SCODE ); } else if( 0 != fileinfo.nFileSizeHigh || CBMAXPROPSETSTREAM < fileinfo.nFileSizeLow ) { nffErr( EH_Err, STG_E_INVALIDHEADER ); } _cbMappedStream = _cbMappedStreamActual = fileinfo.nFileSizeLow; // Allocate a buffer to hold the Stream. If there isn't sufficient // memory in the system, lock and get the reserved buffer. In the // end, 'pv' points to the appropriate buffer. #if DBG pv = _fSimulateLowMem ? NULL : CoTaskMemAlloc( _cbMappedStreamActual ); #else pv = CoTaskMemAlloc( _cbMappedStreamActual ); #endif if( NULL == pv ) { // could block until previous property call completes pv = g_ReservedMemory.LockMemory(); if( NULL == pv ) nffErr( EH_Err, E_OUTOFMEMORY ); _fLowMem = TRUE; } _pbMappedStream = (BYTE*) pv; // Read in the file. if( 0 != _cbMappedStreamActual ) { ULARGE_INTEGER ulOffset; ulOffset.QuadPart = 0; if( FAILED(_pnffstm->SyncReadAtFile( ulOffset, _pbMappedStream, _cbMappedStreamActual, &_cbMappedStream))) { nffErr( EH_Err, LAST_SCODE ); } // Ensure that we got all the bytes we requested. if( _cbMappedStream != _cbMappedStreamActual ) { propDbg((DEBTRACE_ERROR, "CMappedStreamOnHFile(%08X)::Open bytes-read (%lu) doesn't match bytes-requested (%lu)\n", this, _cbMappedStream, _cbMappedStreamActual )); nffErr( EH_Err, STG_E_INVALIDHEADER ); } } #if BIGENDIAN==1 // Notify our owner that we've read in new data. if( _pMappedStreamOwner != NULL && 0 != _cbMappedStream ) { nffChk( PrOnMappedStreamEvent( _pMappedStreamOwner, _pbMappedStream, _cbMappedStream ) ); } #endif } // if( NULL == _pbMappedStream ) // ---- // Exit // ---- EH_Err: if( fUsingLatestStream ) EndUsingLatestStream(); // If there was an error, free any memory we have. if( FAILED(sc) ) { propDbg((DEB_ERROR, "IMappedStream::CNtfsStream(%08X)::Open exception returns %08X\n", this, *phr)); if (_fLowMem) g_ReservedMemory.UnlockMemory(); else CoTaskMemFree(pv); _pbMappedStream = NULL; _cbMappedStream = _cbMappedStreamActual = 0; _fLowMem = FALSE; } _pnffstm->Unlock(); *phr = sc; return; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::Flush (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::Flush(OUT LONG *phr) { nffITrace( "CNFFMappedStream::Flush" ); HRESULT sc=S_OK; BOOL fUsingLatestStream = FALSE; _pnffstm->Lock( INFINITE );; BeginUsingLatestStream(); fUsingLatestStream = TRUE; nffChk( _pnffstm->CheckReverted() ); if( !IsWriteable() ) nffErr( EH_Err, STG_E_ACCESSDENIED ); // If the IMappedStream is being used, write it out to the // underlying file. if( NULL != _pbMappedStream ) nffChk( WriteMappedStream() ); // Commit the Stream. if( !FlushFileBuffers( _pnffstm->GetFileHandle() )) nffErr( EH_Err, LAST_SCODE ); EndUsingLatestStream(); fUsingLatestStream = FALSE; nffChk( ReplaceOriginalWithUpdate( CREATE_NEW_UPDATE_STREAM )); sc = S_OK; EH_Err: if( fUsingLatestStream ) EndUsingLatestStream(); _pnffstm->Unlock(); *phr = sc; return; } //+------------------------------------------------------------------- // // Member: IMappedStream::Close // // Synopsis: Close the mapped stream by writing out // the mapping buffer and then freeing it. // Errors are ignored, so if the caller wants an // opportunity to recover from an error, they should // call Flush before calling Close. // // Arguments: [LONG*] phr // An HRESULT error code. // // Returns: None. // //-------------------------------------------------------------------- VOID CNFFMappedStream::Close(OUT LONG *phr) { nffITrace( "CNFFMappedStream::Close" ); HRESULT sc=S_OK; _pnffstm->Lock( INFINITE ); // So watch out for multiple closes. sc = _pnffstm->CheckReverted(); // If we are already closed then return immediatly (but don't error) if( STG_E_REVERTED == sc ) { sc = S_OK; goto EH_Err; } // Report any real errors. if( FAILED( sc ) ) nffErr( EH_Err, sc ); // Write the changes. We don't need to Commit them, // they will be implicitely committed when the // Stream is Released. sc = WriteMappedStream(); // Even if we fail the write, we must free the memory. // (PrClosePropertySet deletes everything whether or not // there was an error here, so we must free the memory. // There's no danger of this happenning due to out-of- // disk-space conditions, because the propset code // pre-allocates). CoTaskMemFree( _pbMappedStream ); _pbMappedStream = NULL; // Re-zero the member data. InitMappedStreamMembers(); sc = S_OK; EH_Err: _pnffstm->Unlock(); *phr = sc; return; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::ReOpen (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::ReOpen(IN OUT VOID **ppv, OUT LONG *phr) { nffITrace( "CNFFMappedStream::ReOpen" ); HRESULT sc=S_OK; *ppv = NULL; _pnffstm->Lock( INFINITE );; nffChk( _pnffstm->CheckReverted() ); this->Open(NULL, &sc); nffChk(sc); *ppv = _pbMappedStream; EH_Err: _pnffstm->Unlock(); *phr = sc; return; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::Quiesce (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::Quiesce(VOID) { nffITrace( "CNFFMappedStream::Quiesce" ); // Not necessary for this implemented } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::Map (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::Map(IN BOOLEAN fCreate, OUT VOID **ppv) { nffITrace( "CNFFMappedStream::Map" ); HRESULT sc; _pnffstm->Lock( INFINITE );; nffChk( _pnffstm->CheckReverted() ); DfpAssert(_pbMappedStream != NULL); *ppv = _pbMappedStream; EH_Err: _pnffstm->Unlock(); } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::Unmap (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::Unmap(BOOLEAN fFlush, VOID **ppv) { nffITrace( "CNFFMappedStream::Unmap" ); *ppv = NULL; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::WriteMappedStream (internal support for IMappedStream) // // Returns: S_OK if successful, S_FALSE if there was nothing to write. // //-------------------------------------------------------------------- #define STACK_BYTES 16 HRESULT CNFFMappedStream::WriteMappedStream() { nffITrace( "CNFFMappedStream::WriteMappedStream" ); HRESULT sc = S_OK; ULONG cbWritten; BOOL fOwnerSignaled = FALSE; BOOL fUsingUpdateStream = FALSE; // We can return right away if there's nothing to write. // (_pbMappedStream may be NULL in the error path of our // caller). if (!IsModified() || NULL == _pbMappedStream ) { propDbg((DEB_TRACE, "IMappedStream::CNtfsStream(%08X)::Flush returns with not-dirty\n", this)); return S_FALSE; } // Put the update stream's handle into _pnffstm, so that we write out to it. BeginUsingUpdateStream(); fUsingUpdateStream = TRUE; DfpAssert( INVALID_HANDLE_VALUE != _pnffstm->GetFileHandle() ); #if BIGENDIAN==1 // Notify our owner that we're about to perform a Write. nffChk( PrOnMappedStreamEvent( _powner, _pbMappedStream, _cbMappedStream ) ); fOwnerSignaled = TRUE; #endif // Write out the mapping buffer (to the update stream). ULARGE_INTEGER ulOffset; ulOffset.QuadPart = 0; nffChk( _pnffstm->SyncWriteAtFile( ulOffset, _pbMappedStream, _cbMappedStream, &cbWritten )); if( cbWritten != _cbMappedStream ) { propDbg((DEB_ERROR, "CMappedStreamOnHFile(%08X)::Write bytes-written (%lu) doesn't match bytes-requested (%lu)\n", this, cbWritten, _cbMappedStream )); sc = STG_E_INVALIDHEADER; goto EH_Err; } // If the buffer is shrinking, this is a good time to shrink the file. if (_cbMappedStream < _cbMappedStreamActual) { nffChk( _pnffstm->SetSize( static_cast(_cbMappedStream) ) ); _cbMappedStreamActual = _cbMappedStream; } if( _fStreamRenameSupported ) { // We wrote the data to the update stream. So flag that it now // has the latest data. _fUpdateStreamHasLatest = TRUE; DfpAssert( NULL != _pstmUpdate && INVALID_HANDLE_VALUE != _pstmUpdate->GetFileHandle() ); } // ---- // Exit // ---- EH_Err: #if BIGENDIAN==1 // Notify our owner that we're done with the Write. We do this // whether or not there was an error, because _pbMappedStream is // not modified, and therefore intact even in the error path. if( fOwnerSignaled ) { DfpVerify( PrOnMappedStreamEvent( _powner, _pbMappedStream, _cbMappedStream ) ); } #endif if( fUsingUpdateStream ) EndUsingUpdateStream(); if (sc == S_OK || sc == STG_E_REVERTED) { _fMappedStreamDirty = FALSE; } propDbg(( DbgFlag(sc,DEB_ITRACE), "CNtfsStream(%08X)::Write %s returns hr=%08X\n", this, sc != S_OK ? "exception" : "", sc)); return sc; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::GetSize (IMappedStream) // //-------------------------------------------------------------------- ULONG CNFFMappedStream::GetSize(OUT LONG *phr) { nffITrace( "CNFFMappedStream::GetSize" ); HRESULT sc=S_OK; _pnffstm->Lock( INFINITE );; nffChk( _pnffstm->CheckReverted() ); // If necessary, open the Stream. if( NULL == _pbMappedStream ) { this->Open(NULL, &sc); } if( SUCCEEDED(sc) ) { DfpAssert( NULL != _pbMappedStream ); } // Return the size of the mapped stream. If there was an // Open error, it will be zero, and *phr will be set. EH_Err: _pnffstm->Unlock(); *phr = sc; return _cbMappedStream; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::InitMappedStreamMembers // //-------------------------------------------------------------------- void CNFFMappedStream::InitMappedStreamMembers() { nffITrace( "CNFFMappedStream::InitMappedStreamMembers" ); _pbMappedStream = NULL; _cbMappedStream = 0; _cbMappedStreamActual = 0; _pMappedStreamOwner = NULL; _fLowMem = FALSE; _fMappedStreamDirty = FALSE; _fCheckedForRollForward = FALSE; _fStreamRenameSupported = FALSE; _cUpdateStreamInUse = _cLatestStreamInUse = 0; } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::SetSize (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::SetSize(IN ULONG cb, IN BOOLEAN fPersistent, IN OUT VOID **ppv, OUT LONG *phr) { nffITrace( "CNFFMappedStream::SetSize" ); BYTE *pv; HRESULT &sc = *phr; BOOL fUsingUpdateStream = FALSE, fUsingLatestStream = FALSE; DfpAssert(cb != 0); sc = S_OK; _pnffstm->Lock( INFINITE );; nffChk( _pnffstm->CheckReverted() ); if( CBMAXPROPSETSTREAM < cb ) nffErr( EH_Err, STG_E_MEDIUMFULL ); if( fPersistent ) { nffChk( CreateUpdateStreamIfNecessary() ); BeginUsingUpdateStream(); fUsingUpdateStream = TRUE; } else { BeginUsingLatestStream(); fUsingLatestStream = TRUE; } // if we are growing the data, we should grow the file if( fPersistent && cb > _cbMappedStreamActual ) { nffChk( _pnffstm->SetFileSize( CULargeInteger(cb) ) ); _cbMappedStreamActual = cb; } // We only get here if we either (1) didn't want to grow the // underlying stream, or (2) we successfully grew the underlying stream. // Re-size the buffer to the size specified in cb. if( _fLowMem ) { // If we want to grow the buffer In low-memory conditions, // no realloc is necessary, because // _pbMappedStream is already large enough for the largest // property set. if( NULL != ppv ) *ppv = _pbMappedStream; } else if ( cb != _cbMappedStream ) { // We must re-alloc the buffer. #if DBG pv = _fSimulateLowMem ? NULL : (PBYTE) CoTaskMemRealloc( _pbMappedStream, cb ); #else pv = (PBYTE)CoTaskMemRealloc( _pbMappedStream, cb ); #endif if ((pv == NULL) ) { // allocation failed: we need to try using a backup mechanism for // more memory. // copy the data to the global reserved chunk... we will wait until // someone else has released it. it will be released on the way out // of the property code. pv = g_ReservedMemory.LockMemory(); if( NULL == pv ) nffErr( EH_Err, E_OUTOFMEMORY ); _fLowMem = TRUE; if( NULL != _pbMappedStream ) { memcpy( pv, _pbMappedStream, _cbMappedStream ); } CoTaskMemFree( _pbMappedStream ); } _pbMappedStream = pv; if( NULL != ppv ) *ppv = pv; } _cbMappedStream = cb; // ---- // Exit // ---- EH_Err: if( fUsingUpdateStream ) { DfpAssert( !fUsingLatestStream ); EndUsingUpdateStream(); } else if( fUsingLatestStream ) { EndUsingLatestStream(); } _pnffstm->Unlock(); if( FAILED(*phr) ) { propDbg((DbgFlag(*phr,DEB_ITRACE), "IMappedStream::CNtfsStream(%08X)::SetSize %s returns hr=%08X\n", this, *phr != S_OK ? "exception" : "", *phr)); } } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::Lock (IMappedStream) // //-------------------------------------------------------------------- NTSTATUS CNFFMappedStream::Lock(IN BOOLEAN fExclusive) { // Don't trace at this level. The noice is too great! //nffXTrace( "CNFFMappedStream::Lock"); UNREFERENCED_PARM(fExclusive); _pnffstm->Lock( INFINITE ); return(STATUS_SUCCESS); } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::Unlock (IMappedStream) // //-------------------------------------------------------------------- // Should we unlock even if there's an error? NTSTATUS CNFFMappedStream::Unlock(VOID) { // Don't trace at this level. The noice is too great! //nffXTrace( "CNFFMappedStream::Unlock"); // if at the end of the properties set/get call we have the low // memory region locked, we flush to disk. HRESULT sc = S_OK; if (_fLowMem) { Flush(&sc); g_ReservedMemory.UnlockMemory(); _pbMappedStream = NULL; _cbMappedStream = _cbMappedStreamActual = 0; _fLowMem = FALSE; propDbg((DEB_PROP_INFO, "CMappedStreamOnHFile(%08X):Unlock low-mem returns NTSTATUS=%08X\n", this, sc)); } _pnffstm->Unlock(); return(sc); } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::QueryTimeStamps (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::QueryTimeStamps(OUT STATPROPSETSTG *pspss, BOOLEAN fNonSimple) const { nffITrace( "CNFFMappedStream::QueryTimeStamps" ); } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::QueryModifyTime (IMappedStream) // //-------------------------------------------------------------------- BOOLEAN CNFFMappedStream::QueryModifyTime(OUT LONGLONG *pll) const { nffITrace( "CNFFMappedStream::QueryModifyTime" ); return(FALSE); } //+------------------------------------------------------------------- // // Member: Unused methods by this IMappedStream implementation: // QuerySecurity, IsWritable, GetHandle // //-------------------------------------------------------------------- BOOLEAN CNFFMappedStream::QuerySecurity(OUT ULONG *pul) const { nffITrace( "CNFFMappedStream::QuerySecurity" ); return(FALSE); } BOOLEAN CNFFMappedStream::IsWriteable() const { nffITrace( "CNFFMappedStream::IsWriteable" ); return( (BOOLEAN) _pnffstm->IsWriteable() ); } HANDLE CNFFMappedStream::GetHandle(VOID) const { nffITrace( "CNFFMappedStream::GetHandle" ); return(INVALID_HANDLE_VALUE); } //+------------------------------------------------------------------- // // Member: CNFFMappedStream::SetModified/IsModified (IMappedStream) // //-------------------------------------------------------------------- VOID CNFFMappedStream::SetModified(OUT LONG *phr) { nffITrace( "CNFFMappedStream::SetModified" ); HRESULT &sc = *phr; _pnffstm->Lock( INFINITE );; nffChk( _pnffstm->CheckReverted() ); nffChk( CreateUpdateStreamIfNecessary() ); _fMappedStreamDirty = TRUE; sc = S_OK; EH_Err: _pnffstm->Unlock(); } BOOLEAN CNFFMappedStream::IsModified(VOID) const { nffITrace( "CNFFMappedStream::IsModified" ); return _fMappedStreamDirty; } //+------------------------------------------------------------------- // // Member: ImappedStream::IsNtMappedStream/SetChangePending // // Synopsis: Debug routines. // //-------------------------------------------------------------------- #if DBGPROP BOOLEAN CNFFMappedStream::IsNtMappedStream(VOID) const { nffITrace( "CNFFMappedStream::IsNtMappedStream" ); return(TRUE); } #endif #if DBGPROP BOOLEAN CNFFMappedStream::SetChangePending(BOOLEAN f) { nffITrace( "CNFFMappedStream::SetChangePending" ); return(f); } #endif // // CNFFMappedStream::BeginUsingLatestStream/EndUsingLatestStream // // These routines are similar to Begin/EndUsing*Update*Stream, // except that they honor the _fUpdateStreamHasLatest flag. // Thus, if the original stream has the latest data, then this // routine will do nothing. // void CNFFMappedStream::BeginUsingLatestStream() { if( _fUpdateStreamHasLatest ) { if( 0 == _cLatestStreamInUse++ ) BeginUsingUpdateStream(); } } void CNFFMappedStream::EndUsingLatestStream() { if( 0 != _cLatestStreamInUse ) { EndUsingUpdateStream(); _cLatestStreamInUse--; } DfpAssert( static_cast(-1) != _cLatestStreamInUse ); } // // CNFFMappedStream::BeginUsingUpdateStream // // This is called when the update stream is to be used. It // does nothing, though, if we don't have an update stream // (e.g. if the file system doesn't support stream renames). // We increment the _cUpdateStreamInUse count, so that we can determine in // EndUsingUpdateStream when to swap the handles back. void CNFFMappedStream::BeginUsingUpdateStream() { if( NULL != _pstmUpdate && INVALID_HANDLE_VALUE != _pstmUpdate->GetFileHandle() && 0 == _cUpdateStreamInUse++ ) { HANDLE hTemp = _pnffstm->_hFile; _pnffstm->_hFile = _pstmUpdate->_hFile; _pstmUpdate->_hFile = hTemp; } } // // CNFFMappedStream::EndUsingUpdateStream // // Decrement the _cUpdateStreamInUse count. And, if that puts // the count down to zero, swap the handles back. // void CNFFMappedStream::EndUsingUpdateStream() { if( 0 != _cUpdateStreamInUse && 0 == --_cUpdateStreamInUse ) { DfpAssert( NULL != _pstmUpdate && INVALID_HANDLE_VALUE != _pstmUpdate->GetFileHandle() ); HANDLE hTemp = _pnffstm->_hFile; _pnffstm->_hFile = _pstmUpdate->_hFile; _pstmUpdate->_hFile = hTemp; } DfpAssert( static_cast(-1) != _cUpdateStreamInUse ); } inline HRESULT CNFFMappedStream::CreateUpdateStreamIfNecessary() { if( _fStreamRenameSupported && ( NULL == _pstmUpdate || INVALID_HANDLE_VALUE == _pstmUpdate->GetFileHandle() ) ) { return( OpenUpdateStream( TRUE )); } else return( S_OK ); } //+---------------------------------------------------------------------------- // // Method: CNFFMAppedStream::RollForwardIfNecessary (non-interface method) // // In the open path, we look to see if there's a leftover update stream for // a previous open of the stream, which must have crashed during a write. // If we're opening for write, we fix the problem. Otherwise, we // just remember that we'll have to read out of the update stream. // // See the CNtfsStreamForPropStg class declaration for a description of // this transactioning. // //+---------------------------------------------------------------------------- HRESULT CNFFMappedStream::RollForwardIfNecessary() { HRESULT hr = S_OK; BY_HANDLE_FILE_INFORMATION ByHandleFileInformation; // If we've already checked for this, then we needn't check again. if( _fCheckedForRollForward ) goto Exit; // We also needn't do anything if we're creating, since that overwrites // any existing data anyway. if( !(STGM_CREATE & _pnffstm->_grfMode) ) { // Get the size of the current stream. if( !GetFileInformationByHandle( _pnffstm->_hFile, &ByHandleFileInformation )) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Exit; } // If the size is zero, then there might be an update // stream with the real data. if( 0 == ByHandleFileInformation.nFileSizeLow && 0 == ByHandleFileInformation.nFileSizeHigh ) { // See if there's an update stream hr = OpenUpdateStream( FALSE ); if( SUCCEEDED(hr) ) { // We have a zero-length main stream and an update stream, // so there must have been a crash in ReplaceOriginalWithUpdate, // after the truncation but before the NtSetInformationFile // (FileRenameInformation). // If this is a writable stream, rename the update stream // over the zero-length one. Otherwise, we'll just read from // the update stream. _fUpdateStreamHasLatest = TRUE; if( IsWriteable() ) { hr = ReplaceOriginalWithUpdate( DONT_CREATE_NEW_UPDATE_STREAM ); if( FAILED(hr) ) goto Exit; } } else if( STG_E_FILENOTFOUND == hr ) // Ignore the case where there's no update stream. This happens // when the stream is created without STGM_CREATE set. hr = S_OK; else goto Exit; } } // if( !(STGM_CREATE & _grfMode) ) // We don't need to check for this again. _fCheckedForRollForward = TRUE; Exit: return( hr ); } // CNtfsStreamForPropStg::RollForwardIfNecessary //+---------------------------------------------------------------------------- // // Method: CNtfsStreamForPropStg::ReplaceOriginalWithUpdate (internal method) // // This method renames the update stream over the original stream, then // creates a new update stream (with no data but properly sized). If, however, // the update stream doesn't have the latest data anyway, then this routine // noops. // // See the CNtfsStreamForPropStg class declaration for a description of // this transactioning. // //+---------------------------------------------------------------------------- HRESULT CNFFMappedStream::ReplaceOriginalWithUpdate( enumCREATE_NEW_UPDATE_STREAM CreateNewUpdateStream ) { HRESULT hr = S_OK; NTSTATUS status; FILE_END_OF_FILE_INFORMATION file_end_of_file_information; IO_STATUS_BLOCK io_status_block; // If the original stream already has the latest data, then // there's nothing to do. if( !_fUpdateStreamHasLatest ) goto Exit; DfpAssert( NULL != _pstmUpdate ); DfpAssert( 0 == _cUpdateStreamInUse ); // We must write the update data all the way to disk. hr = _pstmUpdate->Flush(); if( FAILED(hr) ) goto Exit; // Truncate the original stream so that it can be overwritten. // After this atomic operation, the update stream is considered // *the* stream (which is why we had to flush it above). file_end_of_file_information.EndOfFile = CLargeInteger(0); status = NtSetInformationFile( _pnffstm->_hFile, &io_status_block, &file_end_of_file_information, sizeof(file_end_of_file_information), FileEndOfFileInformation ); if( !NT_SUCCESS(status) ) { hr = NtStatusToScode(status); goto Exit; } NtClose( _pnffstm->_hFile ); _pnffstm->_hFile = INVALID_HANDLE_VALUE; // Rename the updated stream over the original (now empty) stream. // This is atomic. hr = _pstmUpdate->Rename( _pnffstm->_pwcsName, TRUE ); if( FAILED(hr) ) { // Go into the reverted state NtClose( _pstmUpdate->_hFile ); _pstmUpdate->_hFile = INVALID_HANDLE_VALUE; goto Exit; } // Make the updated stream the master _pnffstm->_hFile = _pstmUpdate->_hFile; _pstmUpdate->_hFile = INVALID_HANDLE_VALUE; _fUpdateStreamHasLatest = FALSE; // Optionally create a new update stream. if( CREATE_NEW_UPDATE_STREAM == CreateNewUpdateStream ) { // return an error if we cannot create the update stream hr = OpenUpdateStream( TRUE ); if( FAILED(hr) ) goto Exit; } else DfpAssert( DONT_CREATE_NEW_UPDATE_STREAM == CreateNewUpdateStream ); Exit: return( hr ); } // CNFFMappedStream::ReplaceOriginalWithUpdate() //+---------------------------------------------------------------------------- // // Method: CNFFMappedStream::OpenUpdateStream // // This method opens the update stream, to which stream updates are written. // This is necessary to provide a minimal level of transactioning. // // See the CNtfsStreamForPropStg class declaration for a description of // this transactioning. // //+---------------------------------------------------------------------------- HRESULT CNFFMappedStream::OpenUpdateStream( BOOL fCreate ) { HRESULT hr = S_OK; HANDLE hStream = INVALID_HANDLE_VALUE; CNtfsUpdateStreamName UpdateStreamName = _pnffstm->_pwcsName; // Open the NTFS stream hr = _pnffstm->_pnffstg->GetStreamHandle( &hStream, UpdateStreamName, _pnffstm->_grfMode | (fCreate ? STGM_CREATE : 0), fCreate ); if( FAILED(hr) ) goto Exit; // If necessary, instantiate a CNtfsUpdateStreamForPropStg if( NULL == _pstmUpdate ) { _pstmUpdate = new CNtfsUpdateStreamForPropStg( _pnffstm->_pnffstg, _pnffstm->_pBlockingLock ); if( NULL == _pstmUpdate ) { hr = E_OUTOFMEMORY; goto Exit; } } // Put the NTFS stream handle into the CNtfsUpdateStreamForPropStg hr = _pnffstm->_pnffstg->InitCNtfsStream( _pstmUpdate, hStream, _pnffstm->_grfMode | (fCreate ? STGM_CREATE : 0), UpdateStreamName ); hStream = INVALID_HANDLE_VALUE; // ownership of the handle has changed if( FAILED(hr) ) goto Exit; // If we're creating the update stream, size it to match the size // of the original stream. if( fCreate ) { ULONG ulSize = GetSize(&hr); if( FAILED(hr) ) goto Exit; hr = _pstmUpdate->SetSize( CULargeInteger(ulSize) ); if( FAILED(hr) ) goto Exit; } Exit: if( INVALID_HANDLE_VALUE != hStream ) NtClose( hStream ); if( FAILED(hr) ) { // If we were attempting a create but failed, then ensure the // update stream is gone. if( NULL != _pstmUpdate && fCreate ) _pstmUpdate->Delete(); DfpVerify( 0 == RELEASE_INTERFACE(_pstmUpdate) ); } return( hr ); } // CNFFMappedStream::OpenUpdateStream() //+---------------------------------------------------------------------------- // // Method: CNFFMappedStream::Init (override from CNtfsStream) // // This method initializes the CNtfsStream, and checks the file system to // determine if we can support the update stream (for robustness). The // necessary file system support is stream renaming, which we use to provide // a minimal level of transactioning. // // See the CNtfsStreamForPropStg class declaration for a description of // this transactioning. // //+---------------------------------------------------------------------------- HRESULT CNFFMappedStream::Init( HANDLE hFile ) { HRESULT hr = S_OK; NTSTATUS status = STATUS_SUCCESS; FILE_FS_ATTRIBUTE_INFORMATION file_fs_attribute_information; IO_STATUS_BLOCK io_status_block; // Check to see if we'll be able to support stream renaming. if( NULL != _pnffstm->_pnffstg ) { // We can at least see an IStorage for the file, so stream renaming // could potentially work, but we also need to query the file system // attributes to see if it actually supports it. status = NtQueryVolumeInformationFile( hFile, &io_status_block, &file_fs_attribute_information, sizeof(file_fs_attribute_information), FileFsAttributeInformation ); // We should always get a buffer-overflow error here, because we don't // provide enough buffer for the file system name, but that's OK because // we don't need it (status_buffer_overflow is just a warning, so the rest // of the data is good). if( !NT_SUCCESS(status) && STATUS_BUFFER_OVERFLOW != status) { hr = NtStatusToScode(status); goto Exit; } // There's no attribute bit which says "supports stream rename". The best // we can do is look for another NTFS5 feature and make an inferrence. if( FILE_SUPPORTS_OBJECT_IDS & file_fs_attribute_information.FileSystemAttributes ) _fStreamRenameSupported = TRUE; } Exit: return( hr ); } // CNFFMappedStream::Init() HRESULT CNFFMappedStream::ShutDown() { // mikehill step HRESULT hr = S_OK; _pnffstm->Lock( INFINITE ); // Close the mapped stream Close( &hr ); if( FAILED(hr) && STG_E_REVERTED != hr ) propDbg(( DEB_ERROR, "CNFFMappedStream(0x%x)::ShutDown failed call to CNtfsStream::Close (%08x)\n", this, hr )); // Overwrite the original stream with the update (if necessary), // but don't bother to create a new update stream afterwards. if( NULL != _pstmUpdate ) { hr = ReplaceOriginalWithUpdate( DONT_CREATE_NEW_UPDATE_STREAM ); if( FAILED(hr) ) propDbg(( DEB_ERROR, "CNFFMappedStream(0x%x)::ShutDown failed call to ReplaceOriginalWithUpdate (%08x)\n", this, hr )); } // Release the update stream. if( NULL != _pstmUpdate ) DfpVerify( 0 == RELEASE_INTERFACE(_pstmUpdate) ); propDbg(( DbgFlag(hr,DEB_ITRACE), "CNFFMappedStream(0x%x)::ShutDown() returns %08x\n", this, hr )); _pnffstm->Unlock(); return( hr ); } // CNFFMappedStream::ShutDown void CNFFMappedStream::Read( void *pv, ULONG ulOffset, ULONG *pcbCopy ) { if( *pcbCopy > _cbMappedStream ) *pcbCopy = 0; else if( *pcbCopy > _cbMappedStream - ulOffset ) *pcbCopy = _cbMappedStream - ulOffset; memcpy( pv, &_pbMappedStream[ ulOffset ], *pcbCopy ); return; } void CNFFMappedStream::Write( const void *pv, ULONG ulOffset, ULONG *pcbCopy ) { if( *pcbCopy > _cbMappedStream ) *pcbCopy = 0; else if( *pcbCopy + ulOffset > _cbMappedStream ) *pcbCopy = _cbMappedStream - ulOffset; memcpy( &_pbMappedStream[ulOffset], pv, *pcbCopy ); return; } //+---------------------------------------------------------------------------- // // Method: IStorageTest::UseNTFS4Streams (DBG only) // // This method can be used to disable the stream-renaming necessary for // robust property sets. This emulates an NTFS4 volume. // //+---------------------------------------------------------------------------- #if DBG HRESULT STDMETHODCALLTYPE CNFFMappedStream::UseNTFS4Streams( BOOL fUseNTFS4Streams ) { HRESULT hr = S_OK; if( _fUpdateStreamHasLatest ) { hr = STG_E_INVALIDPARAMETER; propDbg(( DEB_ERROR, "CNtfsStreamForPropStg(0x%x)::UseNTFS4Streams(%s)" "was called while an update stream was already in use\n", this, fUseNTFS4Streams ? "True" : "False" )); } else if( fUseNTFS4Streams ) { DfpVerify( 0 == RELEASE_INTERFACE(_pstmUpdate) ); _fStreamRenameSupported = FALSE; } else { // Shutting NTFS4streams off isn't implemented hr = E_NOTIMPL; } return( hr ); } // CNFFMAppedStream::UseNTFS4Streams #endif // #if DBG #if DBG HRESULT CNFFMappedStream::GetFormatVersion( WORD *pw ) { return( E_NOTIMPL ); } #endif // #if DBG #if DBG HRESULT CNFFMappedStream::SimulateLowMemory( BOOL fSimulate ) { _fSimulateLowMem = fSimulate; return( S_OK ); } #endif // #if DBG #if DBG LONG CNFFMappedStream::GetLockCount() { return( _pnffstm->GetLockCount() ); } #endif // #if DBG #if DBG HRESULT CNFFMappedStream::IsDirty() { return( _fMappedStreamDirty ? S_OK : S_FALSE ); } #endif // #if DBG //+---------------------------------------------------------------------------- // // Method: CNtfsUpdateStreamForPropStg::ShutDown (overrides CNtfsStream) // // Override so that we can remove this stream from the linked-list, but // not do a flush. See the CNtfsStreamForPropStg class declaration for // more information on this class. // //+---------------------------------------------------------------------------- HRESULT CNtfsUpdateStreamForPropStg::ShutDown() { RemoveSelfFromList(); return( S_OK ); } // CNtfsUpdateStreamForPropStg::ShutDown