windows-nt/Source/XPSP1/NT/com/ole32/stg/props/nffmstm.cxx
2020-09-26 16:20:57 +08:00

1469 lines
38 KiB
C++

//+============================================================================
//
// File: nffmstm.cxx
//
// This file provides the NFF (NTFS Flat File) IMappedStream implementation.
//
// History:
// 5/6/98 MikeHill
// - Misc dbg cleanup.
//
//+============================================================================
#include <pch.cxx>
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<CULargeInteger>(_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<USHORT>(-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<USHORT>(-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