You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3274 lines
88 KiB
3274 lines
88 KiB
|
|
|
|
//+============================================================================
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1994 - 1998.
|
|
//
|
|
// File: hntfsstg.cxx
|
|
//
|
|
// This file provides the NFF (NTFS Flat File) IStorage implementation.
|
|
//
|
|
// History:
|
|
// 5/6/98 MikeHill
|
|
// - Use CoTaskMem rather than new/delete.
|
|
// - Split the Init method into two methods, one which is
|
|
// file name based and the other which is handle based.
|
|
// 5/18/98 MikeHill
|
|
// - Use the cleaned up CPropertySetStorage & CPropertyBagEx
|
|
// constructors.
|
|
//
|
|
//+============================================================================
|
|
|
|
|
|
#include <pch.cxx>
|
|
#include "expparam.hxx"
|
|
|
|
|
|
#define UNSUPPORTED_STGM_BITS ( STGM_CONVERT | \
|
|
STGM_TRANSACTED | \
|
|
STGM_PRIORITY | \
|
|
STGM_SIMPLE | \
|
|
STGM_DELETEONRELEASE )
|
|
|
|
|
|
WCHAR GetDriveLetter (WCHAR const *pwcsName);
|
|
BOOL IsDataStream( const PFILE_STREAM_INFORMATION pFileStreamInformation );
|
|
|
|
|
|
//-----------------------------------------------------------
|
|
//
|
|
// NFFOpen();
|
|
//
|
|
// Routine for the rest of Storage to use to open NFF files
|
|
// without knowing a lot of details.
|
|
//
|
|
//-----------------------------------------------------------
|
|
|
|
HRESULT
|
|
NFFOpen(const WCHAR *pwcsName,
|
|
DWORD grfMode,
|
|
DWORD dwFlags,
|
|
BOOL fCreateAPI,
|
|
REFIID riid,
|
|
void **ppv)
|
|
{
|
|
CNtfsStorage* pnffstg=NULL;
|
|
IUnknown *punk=NULL;
|
|
HRESULT sc=S_OK;
|
|
|
|
nffDebug(( DEB_TRACE | DEB_INFO | DEB_OPENS,
|
|
"NFFOpen(\"%ws\", %x, %x, &iid=%x, %x)\n",
|
|
pwcsName, grfMode, fCreateAPI, &riid, ppv));
|
|
|
|
// Parameter validation
|
|
|
|
if( 0 != ( grfMode & UNSUPPORTED_STGM_BITS ) )
|
|
nffErr( EH_Err, STG_E_INVALIDFLAG );
|
|
|
|
// We don't support Write only storage (yet)
|
|
|
|
if( STGM_WRITE == (grfMode & STGM_RDWR_MASK) )
|
|
nffErr( EH_Err, STG_E_INVALIDFLAG );
|
|
|
|
// Instantiate and initialize a CNtfsStorage for this file.
|
|
|
|
nffMem( pnffstg = new CNtfsStorage( grfMode ));
|
|
|
|
nffChk( pnffstg->InitFromName( pwcsName, fCreateAPI, dwFlags ) );
|
|
|
|
nffChk( pnffstg->QueryInterface( riid, (void**)&punk ) );
|
|
|
|
*ppv = punk;
|
|
punk = NULL;
|
|
|
|
EH_Err:
|
|
RELEASE_INTERFACE( pnffstg );
|
|
RELEASE_INTERFACE( punk );
|
|
|
|
// Compatibilty with Docfile: Multiple opens with incompatible
|
|
// STGM_ modes returns LOCK vio not SHARE vio. We use file SHARE'ing
|
|
// Docfile used file LOCK'ing.
|
|
|
|
if(STG_E_SHAREVIOLATION == sc)
|
|
sc = STG_E_LOCKVIOLATION;
|
|
|
|
nffDebug(( DEB_TRACE, "NFFOpen() sc=%x\n", sc ));
|
|
return(sc);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: NFFOpenOnHandle
|
|
//
|
|
// Create or open an NFF IStorage (or QI-able interface) on a given
|
|
// handle.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
NFFOpenOnHandle( BOOL fCreateAPI,
|
|
DWORD grfMode,
|
|
DWORD stgfmt,
|
|
HANDLE* phStream,
|
|
REFIID riid,
|
|
void ** ppv)
|
|
{
|
|
HRESULT sc=S_OK;
|
|
CNtfsStorage *pnffstg=NULL;
|
|
IUnknown *punk=NULL;
|
|
|
|
nffDebug(( DEB_TRACE | DEB_INFO | DEB_OPENS,
|
|
"NFFOpenOnHandle(%x, %x, %x, %x, &iid=%x, %x)\n",
|
|
fCreateAPI, grfMode, stgfmt, *phStream, &riid, ppv));
|
|
|
|
// Parameter validation
|
|
|
|
if( 0 != ( grfMode & UNSUPPORTED_STGM_BITS ) )
|
|
nffErr( EH_Err, STG_E_INVALIDFLAG );
|
|
|
|
// We don't currently support create here.
|
|
|
|
if( fCreateAPI )
|
|
nffErr( EH_Err, STG_E_INVALIDPARAMETER );
|
|
|
|
// Instantiate and initialize a CNtfsStorage on this handle
|
|
|
|
nffMem( pnffstg = new CNtfsStorage( grfMode ));
|
|
|
|
nffChk( pnffstg->InitFromMainStreamHandle( phStream,
|
|
NULL,
|
|
fCreateAPI,
|
|
NFFOPEN_NORMAL,
|
|
stgfmt ) );
|
|
|
|
nffAssert( INVALID_HANDLE_VALUE == *phStream );
|
|
|
|
nffChk( pnffstg->QueryInterface( riid, (void**)&punk ) );
|
|
|
|
*ppv = punk;
|
|
punk = NULL;
|
|
|
|
EH_Err:
|
|
RELEASE_INTERFACE(pnffstg);
|
|
RELEASE_INTERFACE(punk);
|
|
|
|
return( sc );
|
|
|
|
} // OpenNFFOnHandle
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IUnknown::QueryInterface
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::QueryInterface(
|
|
REFIID riid,
|
|
void ** ppvObject
|
|
)
|
|
{
|
|
nffXTrace( "CNtfsStorage::QueryInterface" );
|
|
|
|
HRESULT sc = S_OK;
|
|
|
|
// Parameter validation
|
|
|
|
NFF_VALIDATE( QueryInterface( riid, ppvObject ) );
|
|
|
|
// Check for the known interfaces
|
|
|
|
if( IID_IStorage == riid )
|
|
{
|
|
nffDebug(( DEB_ERROR, "STGFMT_FILE IID_IStorage is not supported\n" ));
|
|
return E_NOINTERFACE;
|
|
}
|
|
else if( IID_IPropertySetStorage == riid || IID_IUnknown == riid )
|
|
{
|
|
*ppvObject = static_cast<IPropertySetStorage*>(this);
|
|
AddRef();
|
|
}
|
|
else if( IID_IBlockingLock == riid )
|
|
{
|
|
*ppvObject = static_cast<IBlockingLock*>(this);
|
|
AddRef();
|
|
}
|
|
else if( IID_ITimeAndNoticeControl == riid )
|
|
{
|
|
*ppvObject = static_cast<ITimeAndNoticeControl*>(this);
|
|
AddRef();
|
|
}
|
|
else if( IID_IPropertyBagEx == riid )
|
|
{
|
|
*ppvObject = static_cast<IPropertyBagEx*>(&_PropertyBagEx);
|
|
AddRef();
|
|
}
|
|
else if( IID_IPropertyBag == riid )
|
|
{
|
|
*ppvObject = static_cast<IPropertyBag*>(&_PropertyBagEx);
|
|
AddRef();
|
|
}
|
|
#if DBG
|
|
else if( IID_IStorageTest == riid )
|
|
{
|
|
*ppvObject = static_cast<IStorageTest*>(this);
|
|
AddRef();
|
|
}
|
|
#endif // #if DBG
|
|
else
|
|
return E_NOINTERFACE ;
|
|
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IUnknown::AddRef
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
ULONG STDMETHODCALLTYPE
|
|
CNtfsStorage::AddRef(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
lRet = InterlockedIncrement( &_cReferences );
|
|
|
|
nffDebug(( DEB_REFCOUNT, "CNtfsStorage::AddRef(this==%x) == %d\n",
|
|
this, lRet));
|
|
|
|
return( lRet );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IUnknown::Release
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
ULONG STDMETHODCALLTYPE
|
|
CNtfsStorage::Release(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
lRet = InterlockedDecrement( &_cReferences );
|
|
|
|
if( 0 == lRet )
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
nffDebug((DEB_REFCOUNT, "CNtfsStorage::Release(this=%x) == %d\n",
|
|
this, lRet));
|
|
|
|
return( lRet );
|
|
|
|
}
|
|
|
|
|
|
#ifdef OLE2ANSI
|
|
#error CNtfsStorage requires OLECHAR to be UNICODE
|
|
#endif
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::CreateStream
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::CreateStream(
|
|
const OLECHAR* pwcsName,
|
|
DWORD grfMode,
|
|
DWORD res1,
|
|
DWORD res2,
|
|
IStream** ppstm)
|
|
{
|
|
HRESULT sc=S_OK;
|
|
CNtfsStream *pstm = NULL;
|
|
CNtfsStream *pstmPrevious = NULL;
|
|
|
|
//
|
|
// Prop code passed streams names with DOCF_ UPDR_ prefix and are too long.
|
|
// MikeHill and BChapman agree that the docfile in a stream code should
|
|
// be moved into this object's (currenly unimplemented) CreateStorage.
|
|
// So for the moment since this IStorage is not exposed, we will remove
|
|
// the parameter validation.
|
|
//
|
|
// NFF_VALIDATE( CreateStream( pwcsName, grfMode, res1, res2, ppstm ) );
|
|
|
|
Lock( INFINITE );
|
|
|
|
// Ensure we're not in the reverted state
|
|
|
|
nffChk( CheckReverted() );
|
|
|
|
nffDebug(( DEB_INFO | DEB_OPENS | DEB_TRACE,
|
|
"CreateStream(\"%ws\", %x)\n", pwcsName, grfMode));
|
|
|
|
// We don't support convert
|
|
|
|
if( STGM_CONVERT & grfMode )
|
|
nffErr( EH_Err, STG_E_INVALIDFLAG );
|
|
|
|
// Is this tream already opened?
|
|
|
|
if( FindAlreadyOpenStream( pwcsName, &pstmPrevious ) )
|
|
{
|
|
// If the stream is already open then return Access Denied because
|
|
// streams are always opened Exclusive. But if we are CREATE'ing
|
|
// then revert the old one and make a new one.
|
|
|
|
if( 0 == (STGM_CREATE & grfMode) )
|
|
{
|
|
nffErr( EH_Err, STG_E_ACCESSDENIED );
|
|
}
|
|
else
|
|
{
|
|
pstmPrevious->ShutDown();
|
|
pstmPrevious->Release(); // FindAOS() Addref'ed, so release here
|
|
pstmPrevious = NULL;
|
|
}
|
|
}
|
|
|
|
// The stream isn't already open, or we blew it away.
|
|
// Instantiate a new one.
|
|
|
|
nffChk( NewCNtfsStream( pwcsName, grfMode, TRUE, &pstm ));
|
|
|
|
// ------------------
|
|
// Set Out Parameters
|
|
// ------------------
|
|
|
|
*ppstm = static_cast<IStream*>(pstm);
|
|
pstm = NULL;
|
|
|
|
EH_Err:
|
|
|
|
if( NULL != pstm )
|
|
pstm->Release();
|
|
|
|
if( NULL != pstmPrevious )
|
|
pstmPrevious->Release();
|
|
|
|
Unlock();
|
|
|
|
nffDebug(( DEB_TRACE, "CreateStream() sc=%x\n", sc ));
|
|
|
|
return( sc );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::OpenStream
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::OpenStream(
|
|
const OLECHAR* pwcsName,
|
|
void* res1,
|
|
DWORD grfMode,
|
|
DWORD res2,
|
|
IStream** ppstm)
|
|
{
|
|
HRESULT sc=S_OK;
|
|
CNtfsStream *pstm = NULL;
|
|
|
|
//
|
|
// Prop code passed streams names with DOCF_ UPDR_ prefix and are too long.
|
|
// MikeHill and BChapman agree that the docfile in a stream code should
|
|
// be moved into this object's (currenly unimplemented) OpenStorage.
|
|
// So for the moment since this IStorage is not exposed, we will remove
|
|
// the parameter validation.
|
|
//
|
|
// NFF_VALIDATE( OpenStream( pwcsName, res1, grfMode, res2, ppstm ) );
|
|
|
|
Lock( INFINITE );
|
|
|
|
nffChk( CheckReverted() );
|
|
|
|
nffDebug(( DEB_INFO | DEB_OPENS | DEB_TRACE,
|
|
"OpenStream(\"%ws\", grf=0x%x);\n", pwcsName, grfMode ));
|
|
|
|
// Is this stream already open? If so => error.
|
|
|
|
if( FindAlreadyOpenStream( pwcsName, &pstm ) )
|
|
nffErr( EH_Err, STG_E_ACCESSDENIED );
|
|
|
|
// Otherwise, open the stream.
|
|
|
|
nffChk( NewCNtfsStream( pwcsName, grfMode, FALSE, &pstm ));
|
|
|
|
*ppstm = static_cast<IStream*>(pstm);
|
|
pstm = NULL;
|
|
|
|
EH_Err:
|
|
|
|
if( NULL != pstm )
|
|
pstm->Release();
|
|
|
|
Unlock();
|
|
|
|
nffDebug(( DEB_TRACE, "OpenStream() sc=%x\n", sc ));
|
|
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::CreateStorage
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::CreateStorage(
|
|
const OLECHAR* pwcsName,
|
|
DWORD grfMode,
|
|
DWORD reserved1,
|
|
DWORD reserved2,
|
|
IStorage** ppstg)
|
|
{
|
|
nffXTrace( "CNtfsStorage::CreateStorage" );
|
|
// Not supported
|
|
return( E_NOTIMPL );
|
|
} // CNtfsStorage::CreateStorage
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::OpenStorage
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT CNtfsStorage::OpenStorage(
|
|
const OLECHAR* pwcsName,
|
|
IStorage* pstgPriority,
|
|
DWORD grfMode,
|
|
SNB snbExclude,
|
|
DWORD reserved,
|
|
IStorage** ppstg)
|
|
{
|
|
nffXTrace( "CNtfsStorage::OpenStorage" );
|
|
// Not supported
|
|
return( E_NOTIMPL );
|
|
} // CNtfsStorage::OpenStorage
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::CopyTo
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::CopyTo(
|
|
DWORD ciidExclude,
|
|
const IID* rgiidExclude,
|
|
SNB snbExclude,
|
|
IStorage* pstgDest)
|
|
{
|
|
nffXTrace( "CNtfsStorage::CopyTo" );
|
|
return E_NOTIMPL;
|
|
}
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::MoveElementTo
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::MoveElementTo(
|
|
const OLECHAR* pwcsName,
|
|
IStorage* pstgDest,
|
|
const OLECHAR* pwcsNewName,
|
|
DWORD grfFlags)
|
|
{
|
|
nffXTrace( "CNtfsStorage::MoveElementTo" );
|
|
HRESULT sc=S_OK;
|
|
|
|
NFF_VALIDATE( MoveElementTo( pwcsName, pstgDest, pwcsNewName, grfFlags ) );
|
|
|
|
Lock( INFINITE );
|
|
|
|
nffChk( CheckReverted() );
|
|
|
|
// MoveElementTo not supported. Use CopyTo and DestroyElement
|
|
|
|
EH_Err:
|
|
Unlock();
|
|
|
|
return( E_NOTIMPL );
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::Commit
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::Commit( DWORD grfCommitFlags )
|
|
{
|
|
nffXTrace( "CNtfsStorage::Commit" );
|
|
CNtfsStream *pstm = NULL;
|
|
HRESULT sc=S_OK;
|
|
|
|
NFF_VALIDATE( Commit( grfCommitFlags ) );
|
|
|
|
Lock( INFINITE );
|
|
|
|
nffChk( CheckReverted() );
|
|
|
|
// Commit the property bag (stored as a special stream).
|
|
|
|
nffChk( _PropertyBagEx.Commit( grfCommitFlags ));
|
|
|
|
// Loop through the open streams and commit (flush) them.
|
|
|
|
if( NULL != _pstmOpenList ) // Skip the head sentinal;
|
|
pstm = _pstmOpenList->_pnffstmNext;
|
|
|
|
while(NULL != pstm)
|
|
{
|
|
sc = pstm->Commit ( grfCommitFlags );
|
|
if( S_OK != sc )
|
|
break;
|
|
|
|
pstm = pstm->_pnffstmNext;
|
|
}
|
|
|
|
EH_Err:
|
|
Unlock();
|
|
|
|
return( sc );
|
|
}
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::Revert
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::Revert( void )
|
|
{
|
|
nffXTrace( "CNtfsStorage::Revert" );
|
|
// We don't support transactioning, so we must be in direct mode.
|
|
// In direct mode, return S_OK on Revert.
|
|
return( S_OK );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::EnumElements
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::EnumElements(
|
|
DWORD res1,
|
|
void* res2,
|
|
DWORD res3,
|
|
IEnumSTATSTG** ppenum )
|
|
{
|
|
nffXTrace( "CNtfsStorage::EnumElements" );
|
|
CNtfsEnumSTATSTG *pNtfsEnumSTATSTG = NULL;
|
|
HRESULT sc=S_OK;
|
|
|
|
NFF_VALIDATE( EnumElements( res1, res2, res3, ppenum ) );
|
|
|
|
Lock( INFINITE );
|
|
|
|
nffChk( CheckReverted() );
|
|
|
|
// Create the enumerator
|
|
pNtfsEnumSTATSTG = new CNtfsEnumSTATSTG(
|
|
static_cast<IBlockingLock*>(_pTreeMutex) );
|
|
|
|
if( NULL == pNtfsEnumSTATSTG )
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto EH_Err;
|
|
}
|
|
|
|
// Initialize the enumerator
|
|
|
|
nffChk( pNtfsEnumSTATSTG->Init( _hFileMainStream ));
|
|
|
|
// ----
|
|
// Exit
|
|
// ----
|
|
|
|
*ppenum = static_cast<IEnumSTATSTG*>(pNtfsEnumSTATSTG);
|
|
pNtfsEnumSTATSTG = NULL;
|
|
sc = S_OK;
|
|
|
|
EH_Err:
|
|
|
|
if( NULL != pNtfsEnumSTATSTG )
|
|
delete pNtfsEnumSTATSTG;
|
|
|
|
Unlock();
|
|
return( sc );
|
|
|
|
} // CNtfsStorage::EnumElements
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::DestroyElement
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::DestroyElement( const OLECHAR* pwcsName )
|
|
{
|
|
nffXTrace( "CNtfsStorage::DestroyElement" );
|
|
HRESULT sc=S_OK;
|
|
|
|
NFF_VALIDATE( DestroyElement( pwcsName ) );
|
|
|
|
Lock( INFINITE );
|
|
|
|
nffChk( CheckReverted() );
|
|
|
|
|
|
// We don't allow Destroying the CONTENT Stream.
|
|
|
|
if( IsContentStream( pwcsName ) )
|
|
nffErr( EH_Err, STG_E_INVALIDFUNCTION );
|
|
|
|
nffDebug((DEB_INFO, "CNtfsStorage::DestroyElement(\"%ws\", %x)\n",
|
|
pwcsName));
|
|
|
|
// Try to delete the stream
|
|
|
|
sc = DestroyStreamElement( pwcsName );
|
|
if( STG_E_PATHNOTFOUND == sc || STG_E_FILENOTFOUND == sc )
|
|
{
|
|
// It didn't exist - delete it as a storage (a storage is
|
|
// really a docfile, and the stream name has a "Docf_" prefix).
|
|
// ?? Why is this storage logic here? The storage logic is supposed
|
|
// to be part of CNtfsStorageForPropSet, and not known here?
|
|
|
|
sc = DestroyStreamElement( CDocfileStreamName(pwcsName) );
|
|
}
|
|
nffChk(sc);
|
|
|
|
// If the stream is already open, revert it.
|
|
|
|
CNtfsStream *pstm;
|
|
if( FindAlreadyOpenStream( pwcsName, &pstm ) ) // revert open stream
|
|
{
|
|
pstm->ShutDown();
|
|
pstm->Release();
|
|
}
|
|
|
|
EH_Err:
|
|
|
|
Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::RenameElement
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::RenameElement(
|
|
const OLECHAR* pwcsOldName,
|
|
const OLECHAR* pwcsNewName)
|
|
{
|
|
HRESULT sc=S_OK;
|
|
CNtfsStream *pstm = NULL;
|
|
nffXTrace( "CNtfsStorage::RenameElement" );
|
|
|
|
//NFF_VALIDATE( RenameElement( pwcsOldName, pwcsNewName ) );
|
|
|
|
Lock( INFINITE );
|
|
nffChk( CheckReverted() );
|
|
|
|
|
|
// We don't allow Renaming the CONTENT Stream.
|
|
|
|
if( IsContentStream( pwcsOldName ) )
|
|
nffErr( EH_Err, STG_E_INVALIDFUNCTION );
|
|
|
|
// Get a CNtfsStream for this stream (you have to open it,
|
|
// with exclusive access, in order to rename it).
|
|
|
|
nffChk( NewCNtfsStream( pwcsOldName,
|
|
STGM_READWRITE|STGM_SHARE_EXCLUSIVE,
|
|
FALSE,
|
|
&pstm ) );
|
|
|
|
// Rename it
|
|
|
|
nffChk( pstm->Rename( pwcsNewName, FALSE ));
|
|
|
|
nffVerify( 0 == pstm->Release() );
|
|
pstm = NULL;
|
|
|
|
EH_Err:
|
|
|
|
if( NULL != pstm )
|
|
nffVerify( 0 == pstm->Release() );
|
|
|
|
Unlock();
|
|
return( sc );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::SetElementTimes
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::SetElementTimes(
|
|
const OLECHAR* pwcsName,
|
|
const FILETIME* pctime,
|
|
const FILETIME* patime,
|
|
const FILETIME* pmtime)
|
|
{
|
|
nffXTrace( "CNtfsStorage::SetElementTimes" );
|
|
HRESULT sc=S_OK;
|
|
|
|
NFF_VALIDATE( SetElementTimes( pwcsName, pctime, patime, pmtime ) );
|
|
|
|
if(NULL != pwcsName)
|
|
return S_OK;
|
|
|
|
Lock ( INFINITE );
|
|
nffChk( CheckReverted() );
|
|
|
|
nffDebug((DEB_INFO, "CNtfsStorage::SetElementTimes(\"%ws\")\n",
|
|
pwcsName));
|
|
|
|
// If user mode code sets the last modified times on a handle,
|
|
// then WriteFile()s no longer changes the last modified time
|
|
|
|
sc = SetAllStreamsTimes(pctime, patime, pmtime);
|
|
|
|
EH_Err:
|
|
Unlock();
|
|
return( sc );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::SetClass
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::SetClass(
|
|
REFCLSID clsid)
|
|
{
|
|
nffXTrace( "CNtfsStorage::SetClass" );
|
|
CLSID clsidOld = _clsidStgClass;
|
|
HRESULT sc = S_OK;
|
|
|
|
NFF_VALIDATE( SetClass( clsid ) );
|
|
|
|
Lock( INFINITE );
|
|
nffChk( CheckReverted() );
|
|
|
|
_clsidStgClass = clsid;
|
|
nffChk( WriteControlStream() );
|
|
|
|
EH_Err:
|
|
|
|
if (FAILED(sc))
|
|
_clsidStgClass = clsidOld;
|
|
|
|
Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::SetStateBits
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::SetStateBits(
|
|
DWORD grfStateBits,
|
|
DWORD grfMask)
|
|
{
|
|
// Reserved for future use
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IStorage::Stat
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::Stat(
|
|
STATSTG *pstatstg,
|
|
DWORD grfStatFlag)
|
|
{
|
|
nffXTrace( "CNtfsStorage::Stat" );
|
|
HRESULT sc = S_OK;
|
|
BY_HANDLE_FILE_INFORMATION ByHandleFileInformation;
|
|
WCHAR* pwszPath=NULL;
|
|
|
|
STATSTG statstg;
|
|
|
|
NFF_VALIDATE( Stat( pstatstg, grfStatFlag ) );
|
|
|
|
statstg.pwcsName = NULL;
|
|
|
|
Lock( INFINITE );
|
|
nffChk( CheckReverted() );
|
|
|
|
nffDebug((DEB_INFO, "CNtfsStorage::Stat()\n"));
|
|
|
|
// Does the caller want a name?
|
|
if( (STATFLAG_NONAME & grfStatFlag) )
|
|
pwszPath = NULL;
|
|
else
|
|
nffChk( GetFilePath( &pwszPath ) );
|
|
|
|
// Get the type
|
|
statstg.type = STGTY_STORAGE;
|
|
|
|
// Get the size & times.
|
|
|
|
if( !GetFileInformationByHandle( _hFileMainStream,
|
|
&ByHandleFileInformation ))
|
|
{
|
|
nffErr( EH_Err, LAST_SCODE );
|
|
}
|
|
|
|
statstg.cbSize.LowPart = ByHandleFileInformation.nFileSizeLow;
|
|
statstg.cbSize.HighPart = ByHandleFileInformation.nFileSizeHigh;
|
|
|
|
statstg.mtime = ByHandleFileInformation.ftLastWriteTime;
|
|
statstg.atime = ByHandleFileInformation.ftLastAccessTime;
|
|
statstg.ctime = ByHandleFileInformation.ftCreationTime;
|
|
|
|
statstg.grfLocksSupported = 0; // no locks supported
|
|
|
|
// Get the STGM modes
|
|
statstg.grfMode = _grfMode & ~STGM_CREATE;
|
|
|
|
|
|
// Get the clsid & state bits
|
|
|
|
statstg.grfStateBits = _dwStgStateBits;
|
|
statstg.clsid = _clsidStgClass;
|
|
|
|
|
|
sc = S_OK;
|
|
|
|
statstg.pwcsName = pwszPath;
|
|
pwszPath = NULL;
|
|
|
|
*pstatstg = statstg;
|
|
|
|
EH_Err:
|
|
|
|
if(NULL != pwszPath)
|
|
CoTaskMemFree( pwszPath);
|
|
|
|
Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IBlockingLock::Lock
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline HRESULT
|
|
CNtfsStorage::Lock( DWORD dwTimeout )
|
|
{
|
|
// Don't trace at this level. The noice is too great!
|
|
// nffCDbgTrace dbg(DEB_ITRACE, "CNtfsStorage::Lock");
|
|
nffAssert( INFINITE == dwTimeout );
|
|
if( INFINITE != dwTimeout )
|
|
return( E_NOTIMPL );
|
|
|
|
// If there was an error during Initialize(), we may not have created the tree
|
|
// mutex.
|
|
|
|
if( NULL == _pTreeMutex )
|
|
return( E_NOTIMPL );
|
|
else
|
|
return _pTreeMutex->Lock( dwTimeout );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage IBlockingLock::Unlock
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline HRESULT
|
|
CNtfsStorage::Unlock()
|
|
{
|
|
// Don't trace at this level. The noice is too great!
|
|
// nffCDbgTrace dbg(DEB_ITRACE, "CNtfsStorage::Unlock");
|
|
|
|
// If there was an error during Initialize(), we may not have created the tree
|
|
// mutex.
|
|
|
|
if( NULL == _pTreeMutex )
|
|
return( E_NOTIMPL );
|
|
else
|
|
return _pTreeMutex->Unlock();
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage ITimeAndNoticeControl::SuppressChanges
|
|
//
|
|
// This method puts the storage in a state such that the modify timestamp
|
|
// is not updated, and
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::SuppressChanges(
|
|
DWORD res1,
|
|
DWORD res2)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
// End of Interface Methods
|
|
// -------------
|
|
// Start of C++ Methods.
|
|
//+----------------------------------------------------------------------------
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Constructor
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline
|
|
CNtfsStorage::CNtfsStorage( DWORD grfMode )
|
|
: _sig(NTFSSTORAGE_SIG),
|
|
CPropertySetStorage( MAPPED_STREAM_QI ),
|
|
_PropertyBagEx( grfMode )
|
|
{
|
|
nffITrace("CNtfsStorage::CNtfsStorage");
|
|
|
|
_grfMode = grfMode;
|
|
|
|
_pstmOpenList = NULL;
|
|
_hFileMainStream = INVALID_HANDLE_VALUE;
|
|
_hFileControlStream = INVALID_HANDLE_VALUE;
|
|
|
|
_wcDriveLetter = 0;
|
|
_dwState = 0;
|
|
|
|
_hsmStatus = 0;
|
|
_dwStgStateBits = 0;
|
|
_clsidStgClass = CLSID_NULL;
|
|
|
|
_pTreeMutex = NULL;
|
|
_filetime.dwHighDateTime = 0;
|
|
_filetime.dwLowDateTime = 0;
|
|
|
|
// Finish initialization the property set objects.
|
|
|
|
_NtfsStorageForPropSetStg.Init( this ); // Not add-refed
|
|
|
|
CPropertySetStorage::Init( static_cast<IStorage*>(&_NtfsStorageForPropSetStg),
|
|
static_cast<IBlockingLock*>(this),
|
|
FALSE ); // fControlLifetimes (=> don't addref)
|
|
|
|
// These are also not add-refed
|
|
_PropertyBagEx.Init( static_cast<IPropertySetStorage*>(this),
|
|
static_cast<IBlockingLock*>(this) );
|
|
};
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::IsNffAppropriate
|
|
//
|
|
// Synopsis: Looks for Control stream and Docfile Header given a filename
|
|
//
|
|
// Arguments: [pwszName] - Filename.
|
|
//
|
|
// History: 22-July-98 BChapman Created
|
|
// 10-Nov-98 BChapman Added global routine so it can be
|
|
// called by code that doesn't include
|
|
// CNtfsStorage/CNtfsStream definitions.
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT IsNffAppropriate( const LPCWSTR pwcsName )
|
|
{
|
|
return CNtfsStorage::IsNffAppropriate( pwcsName );
|
|
}
|
|
|
|
HRESULT // static
|
|
CNtfsStorage::IsNffAppropriate( const LPCWSTR pwcsName )
|
|
{
|
|
UNICODE_STRING usNtfsName;
|
|
LPWSTR pFreeBuffer=NULL;
|
|
HANDLE hFile=INVALID_HANDLE_VALUE;
|
|
HRESULT sc=S_OK;
|
|
|
|
if (NULL == pwcsName)
|
|
nffErr (EH_Err, STG_E_INVALIDNAME);
|
|
|
|
if (!RtlDosPathNameToNtPathName_U(pwcsName, &usNtfsName, NULL, NULL))
|
|
nffErr(EH_Err, STG_E_INVALIDNAME);
|
|
|
|
// This buffer will need free'ing later
|
|
pFreeBuffer = usNtfsName.Buffer;
|
|
|
|
// When Checking file state always open the main stream ReadOnly share
|
|
// everything. We allow opening Directories.
|
|
//
|
|
nffChk( OpenNtFileHandle( usNtfsName,
|
|
NULL, // No Parent File Handle
|
|
STGM_READ | STGM_SHARE_DENY_NONE,
|
|
NFFOPEN_NORMAL,
|
|
FALSE, // Not a Create API
|
|
&hFile ) );
|
|
|
|
nffChk( IsNffAppropriate( hFile, pwcsName ) );
|
|
|
|
EH_Err:
|
|
if (NULL != pFreeBuffer)
|
|
RtlFreeHeap(RtlProcessHeap(), 0, pFreeBuffer);
|
|
|
|
if( INVALID_HANDLE_VALUE != hFile )
|
|
NtClose( hFile );
|
|
|
|
return( sc );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::IsNffAppropriate
|
|
//
|
|
// Synopsis: Looks for Control stream and Docfile Header given a HFILE
|
|
//
|
|
// Arguments: [hFile] - readable File Handle to the main stream.
|
|
//
|
|
// History: 22-July-98 BChapman Created
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::IsNffAppropriate( HANDLE hFile,
|
|
const WCHAR* wcszPath )
|
|
{
|
|
PFILE_STREAM_INFORMATION pfsiBuf = NULL;
|
|
ULONG cbBuf;
|
|
OVERLAPPED olpTemp;
|
|
HANDLE ev=NULL;
|
|
HRESULT sc=S_OK;
|
|
|
|
olpTemp.hEvent = NULL;
|
|
|
|
|
|
// Check that we are on NTFS by doing a stream enum.
|
|
// This is also useful later when looking for a control stream.
|
|
// PERF we should cache the fsi (reload every create or delete)
|
|
//
|
|
sc = EnumNtStreams( hFile, &pfsiBuf, &cbBuf, TRUE );
|
|
if( FAILED(sc) )
|
|
{
|
|
nffDebug((DEB_IWARN, "EnumNtStreams Failed. Not NTFS.\n" ));
|
|
nffErr( EH_Err, STG_E_INVALIDFUNCTION );
|
|
}
|
|
|
|
// If the control stream exists then the file is NFF.
|
|
|
|
if( IsControlStreamExtant( pfsiBuf ) )
|
|
{
|
|
// This is a kludge test for nt4-pre-sp4 system over the RDR.
|
|
// We don't support NFF on those systems (because they didn't
|
|
// allow the '\005' property set name prefix charachter).
|
|
|
|
nffChk( TestNt4StreamNameBug( pfsiBuf, wcszPath ) );
|
|
|
|
goto EH_Err; // return S_OK;
|
|
}
|
|
|
|
|
|
// Don't read HSM migrated Files.
|
|
// If the test fails in any way, assume it is not an HSM file.
|
|
//
|
|
if( S_OK == IsOfflineFile( hFile ) )
|
|
nffErr( EH_Err, STG_E_INCOMPLETE );
|
|
|
|
// Check that the file is not a Storage File. Docfile and NSS don't
|
|
// want this implementation making NTFS streams on their files.
|
|
//
|
|
// To do this we need to read the main stream.
|
|
|
|
ZeroMemory( &olpTemp, sizeof(OVERLAPPED) );
|
|
|
|
// Create the Event for the Overlapped structure.
|
|
//
|
|
ev = CreateEvent( NULL, // Security Attributes.
|
|
TRUE, // Manual Reset Flag.
|
|
FALSE, // Inital State = Signaled, Flag.
|
|
NULL ); // Name
|
|
|
|
if( NULL == ev)
|
|
nffErr( EH_Err, LAST_SCODE );
|
|
|
|
olpTemp.hEvent = ev;
|
|
|
|
// See if this is an NSS
|
|
|
|
nffChk( StgIsStorageFileHandle( hFile, &olpTemp ) );
|
|
if( S_OK == sc )
|
|
nffErr (EH_Err, STG_E_INVALIDFUNCTION);
|
|
|
|
nffAssert(S_FALSE == sc);
|
|
|
|
// Ensure this isn't NT4-pre-SP4, where we don't
|
|
// support NFF (that was before '\005' became a legal
|
|
// character in stream names).
|
|
|
|
nffChk( TestNt4StreamNameBug( pfsiBuf, wcszPath ) );
|
|
|
|
sc = S_OK;
|
|
|
|
EH_Err:
|
|
if( NULL != pfsiBuf )
|
|
delete [] (BYTE *)pfsiBuf;
|
|
|
|
if( NULL != olpTemp.hEvent )
|
|
CloseHandle( olpTemp.hEvent );
|
|
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::TestNt4StreamNameBug
|
|
//
|
|
// Synopsis: Check if a stream with a \005 in the name can be opened.
|
|
// This routine added an extra NTCreateFile to the NFF Open
|
|
// path. And limits the length of the filename component to
|
|
// MAX_PATH - StrLen(PropSetName(). This routine should be
|
|
// eliminated as soon as NT4 Sp3 is history.
|
|
// The bug this looking for was fixed in nt4 sp4 and Nt5.
|
|
//
|
|
// Returns: S_OK if the system is working correctly
|
|
// STG_E_INVALIDFUNCTION if a \005 stream cannot be opened
|
|
//
|
|
// Arguments: [pfsiBuf] - Buffer of Enumerated Stream Names.
|
|
// [wcszPath] - Full Pathname of file.
|
|
//
|
|
// History: 12-Oct-98 BChapman Created
|
|
//+----------------------------------------------------------------------------
|
|
|
|
FMTID FMTID_NT4Check = { /* ffc11011-5e3b-11d2-8a3e-00c04f8eedad */
|
|
0xffc11011,
|
|
0x5e3b,
|
|
0x11d2,
|
|
{0x8a, 0x3e, 0x00, 0xc0, 0x4f, 0x8e, 0xed, 0xad}
|
|
};
|
|
|
|
HRESULT
|
|
CNtfsStorage::TestNt4StreamNameBug(
|
|
PFILE_STREAM_INFORMATION pfsiBuf,
|
|
const WCHAR* pwcszPath )
|
|
{
|
|
const WCHAR* pwcszNtStreamName=NULL;
|
|
WCHAR* pwszPathBuf=NULL;
|
|
int ccBufSize=0;
|
|
HANDLE hFile;
|
|
UNICODE_STRING usNtfsName;
|
|
OBJECT_ATTRIBUTES object_attributes;
|
|
IO_STATUS_BLOCK iostatusblock;
|
|
ACCESS_MASK accessmask=0;
|
|
ULONG ulAttrs=0;
|
|
ULONG ulSharing=0;
|
|
ULONG ulCreateDisp=0;
|
|
ULONG ulCreateOpt = 0;
|
|
NTSTATUS status;
|
|
HRESULT sc=S_OK;
|
|
|
|
// If there is no pathname (and in some calling paths there isn't)
|
|
// then we can't do the test.
|
|
//
|
|
if( NULL == pwcszPath )
|
|
goto EH_Err; // S_OK
|
|
|
|
// last ditch optimization to prevent having to do
|
|
// the NT4 pre-sp4 stream name bug tests.
|
|
//
|
|
if( AreAnyNtPropertyStreamsExtant( pfsiBuf ) )
|
|
goto EH_Err; // S_OK
|
|
//
|
|
// OK here is the deal....
|
|
// Try to open READONLY a stream that doesn't exist with a \005 in
|
|
// the name. If the system supports such stream names then it will
|
|
// return filenotfound, on NT4 before sp4 it will return INVALIDNAME.
|
|
//
|
|
{
|
|
CPropSetName psn( FMTID_NT4Check );
|
|
CNtfsStreamName nsn( psn.GetPropSetName() );
|
|
pwcszNtStreamName = nsn;
|
|
|
|
//
|
|
// Use the NT API so we don't have to worry about the length
|
|
// of the name. We have to convert the name while it is less
|
|
// than MAX_PATH.
|
|
//
|
|
if (!RtlDosPathNameToNtPathName_U(pwcszPath, &usNtfsName, NULL, NULL))
|
|
nffErr(EH_Err, STG_E_INVALIDNAME);
|
|
|
|
//
|
|
// Build a buffer with the Path + Stream name. Free the
|
|
// allocated UNICODE_STRING name and point at the buffer.
|
|
//
|
|
ccBufSize = usNtfsName.Length/sizeof(WCHAR)+ (ULONG)wcslen(pwcszNtStreamName) + 1;
|
|
__try
|
|
{
|
|
pwszPathBuf = (WCHAR*) alloca( ccBufSize*sizeof(WCHAR) );
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
RtlFreeHeap(RtlProcessHeap(), 0, usNtfsName.Buffer);
|
|
nffErr(EH_Err, STG_E_INSUFFICIENTMEMORY);
|
|
}
|
|
|
|
StringCchCopy( pwszPathBuf, ccBufSize, usNtfsName.Buffer );
|
|
StringCchCat( pwszPathBuf, ccBufSize, pwcszNtStreamName );
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, usNtfsName.Buffer);
|
|
usNtfsName.Buffer = pwszPathBuf;
|
|
usNtfsName.Length = (USHORT) wcslen(pwszPathBuf)*sizeof(WCHAR);
|
|
usNtfsName.MaximumLength = (USHORT)(ccBufSize*sizeof(WCHAR));
|
|
|
|
InitializeObjectAttributes(&object_attributes,
|
|
&usNtfsName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
nffChk( ModeToNtFlags( STGM_READ|STGM_SHARE_DENY_NONE, 0, FALSE,
|
|
&accessmask, &ulAttrs, &ulSharing,
|
|
&ulCreateDisp, &ulCreateOpt ) );
|
|
|
|
status = NtCreateFile( &hFile, accessmask,
|
|
&object_attributes, &iostatusblock,
|
|
NULL,
|
|
ulAttrs, ulSharing,
|
|
ulCreateDisp, ulCreateOpt,
|
|
NULL, 0);
|
|
|
|
|
|
nffAssert( NULL == hFile && "NFF Property TestNt4StreamNameBug" );
|
|
if( NULL != hFile )
|
|
CloseHandle( hFile );
|
|
|
|
// The system doesn't support \005.
|
|
//
|
|
if( STATUS_OBJECT_NAME_INVALID == status )
|
|
{
|
|
nffDebug(( DEB_OPENS, "Nt4File: file=(%x) \"%ws\"\n",
|
|
pwszPathBuf, pwszPathBuf ));
|
|
nffErr( EH_Err, STG_E_INVALIDFUNCTION );
|
|
}
|
|
|
|
// NOT_FOUND is the expected status for good systems.
|
|
//
|
|
if( STATUS_OBJECT_NAME_NOT_FOUND != status )
|
|
{
|
|
nffDebug(( DEB_IWARN, "NT4Chk Create Stream status 0x%x\n", status ));
|
|
nffErr(EH_Err, STG_E_INVALIDFUNCTION);
|
|
}
|
|
}
|
|
|
|
EH_Err:
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::IsOfflineFile
|
|
//
|
|
// Synopsis: Check for FILE_ATTRIBUTE_OFFLINE in the file attributes.
|
|
//
|
|
// Returns: S_OK if the file attributes have the bit set.
|
|
// S_FALSE if the file attributes do not have the bit set.
|
|
// E_* if an error occured while accessing the attributes.
|
|
//
|
|
// Arguments: [hFile] - Attribute Readable file handle
|
|
//
|
|
// History: 27-July-98 BChapman Created
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::IsOfflineFile( HANDLE hFile )
|
|
{
|
|
HRESULT sc=S_OK;
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK iostatblk;
|
|
FILE_BASIC_INFORMATION fbi;
|
|
|
|
status = NtQueryInformationFile( hFile,
|
|
&iostatblk,
|
|
(PVOID) &fbi,
|
|
sizeof(FILE_BASIC_INFORMATION),
|
|
FileBasicInformation );
|
|
|
|
if( !NT_SUCCESS(status) )
|
|
{
|
|
nffDebug(( DEB_IERROR,
|
|
"Query FileAttributeTagInformation file=%x, failed stat=%x\n",
|
|
hFile, status ));
|
|
nffErr( EH_Err, NtStatusToScode( status ) );
|
|
}
|
|
|
|
// If it does not have a reparse tag, it is not HighLatency
|
|
//
|
|
if( 0==( FILE_ATTRIBUTE_OFFLINE & fbi.FileAttributes) )
|
|
{
|
|
sc = S_FALSE;
|
|
goto EH_Err;
|
|
}
|
|
|
|
EH_Err:
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::InitFromName
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::InitFromName(
|
|
const WCHAR *pwcsName,
|
|
BOOL fCreateAPI,
|
|
DWORD dwOpenFlags )
|
|
{
|
|
nffITrace( "CNtfsStorage::Init (name)" );
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
UNICODE_STRING usNtfsName;
|
|
WCHAR* pFreeBuffer=NULL;
|
|
DWORD dwTid;
|
|
HRESULT sc=S_OK;
|
|
DWORD fCreateOrNot=0;
|
|
|
|
if (!RtlDosPathNameToNtPathName_U(pwcsName, &usNtfsName, NULL, NULL))
|
|
nffErr(EH_Err, STG_E_INVALIDNAME);
|
|
|
|
// This buffer will need free'ing later
|
|
pFreeBuffer = usNtfsName.Buffer;
|
|
|
|
|
|
// Regardless of _grfMode, always open the unnamed stream read-only share
|
|
// everything. We allow opening Directories. We use this handle to
|
|
// dup-open all the other streams.
|
|
//
|
|
if( fCreateAPI && (STGM_CREATE & _grfMode) )
|
|
fCreateOrNot = STGM_CREATE;
|
|
|
|
nffChk( OpenNtFileHandle( usNtfsName,
|
|
NULL, // No Parent File Handle
|
|
STGM_READ | STGM_SHARE_DENY_NONE | fCreateOrNot,
|
|
dwOpenFlags,
|
|
fCreateAPI,
|
|
&hFile ) );
|
|
|
|
// Cache the drive letter so that in Stat we can compose a
|
|
// complete path name
|
|
_wcDriveLetter = GetDriveLetter( pwcsName );
|
|
|
|
nffChk( InitFromMainStreamHandle( &hFile,
|
|
pwcsName,
|
|
fCreateAPI,
|
|
dwOpenFlags,
|
|
STGFMT_ANY ) );
|
|
|
|
// hFile now belongs to the object.
|
|
nffAssert( INVALID_HANDLE_VALUE == hFile );
|
|
|
|
|
|
EH_Err:
|
|
if (NULL != pFreeBuffer)
|
|
RtlFreeHeap(RtlProcessHeap(), 0, pFreeBuffer);
|
|
|
|
|
|
if( INVALID_HANDLE_VALUE != hFile )
|
|
NtClose( hFile );
|
|
|
|
return( sc );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::InitFromMainStreamHandle
|
|
//
|
|
// Synopsis: Opens NFF file from a handle.
|
|
//
|
|
// Arguments: [hFileMainStream] - ReadOnly DenyNone file handle.
|
|
// [fCreateAPI] - Called from a Create API (vs. Open)
|
|
// [fmtKnown] - STGFMT_FILE if IsNffAppropriate has
|
|
// already been called. STGFMT_ANY otherwise
|
|
//
|
|
// History: 05-Jun-98 BChapman Created
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::InitFromMainStreamHandle(
|
|
HANDLE* phFileMainStream,
|
|
const WCHAR* wcszPath,
|
|
BOOL fCreateAPI,
|
|
DWORD dwOpenFlags,
|
|
DWORD fmtKnown )
|
|
{
|
|
nffITrace( "CNtfsStorage::InitFromMainStreamHandle(HANDLE)" );
|
|
|
|
HRESULT sc = S_OK;
|
|
|
|
CNtfsStream* pstmHeadSentinal= NULL;
|
|
CNFFTreeMutex* pTreeMutex = NULL;
|
|
|
|
|
|
// Check that this file should be opened by NFF.
|
|
// Skip the check if the caller has already figured it out.
|
|
//
|
|
if( STGFMT_FILE != fmtKnown )
|
|
{
|
|
nffChk( IsNffAppropriate( *phFileMainStream, wcszPath ) );
|
|
fmtKnown = STGFMT_FILE;
|
|
}
|
|
|
|
//
|
|
// Load the Main Stream Handle here so member functions (below)
|
|
// can use it.
|
|
//
|
|
_hFileMainStream = *phFileMainStream;
|
|
*phFileMainStream = INVALID_HANDLE_VALUE;
|
|
|
|
// If requested, suppress changes, starting with the main stream handle
|
|
// we just accepted. All subsequent stream opens/creates (including the
|
|
// Control stream) will be marked similarly.
|
|
//
|
|
if( NFFOPEN_SUPPRESS_CHANGES & dwOpenFlags )
|
|
nffChk( SuppressChanges(1, 0) );
|
|
|
|
// Open the ControlPropertySet and place the SHARE MODE locks there
|
|
//
|
|
nffChk( OpenControlStream( fCreateAPI ) );
|
|
|
|
// Create a Mutex to Serialize access to the NFF File
|
|
//
|
|
nffMem( pTreeMutex = new CNFFTreeMutex() );
|
|
nffChk( pTreeMutex->Init() );
|
|
|
|
|
|
// Create an Head Sentinal for the Open Stream List
|
|
//
|
|
nffMem( pstmHeadSentinal = new CNtfsStream( this, pTreeMutex ) );
|
|
|
|
|
|
// Success!
|
|
_dwState |= NFF_INIT_COMPLETED;
|
|
|
|
_pTreeMutex = pTreeMutex;
|
|
pTreeMutex = NULL;
|
|
|
|
_pstmOpenList = pstmHeadSentinal;
|
|
pstmHeadSentinal = NULL;
|
|
|
|
|
|
EH_Err:
|
|
if( NULL != pTreeMutex )
|
|
pTreeMutex->Release();
|
|
|
|
if( NULL != pstmHeadSentinal )
|
|
pstmHeadSentinal->Release();
|
|
|
|
return( sc );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Destructor
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline
|
|
CNtfsStorage::~CNtfsStorage()
|
|
{
|
|
nffITrace("CNtfsStorage::~CNtfsStorage");
|
|
|
|
DWORD rc, hrThread=S_OK;
|
|
HANDLE thread;
|
|
|
|
nffDebug(( DEB_REFCOUNT, "~CNtfsStorage\n" ));
|
|
|
|
ShutDownStorage();
|
|
|
|
if( NULL != _pTreeMutex )
|
|
_pTreeMutex->Release();
|
|
|
|
nffAssert( NULL == _pstmOpenList );
|
|
|
|
_sig = NTFSSTORAGE_SIGDEL;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::ShutDownStorage
|
|
//
|
|
// Flush data, Close File handle and mark the objects as reverted.
|
|
// This is called when the Oplock Breaks and when the Storage is released.
|
|
// In neither case does the caller hold the tree mutex. So we take it here.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::ShutDownStorage()
|
|
{
|
|
nffITrace( "CNtfsStorage::ShutDownStorage" );
|
|
HRESULT sc, scAccum=S_OK;
|
|
CNtfsStream *pstm = NULL;
|
|
CNtfsStream *pstmNext = NULL;
|
|
ULONG cbBuf;
|
|
|
|
Lock( INFINITE );
|
|
|
|
if(NFF_FILE_CLOSED & _dwState)
|
|
goto EH_Err;
|
|
|
|
_dwState |= NFF_FILE_CLOSED;
|
|
|
|
nffDebug(( DEB_INFO, "CNtfsStorage::Shutdown called\n" ));
|
|
|
|
// Shut down the property bag (it is initialized in the
|
|
// constructor, so we need to shut it down whether or not
|
|
// we ever completed initialization).
|
|
|
|
if( FAILED(sc = _PropertyBagEx.ShutDown() ))
|
|
scAccum = sc;
|
|
|
|
// Clean up the tree mutex and open stream list, neither of which
|
|
// will exist unless we're fully initialized.
|
|
|
|
if(NFF_INIT_COMPLETED & _dwState)
|
|
{
|
|
|
|
nffAssert( NULL != _pstmOpenList );
|
|
|
|
// Skip the head sentinal;
|
|
pstm = _pstmOpenList->_pnffstmNext;
|
|
|
|
//
|
|
// Shutdown all the streams. If there is a problem, make note of it
|
|
// but continue until all the streams are shutdown.
|
|
//
|
|
while(NULL != pstm)
|
|
{
|
|
// ShutDown will cut itself from the list, so pick up the
|
|
// Next pointer before calling it.
|
|
// Let the streams go loose (don't delete or Release them)
|
|
// the app still holds the reference (the list never had a reference)
|
|
//
|
|
pstmNext = pstm->_pnffstmNext;
|
|
|
|
if( FAILED( sc = pstm->ShutDown() ) )
|
|
scAccum = sc;
|
|
|
|
pstm = pstmNext;
|
|
}
|
|
|
|
// Delete the head sentinal because it is not a stream and the
|
|
// app doesn't have a pointer to it.
|
|
//
|
|
nffAssert( NULL == _pstmOpenList->_pnffstmNext );
|
|
_pstmOpenList->Release();
|
|
_pstmOpenList = NULL;
|
|
|
|
}
|
|
|
|
// Close down all the handles
|
|
|
|
nffDebug(( DEB_OPENS, "Closing Storage w/ _hFileMainStream=%x\n",
|
|
_hFileMainStream ));
|
|
|
|
if( INVALID_HANDLE_VALUE != _hFileControlStream )
|
|
{
|
|
CloseHandle( _hFileControlStream );
|
|
_hFileControlStream = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if( INVALID_HANDLE_VALUE != _hFileMainStream )
|
|
{
|
|
CloseHandle( _hFileMainStream );
|
|
_hFileMainStream = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
_dwState |= NFF_REVERTED;
|
|
|
|
|
|
EH_Err:
|
|
|
|
Unlock();
|
|
return scAccum;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::GetStreamHandle
|
|
//
|
|
// This method gets the HANDLE for the named stream.
|
|
// It understands grfModes and RelativeOpens of a main stream handle.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::GetStreamHandle(
|
|
HANDLE *phStream,
|
|
const WCHAR * pwcsName,
|
|
DWORD grfMode,
|
|
BOOL fCreateAPI)
|
|
{
|
|
HANDLE hFileStream = INVALID_HANDLE_VALUE;
|
|
HRESULT sc=S_OK;
|
|
DWORD dwNffOpenFlags=0;
|
|
CNtfsStreamName nsn( pwcsName );
|
|
|
|
nffDebug(( DEB_ITRACE | DEB_INFO,
|
|
"GetStreamHandle(\"%ws\", grf=0x%x, %s)\n",
|
|
pwcsName, grfMode, fCreateAPI?"Create":"Open" ));
|
|
|
|
Lock( INFINITE );
|
|
|
|
if( IsContentStream( pwcsName ) )
|
|
{
|
|
// The content (main) stream always exists
|
|
//
|
|
if( fCreateAPI && !( STGM_CREATE & grfMode ) )
|
|
nffErr( EH_Err, STG_E_FILEALREADYEXISTS );
|
|
|
|
|
|
// only allow DenyNone mode.
|
|
//
|
|
grfMode &= ~STGM_SHARE_MASK;
|
|
grfMode |= STGM_SHARE_DENY_NONE; // Its is already open for read.
|
|
|
|
dwNffOpenFlags |= NFFOPEN_CONTENTSTREAM;
|
|
}
|
|
else // not the content stream
|
|
{
|
|
// Use the given access mode but
|
|
// Use the container's Share mode.
|
|
//
|
|
grfMode &= ~STGM_SHARE_MASK;
|
|
grfMode |= _grfMode & STGM_SHARE_MASK;
|
|
}
|
|
|
|
//dwNffOpenFlags |= NFFOPEN_ASYNC;
|
|
|
|
sc = OpenNtStream( nsn, // ":name:$DATA"
|
|
grfMode,
|
|
dwNffOpenFlags,
|
|
fCreateAPI,
|
|
&hFileStream );
|
|
#if DBG==1
|
|
if( STG_E_FILENOTFOUND == sc )
|
|
{
|
|
nffDebug(( DEB_IWARN, "GetStreamHandle: stream '%ws' not found\n",
|
|
(const WCHAR*)nsn ));
|
|
goto EH_Err;
|
|
}
|
|
#endif DBG
|
|
nffChk( sc );
|
|
|
|
*phStream = hFileStream;
|
|
hFileStream = INVALID_HANDLE_VALUE;
|
|
|
|
EH_Err:
|
|
|
|
#if DBG==1
|
|
if( S_OK != sc )
|
|
{
|
|
nffDebug(( DEB_OPENS|DEB_INFO,
|
|
"Open on stream '%ws' Failed\n",
|
|
pwcsName ));
|
|
}
|
|
#endif
|
|
if( INVALID_HANDLE_VALUE != hFileStream )
|
|
NtClose( hFileStream );
|
|
|
|
Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::FindAlreadyOpenStream
|
|
//
|
|
// This routine finds the previously open stream by the given name.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
BOOL
|
|
CNtfsStorage::FindAlreadyOpenStream(
|
|
const WCHAR* pwcsName,
|
|
CNtfsStream** ppstm)
|
|
{
|
|
// Skip the head sentinal.
|
|
CNtfsStream *pstm = _pstmOpenList->_pnffstmNext;
|
|
|
|
while(NULL != pstm)
|
|
{
|
|
//if( 0 == _wcsicmp(pwcsName, pstm->GetName() ) )
|
|
if( 0 == dfwcsnicmp( pwcsName, pstm->GetName(), -1 ))
|
|
{
|
|
*ppstm = pstm;
|
|
pstm->AddRef();
|
|
return TRUE;
|
|
}
|
|
pstm = pstm->_pnffstmNext;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::NewCNtfsStream
|
|
//
|
|
// This method lumps together the three phases of creating a new stream
|
|
// object (Constructor, Filesystem Open, and Object Initialization). And
|
|
// it handles the special case of opening the "already open" CONTENTS stream.
|
|
// This begs the question, why is there three phases.
|
|
// 1) We can't put to much in the constructor because of the inability to
|
|
// return errors.
|
|
// 2) GetStreamHandle and InitCNtfsStream are broken apart because of the
|
|
// the special needs in the Storage::Init routine. It opens the
|
|
// CONTENT stream directly and calls InitCNtfsStream to finish.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::NewCNtfsStream( const WCHAR *pwcsName,
|
|
DWORD grfMode,
|
|
BOOL fCreateAPI,
|
|
CNtfsStream **ppstm )
|
|
{
|
|
HRESULT sc=S_OK;
|
|
HANDLE hStream = INVALID_HANDLE_VALUE;
|
|
CNtfsStream *pstm = NULL;
|
|
|
|
Lock( INFINITE );
|
|
|
|
nffMem( pstm = new CNtfsStream( this, (IBlockingLock*) _pTreeMutex ) );
|
|
nffChk( GetStreamHandle( &hStream, pwcsName, grfMode, fCreateAPI ) );
|
|
sc = InitCNtfsStream( pstm, hStream, grfMode, pwcsName );
|
|
|
|
hStream = INVALID_HANDLE_VALUE; // hStream now belongs the the object.
|
|
nffChk(sc);
|
|
|
|
// If we're creating, truncate the stream just in case we opened one
|
|
// that already existed.
|
|
|
|
if( fCreateAPI )
|
|
nffChk( pstm->SetSize( CULargeInteger(0) ));
|
|
|
|
// Load Out parameters and clear working state
|
|
*ppstm = pstm;
|
|
pstm = NULL;
|
|
|
|
EH_Err:
|
|
|
|
nffDebug(( DEB_ITRACE | DEB_INFO,
|
|
"NewCNtfsStream() sc=%x. hFile=0x%x\n",
|
|
sc,
|
|
FAILED(sc)?INVALID_HANDLE_VALUE:(*ppstm)->GetFileHandle() ));
|
|
|
|
if( INVALID_HANDLE_VALUE != hStream )
|
|
NtClose( hStream );
|
|
|
|
if( NULL != pstm )
|
|
pstm->Release();
|
|
|
|
Unlock();
|
|
return( sc );
|
|
}
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface ::DestroyStreamElement
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::DestroyStreamElement( const OLECHAR *pwcsName )
|
|
{
|
|
nffXTrace( "CNtfsStorage::DestroyStreamElement" );
|
|
|
|
HANDLE hFileStream = INVALID_HANDLE_VALUE;
|
|
HRESULT sc=S_OK;
|
|
|
|
Lock( INFINITE );
|
|
nffChk( CheckReverted() );
|
|
nffDebug(( DEB_INFO | DEB_WRITE,
|
|
"CNtfsStorage::DestroyStreamElement('%ws')\n",
|
|
pwcsName));
|
|
|
|
// Open the handle with DELETE permissions. Write includes Delete.
|
|
//
|
|
nffChk( OpenNtStream( CNtfsStreamName(pwcsName), // ":name:$Data"
|
|
STGM_WRITE | STGM_SHARE_DENY_NONE, // grfMode
|
|
NFFOPEN_SYNC,
|
|
FALSE, // not CreateAPI
|
|
&hFileStream ) );
|
|
|
|
nffChk( CNtfsStream::DeleteStream( &hFileStream ));
|
|
|
|
EH_Err:
|
|
|
|
if( INVALID_HANDLE_VALUE != hFileStream )
|
|
{
|
|
NtClose( hFileStream );
|
|
}
|
|
|
|
Unlock();
|
|
return( sc );
|
|
|
|
} // CNtfsStorage::DestroyStreamElement
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface ::GetFilePath
|
|
// Helper routine for Stat.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::GetFilePath( WCHAR** ppwszPath )
|
|
{
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
ULONG cbFileNameInfo = 2*(MAX_PATH+1)+sizeof(FILE_NAME_INFORMATION);
|
|
PFILE_NAME_INFORMATION pFileNameInfo=NULL;
|
|
WCHAR* pwsz=NULL;
|
|
|
|
HRESULT sc = E_FAIL;
|
|
NTSTATUS status;
|
|
|
|
// Query the handle for the "volume-relative" path. This isn't
|
|
// actually volume-relative (e.g. on a UNC path), it's actually
|
|
// the complete path without the leading "D:" or leading "\".
|
|
|
|
pFileNameInfo = (PFILE_NAME_INFORMATION)CoTaskMemAlloc( cbFileNameInfo );
|
|
nffMem( pFileNameInfo );
|
|
|
|
// Get the file name
|
|
status = NtQueryInformationFile( _hFileMainStream,
|
|
&IoStatusBlock,
|
|
pFileNameInfo,
|
|
cbFileNameInfo,
|
|
FileNameInformation );
|
|
|
|
if( !NT_SUCCESS(status) )
|
|
{
|
|
if( STATUS_BUFFER_OVERFLOW == status )
|
|
nffErr( EH_Err, CO_E_PATHTOOLONG);
|
|
nffErr( EH_Err, NtStatusToScode( status ) );
|
|
}
|
|
|
|
if( 0 == pFileNameInfo->FileNameLength )
|
|
nffErr( EH_Err, STG_E_INVALIDHEADER );
|
|
|
|
|
|
// Allocate and copy this filename for the return.
|
|
//
|
|
|
|
int cbFileName;
|
|
int cchPrefix;
|
|
|
|
cbFileName = pFileNameInfo->FileNameLength + (sizeof(WCHAR) * 3);
|
|
|
|
nffMem( pwsz = (WCHAR*) CoTaskMemAlloc( cbFileName ) );
|
|
|
|
// Start with the Drive Letter or "\" for UNC paths
|
|
if (IsCharAlphaW(_wcDriveLetter))
|
|
{
|
|
pwsz[0] = _wcDriveLetter;
|
|
pwsz[1] = L':';
|
|
pwsz[2] = L'\0';
|
|
cchPrefix = 2;
|
|
}
|
|
else
|
|
{
|
|
nffAssert( L'\\' == _wcDriveLetter );
|
|
pwsz[0] = L'\\';
|
|
pwsz[1] = L'\0';
|
|
cchPrefix = 1;
|
|
}
|
|
|
|
// Copy in the File Path we got from NT. We have a length and it is
|
|
// not necessarily NULL terminated.
|
|
//
|
|
CopyMemory(&pwsz[cchPrefix],
|
|
&pFileNameInfo->FileName,
|
|
pFileNameInfo->FileNameLength );
|
|
|
|
// NULL terminiate the string. Assuming we got the length allocation
|
|
// right, then the NULL just goes at the end.
|
|
//
|
|
pwsz[ cchPrefix + pFileNameInfo->FileNameLength/sizeof(WCHAR) ] = L'\0';
|
|
|
|
// Copy the Out Params And Clear the Temporaries.
|
|
*ppwszPath = pwsz;
|
|
pwsz = NULL;
|
|
|
|
EH_Err:
|
|
if( NULL != pFileNameInfo )
|
|
CoTaskMemFree( pFileNameInfo );
|
|
|
|
if( NULL != pwsz )
|
|
CoTaskMemFree( pwsz );
|
|
|
|
return sc;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CNtfsStorage::IsControlStreamExtant( PFILE_STREAM_INFORMATION pfsiBuf )
|
|
{
|
|
return IsNtStreamExtant( pfsiBuf, CNtfsStreamName( GetControlStreamName() ));
|
|
}
|
|
|
|
|
|
BOOL
|
|
CNtfsStorage::AreAnyNtPropertyStreamsExtant( PFILE_STREAM_INFORMATION pfsiBuf )
|
|
{
|
|
return FindStreamPrefixInFSI( pfsiBuf, L":\005" );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::OpenControlStream
|
|
//
|
|
// This stream is not a property set at the moment. Because we put one of
|
|
// these on every file and it normally doesn't actually contain _any_ data we
|
|
// feel that the overhead of 88 bytes for an empty PPSet was too great.
|
|
//
|
|
// An important role of the control property set is to the be first stream
|
|
// (after the main stream) to be opened. The share mode of the container is
|
|
// is expressed by the share mode of the control property set stream.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::OpenControlStream( BOOL fCreateAPI )
|
|
{
|
|
nffITrace( "CNtfsStorage::OpenControlPropertySet" );
|
|
|
|
HRESULT sc = S_OK;
|
|
HANDLE hFile=INVALID_HANDLE_VALUE;
|
|
NFFCONTROLBITS nffControlBits;
|
|
DWORD cbDidRead=0;
|
|
DWORD grfModeOpen=0;
|
|
|
|
CNtfsStreamName ntfsnameControlStream( GetControlStreamName() );
|
|
|
|
// We shouldn't be called more than once.
|
|
// But we can handle it correctly here.
|
|
//
|
|
if( INVALID_HANDLE_VALUE != _hFileControlStream )
|
|
goto EH_Err;
|
|
|
|
// Add STGM_CREATE flag in the open path (this is an internal only mode
|
|
// that uses NT's OPEN_IF).
|
|
// Don't add it in the Create path because that would mean OverWrite.
|
|
// Don't add it in the ReadOnly case because it would create a stream.
|
|
//
|
|
grfModeOpen = _grfMode;
|
|
if( !fCreateAPI && GrfModeIsWriteable( _grfMode ) )
|
|
grfModeOpen |= STGM_CREATE;
|
|
|
|
sc = OpenNtStream( ntfsnameControlStream,
|
|
grfModeOpen,
|
|
NFFOPEN_SYNC,
|
|
fCreateAPI,
|
|
&hFile );
|
|
|
|
//
|
|
// If we are a ReadOnly Open, then it is OK to not have a
|
|
// control stream.
|
|
//
|
|
if( STG_E_FILENOTFOUND == sc )
|
|
{
|
|
if( !fCreateAPI && !GrfModeIsWriteable( _grfMode ) )
|
|
{
|
|
sc = S_OK;
|
|
goto EH_Err;
|
|
}
|
|
}
|
|
nffChk(sc);
|
|
|
|
// Set buffer to Zero so short reads are OK.
|
|
ZeroMemory(&nffControlBits, sizeof(NFFCONTROLBITS) );
|
|
|
|
if( !ReadFile( hFile, &nffControlBits,
|
|
sizeof(nffControlBits), &cbDidRead, NULL) )
|
|
{
|
|
nffErr(EH_Err, LAST_SCODE);
|
|
}
|
|
|
|
// Currently we only support version 0 control streams.
|
|
// Note: a zero length stream is a version zero stream.
|
|
//
|
|
if( 0 != nffControlBits.sig)
|
|
nffErr(EH_Err, STG_E_INVALIDHEADER);
|
|
|
|
_dwStgStateBits = nffControlBits.bits;
|
|
_clsidStgClass = nffControlBits.clsid;
|
|
_hsmStatus = nffControlBits.hsmStatus;
|
|
|
|
_hFileControlStream = hFile;
|
|
hFile = INVALID_HANDLE_VALUE;
|
|
|
|
EH_Err:
|
|
if(INVALID_HANDLE_VALUE != hFile)
|
|
NtClose(hFile);
|
|
|
|
return( sc );
|
|
|
|
} // CNtfsStorage::OpenControlPropertySet
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::WriteControlStream
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
HRESULT
|
|
CNtfsStorage::WriteControlStream()
|
|
{
|
|
NFFCONTROLBITS nffcb;
|
|
LONG cbToWrite=0;
|
|
ULONG cbDidWrite=0;
|
|
HRESULT sc=S_OK;
|
|
|
|
nffAssert( INVALID_HANDLE_VALUE != _hFileControlStream );
|
|
|
|
nffcb.sig = 0;
|
|
nffcb.hsmStatus = _hsmStatus;
|
|
nffcb.bits = _dwStgStateBits;
|
|
nffcb.clsid = _clsidStgClass;
|
|
|
|
// Try to save some space in the file by not writing the CLSID
|
|
// if it is all zeros.
|
|
//
|
|
if( IsEqualGUID(_clsidStgClass, CLSID_NULL) )
|
|
{
|
|
cbToWrite = FIELD_OFFSET(NFFCONTROLBITS, clsid);
|
|
// Assert that clsid is the last thing in the struct.
|
|
nffAssert( sizeof(NFFCONTROLBITS) == cbToWrite+sizeof(CLSID) );
|
|
}
|
|
else
|
|
cbToWrite = sizeof(NFFCONTROLBITS);
|
|
|
|
if( -1 == SetFilePointer( _hFileControlStream, 0, NULL, FILE_BEGIN ) )
|
|
nffErr( EH_Err, LAST_SCODE );
|
|
|
|
if( !WriteFile( _hFileControlStream,
|
|
&nffcb,
|
|
cbToWrite,
|
|
&cbDidWrite,
|
|
NULL) )
|
|
{
|
|
nffErr(EH_Err, LAST_SCODE);
|
|
}
|
|
|
|
EH_Err:
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::StreamExists
|
|
// The right way to do this is with enumeration.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::StreamExists( const WCHAR *pwcsName )
|
|
{
|
|
nffITrace( "CNtfsStorage::StreamExists" );
|
|
|
|
HANDLE hFileStream = NULL;
|
|
HRESULT sc = S_OK;
|
|
|
|
if( IsContentStream( pwcsName ) )
|
|
{
|
|
// The Contents stream always exists
|
|
sc = S_OK;
|
|
}
|
|
else
|
|
{
|
|
sc = OpenNtStream( CNtfsStreamName(pwcsName),
|
|
STGM_READ_ATTRIBUTE | STGM_SHARE_DENY_NONE,
|
|
NFFOPEN_NORMAL,
|
|
FALSE, // Not a create API.
|
|
&hFileStream);
|
|
if( S_OK == sc )
|
|
{
|
|
NtClose( hFileStream );
|
|
}
|
|
else
|
|
sc = S_FALSE;
|
|
}
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::SetAllStreamsTimes
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::SetAllStreamsTimes(
|
|
const FILETIME *pctime,
|
|
const FILETIME *patime,
|
|
const FILETIME *pmtime)
|
|
{
|
|
HRESULT sc=S_OK;
|
|
HRESULT scAccum=S_OK;
|
|
CNtfsStream *pstm = NULL;
|
|
|
|
nffDebug(( DEB_INFO | DEB_STATCTRL | DEB_ITRACE,
|
|
"CNtfsStorage::SetAllStreamsTimes()\n" ));
|
|
|
|
// Set the time on the main control stream.
|
|
|
|
if( INVALID_HANDLE_VALUE != _hFileControlStream )
|
|
{
|
|
sc = CNtfsStream::SetFileHandleTime( _hFileControlStream,
|
|
pctime, patime, pmtime );
|
|
if( S_OK != sc )
|
|
scAccum = sc;
|
|
}
|
|
|
|
// We don't set time stamps on _hFileMainStream and _hFileOplock
|
|
// Because they are readonly. (and _hFileOplock is not always open)
|
|
|
|
// Now set the time on any CNtfsStream objects we have open.
|
|
|
|
if( NULL != _pstmOpenList ) // Skip the head sentinal;
|
|
pstm = _pstmOpenList->_pnffstmNext;
|
|
|
|
while(NULL != pstm)
|
|
{
|
|
sc = pstm->SetStreamTime( pctime, patime, pmtime );
|
|
if( S_OK != sc )
|
|
scAccum = sc;
|
|
|
|
pstm = pstm->_pnffstmNext;
|
|
}
|
|
|
|
return scAccum;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::InitCNtfsStream
|
|
//
|
|
// Create and Init an CNtfsStream Object.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::InitCNtfsStream(
|
|
CNtfsStream *pstm,
|
|
HANDLE hStream,
|
|
DWORD grfMode,
|
|
const WCHAR * pwcsName )
|
|
{
|
|
nffITrace("CNtfsStorage::InitCNtfsStream");
|
|
HRESULT sc=S_OK;
|
|
|
|
// Attach the File Stream to the Stream Object
|
|
nffChk( pstm->Init( hStream, grfMode, pwcsName, _pstmOpenList ) );
|
|
|
|
sc = S_OK;
|
|
|
|
EH_Err:
|
|
|
|
return sc;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::ModeToNtFlags
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::ModeToNtFlags(DWORD grfMode,
|
|
DWORD dwFlags,
|
|
BOOL fCreateAPI,
|
|
ACCESS_MASK *pam,
|
|
ULONG *pulAttributes,
|
|
ULONG *pulSharing,
|
|
ULONG *pulCreateDisposition,
|
|
ULONG *pulCreateOptions)
|
|
{
|
|
SCODE sc=S_OK;
|
|
|
|
nffDebug((DEB_ITRACE, "In ModeToNtFlags("
|
|
"%lX, %d %d, %p, %p, %p, %p, %p)\n",
|
|
grfMode, dwFlags, fCreateAPI, pam,
|
|
pulAttributes, pulSharing,
|
|
pulCreateDisposition, pulCreateOptions));
|
|
|
|
*pam = 0;
|
|
*pulAttributes = 0;
|
|
*pulSharing = 0;
|
|
*pulCreateDisposition = 0;
|
|
*pulCreateOptions = 0;
|
|
|
|
switch(grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE | STGM_READ_ATTRIBUTE))
|
|
{
|
|
case STGM_READ:
|
|
*pam = FILE_GENERIC_READ;
|
|
break;
|
|
|
|
case STGM_WRITE:
|
|
*pam = FILE_GENERIC_WRITE;
|
|
if( 0 == (NFFOPEN_CONTENTSTREAM & dwFlags) )
|
|
*pam |= DELETE;
|
|
break;
|
|
|
|
case STGM_READWRITE:
|
|
*pam = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
|
|
if( 0 == (NFFOPEN_CONTENTSTREAM & dwFlags) )
|
|
*pam |= DELETE;
|
|
|
|
break;
|
|
|
|
case STGM_READ_ATTRIBUTE:
|
|
*pam = FILE_READ_ATTRIBUTES;
|
|
break;
|
|
|
|
default:
|
|
nffErr(EH_Err, STG_E_INVALIDFLAG);
|
|
break;
|
|
}
|
|
*pam |= SYNCHRONIZE;
|
|
|
|
|
|
switch(grfMode & (STGM_SHARE_DENY_NONE | STGM_SHARE_DENY_READ |
|
|
STGM_SHARE_DENY_WRITE | STGM_SHARE_EXCLUSIVE))
|
|
{
|
|
case STGM_SHARE_DENY_READ:
|
|
*pulSharing = FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
|
break;
|
|
case STGM_SHARE_DENY_WRITE:
|
|
*pulSharing = FILE_SHARE_READ;
|
|
break;
|
|
case STGM_SHARE_EXCLUSIVE:
|
|
*pulSharing = 0;
|
|
break;
|
|
case STGM_SHARE_DENY_NONE:
|
|
case 0:
|
|
*pulSharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
|
break;
|
|
|
|
default:
|
|
nffErr(EH_Err, STG_E_INVALIDFLAG);
|
|
break;
|
|
}
|
|
|
|
switch(grfMode & (STGM_CREATE | STGM_FAILIFTHERE | STGM_CONVERT))
|
|
{
|
|
case STGM_CREATE:
|
|
if (fCreateAPI)
|
|
*pulCreateDisposition = FILE_OVERWRITE_IF;
|
|
else
|
|
*pulCreateDisposition = FILE_OPEN_IF; // Illegal but used internaly
|
|
break;
|
|
case STGM_FAILIFTHERE: // this is a 0 flag
|
|
if (fCreateAPI)
|
|
*pulCreateDisposition = FILE_CREATE;
|
|
else
|
|
*pulCreateDisposition = FILE_OPEN;
|
|
break;
|
|
|
|
case STGM_CONVERT:
|
|
nffDebug(( DEB_ERROR, "STGM_CONVERT illegal flag to NFF" ));
|
|
nffErr(EH_Err, STG_E_INVALIDFLAG);
|
|
break;
|
|
|
|
default:
|
|
nffErr(EH_Err, STG_E_INVALIDFLAG);
|
|
break;
|
|
}
|
|
|
|
//if( NFFOPEN_SYNC & dwFlags )
|
|
*pulCreateOptions |= FILE_SYNCHRONOUS_IO_NONALERT;
|
|
|
|
*pulAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
sc = S_OK;
|
|
|
|
nffDebug((DEB_ITRACE, "Out ModeToNtFlags\n"));
|
|
EH_Err:
|
|
return sc;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::OpenNtStream
|
|
// Used by NFF to open a named stream, relative to an exiting main
|
|
// stream handle.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::OpenNtStream( const CNtfsStreamName& nsnName,
|
|
DWORD grfMode,
|
|
DWORD dwFlags,
|
|
BOOL fCreateAPI,
|
|
HANDLE* phFile )
|
|
{
|
|
UNICODE_STRING usNtfsStreamName;
|
|
|
|
RtlInitUnicodeString(&usNtfsStreamName, (const WCHAR*)nsnName);
|
|
|
|
return OpenNtFileHandle( usNtfsStreamName,
|
|
_hFileMainStream,
|
|
grfMode,
|
|
dwFlags,
|
|
fCreateAPI,
|
|
phFile);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNtfsStorage Non-Interface::OpenNtFileHandle
|
|
// Common sub-code for OpenNtFile and OpenNtStream
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsStorage::OpenNtFileHandle( const UNICODE_STRING& usNtfsStreamName,
|
|
HANDLE hParent,
|
|
DWORD grfMode,
|
|
DWORD dwFlags,
|
|
BOOL fCreateAPI,
|
|
HANDLE *phFile)
|
|
{
|
|
OBJECT_ATTRIBUTES object_attributes;
|
|
IO_STATUS_BLOCK iostatusblock;
|
|
HANDLE hStream;
|
|
ACCESS_MASK accessmask=0;
|
|
ULONG ulAttrs=0;
|
|
ULONG ulSharing=0;
|
|
ULONG ulCreateDisp=0;
|
|
ULONG ulCreateOpt = 0;
|
|
|
|
|
|
SCODE sc = S_OK;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
nffDebug(( DEB_ITRACE | DEB_OPENS,
|
|
"OpenNtStream(%ws, %p, %lX, %d, %p)\n",
|
|
usNtfsStreamName.Buffer,
|
|
hParent, grfMode,
|
|
fCreateAPI, phFile ));
|
|
|
|
InitializeObjectAttributes(&object_attributes,
|
|
(PUNICODE_STRING) &usNtfsStreamName, // cast away const
|
|
OBJ_CASE_INSENSITIVE,
|
|
hParent,
|
|
NULL);
|
|
|
|
nffChk( ModeToNtFlags( grfMode, dwFlags, fCreateAPI,
|
|
&accessmask, &ulAttrs, &ulSharing,
|
|
&ulCreateDisp, &ulCreateOpt ) );
|
|
|
|
status = NtCreateFile( &hStream, accessmask,
|
|
&object_attributes, &iostatusblock,
|
|
NULL,
|
|
ulAttrs, ulSharing,
|
|
ulCreateDisp, ulCreateOpt,
|
|
NULL, 0);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*phFile = hStream;
|
|
sc = S_OK;
|
|
}
|
|
else
|
|
sc = NtStatusToScode(status);
|
|
|
|
EH_Err:
|
|
nffDebug(( DEB_ITRACE | DEB_OPENS,
|
|
"OpenNtFileHandle returns hFile=%x sc=%x status=%x\n",
|
|
*phFile, sc, status));
|
|
|
|
return sc;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CNFFTreeMutex
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNFFTreeMutex IUnknown::QueryInterface
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNFFTreeMutex::QueryInterface(
|
|
REFIID riid,
|
|
void ** ppvObject
|
|
)
|
|
{
|
|
//nffITrace( "CNFFTreeMutex::QueryInterface");
|
|
HRESULT sc = S_OK;
|
|
|
|
// ----------
|
|
// Validation
|
|
// ----------
|
|
|
|
VDATEREADPTRIN( &riid, IID );
|
|
VDATEPTROUT( ppvObject, void* );
|
|
|
|
*ppvObject = NULL;
|
|
|
|
// -----
|
|
// Query
|
|
// -----
|
|
|
|
if( IID_IUnknown == riid || IID_IBlockingLock == riid )
|
|
{
|
|
*ppvObject = static_cast<IBlockingLock*>(this);
|
|
AddRef();
|
|
}
|
|
else
|
|
sc = E_NOINTERFACE;
|
|
|
|
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNFFTreeMutex IUnknown::AddRef
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
ULONG STDMETHODCALLTYPE
|
|
CNFFTreeMutex::AddRef(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
lRet = InterlockedIncrement( &_cRefs );
|
|
|
|
nffDebug(( DEB_REFCOUNT, "CNFFTreeMutex::AddRef(this==%x) == %d\n",
|
|
this, lRet));
|
|
|
|
return( lRet );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNFFTreeMutex IUnknown::Release
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
ULONG STDMETHODCALLTYPE
|
|
CNFFTreeMutex::Release(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
lRet = InterlockedDecrement( &_cRefs );
|
|
|
|
if( 0 == lRet )
|
|
delete this;
|
|
|
|
nffDebug((DEB_REFCOUNT, "CNFFTreeMutex::Release(this=%x) == %d\n",
|
|
this, lRet));
|
|
|
|
return( lRet );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNFFTreeMutex IBlockingLock::Lock
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline HRESULT
|
|
CNFFTreeMutex::Lock( DWORD dwTimeout )
|
|
{
|
|
|
|
// Don't trace at this level. The noice is too great!
|
|
// nffCDbgTrace dbg(DEB_ITRACE, "CNFFTreeMutex::Lock");
|
|
|
|
nffAssert (_fInitialized == TRUE);
|
|
nffAssert( INFINITE == dwTimeout );
|
|
if( INFINITE != dwTimeout )
|
|
return( E_NOTIMPL );
|
|
|
|
EnterCriticalSection( &_cs );
|
|
nffDebug(( DEB_ITRACE, "Tree Locked. cnt=%d\n", _cs.RecursionCount ));
|
|
return( S_OK );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CNFFTreeMutex IBlockingLock::Unlock
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline HRESULT
|
|
CNFFTreeMutex::Unlock()
|
|
{
|
|
// Don't trace at this level. The noice is too great!
|
|
//nffCDbgTrace dbg(DEB_ITRACE, "CNFFTreeMutex::Unlock");
|
|
nffAssert (_fInitialized == TRUE);
|
|
LeaveCriticalSection( &_cs );
|
|
nffDebug(( DEB_ITRACE, "Tree Unlocked. cnt=%d\n", _cs.RecursionCount ));
|
|
return( S_OK );
|
|
}
|
|
|
|
|
|
#if DBG
|
|
LONG
|
|
CNFFTreeMutex::GetLockCount()
|
|
{
|
|
return( _cs.LockCount + 1 );
|
|
}
|
|
#endif // #if DBG
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CNtfsEnumSTATSTG
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::QueryInterface (IUnknown)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
HRESULT
|
|
CNtfsEnumSTATSTG::QueryInterface(
|
|
REFIID riid,
|
|
void ** ppvObject
|
|
)
|
|
{
|
|
nffXTrace( "CNtfsEnumSTATSTG::QueryInterface" );
|
|
HRESULT sc = S_OK;
|
|
|
|
// ----------
|
|
// Validation
|
|
// ----------
|
|
|
|
VDATEREADPTRIN( &riid, IID );
|
|
VDATEPTROUT( ppvObject, void* );
|
|
|
|
// -----
|
|
// Query
|
|
// -----
|
|
|
|
if( IID_IUnknown == riid || IID_IEnumSTATSTG == riid )
|
|
{
|
|
*ppvObject = static_cast<IEnumSTATSTG*>(this);
|
|
AddRef();
|
|
}
|
|
else
|
|
{
|
|
*ppvObject = NULL;
|
|
sc = E_NOINTERFACE;
|
|
}
|
|
|
|
return( sc );
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::AddRef (IUnknown)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
CNtfsEnumSTATSTG::AddRef(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
lRet = InterlockedIncrement( &_cRefs );
|
|
|
|
nffDebug(( DEB_REFCOUNT, "CNtfsEnumSTATSTG::AddRef(this==%x) == %d\n",
|
|
this, lRet));
|
|
|
|
return( lRet );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::Release (IUnknown)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
CNtfsEnumSTATSTG::Release(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
lRet = InterlockedDecrement( &_cRefs );
|
|
|
|
nffAssert( 0 <= lRet );
|
|
|
|
if( 0 == lRet )
|
|
delete this;
|
|
|
|
nffDebug((DEB_REFCOUNT, "CNtfsEnumSTATSTG::Release(this=%x) == %d\n",
|
|
this, lRet));
|
|
|
|
|
|
return( lRet );
|
|
|
|
}
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::Next (IEnumSTATSTG)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsEnumSTATSTG::Next(ULONG celt, STATSTG *prgstatstg, ULONG *pcFetched)
|
|
{
|
|
nffXTrace( "CNtfsEnumSTATSTG::Next" );
|
|
HRESULT sc = S_OK;
|
|
|
|
|
|
NFF_VALIDATE( Next(celt, prgstatstg, pcFetched ) );
|
|
|
|
if( NULL != pcFetched )
|
|
*pcFetched = 0;
|
|
|
|
// Compatibility requires we return S_OK when 0 elements are requested.
|
|
if( 0 == celt )
|
|
return S_OK;
|
|
|
|
_pBlockingLock->Lock( INFINITE );
|
|
|
|
sc = _pstatstgarray->NextAt( _istatNextToRead, prgstatstg, &celt );
|
|
if( FAILED(sc) ) goto Exit;
|
|
|
|
_istatNextToRead += celt;
|
|
|
|
if( NULL != pcFetched )
|
|
*pcFetched = celt;
|
|
|
|
Exit:
|
|
|
|
_pBlockingLock->Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::Skip (IEnumSTATSTG)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsEnumSTATSTG::Skip(ULONG celt)
|
|
{
|
|
nffXTrace( "CNtfsEnumSTATSTG::Skip" );
|
|
HRESULT sc = S_OK;
|
|
|
|
|
|
NFF_VALIDATE( Skip( celt ) );
|
|
|
|
_pBlockingLock->Lock( INFINITE );
|
|
|
|
// Advance the index, but not past the end of the array
|
|
if( _istatNextToRead + celt > _pstatstgarray->GetCount() )
|
|
{
|
|
_istatNextToRead = _pstatstgarray->GetCount();
|
|
sc = S_FALSE;
|
|
}
|
|
else
|
|
_istatNextToRead += celt;
|
|
|
|
_pBlockingLock->Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::Reset (IEnumSTATSTG)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsEnumSTATSTG::Reset()
|
|
{
|
|
nffXTrace( "CNtfsEnumSTATSTG::Reset" );
|
|
_pBlockingLock->Lock( INFINITE );
|
|
_istatNextToRead = 0;
|
|
_pBlockingLock->Unlock();
|
|
return( S_OK );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::Clone (IEnumSTATSTG)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsEnumSTATSTG::Clone(IEnumSTATSTG **ppenum)
|
|
{
|
|
nffXTrace( "CNtfsEnumSTATSTG::Clone" );
|
|
HRESULT sc = S_OK;
|
|
|
|
NFF_VALIDATE( Clone( ppenum ) );
|
|
|
|
_pBlockingLock->Lock( INFINITE );
|
|
|
|
CNtfsEnumSTATSTG *pNtfsEnumSTATSTG = new CNtfsEnumSTATSTG(*this);
|
|
if( NULL == pNtfsEnumSTATSTG )
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto Exit;
|
|
}
|
|
|
|
*ppenum = static_cast<IEnumSTATSTG*>(pNtfsEnumSTATSTG);
|
|
pNtfsEnumSTATSTG = NULL;
|
|
|
|
Exit:
|
|
|
|
_pBlockingLock->Unlock();
|
|
|
|
if( NULL != pNtfsEnumSTATSTG )
|
|
delete pNtfsEnumSTATSTG;
|
|
|
|
return( sc );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsEnumSTATSTG::ReadFileStreamInfo (private)
|
|
//
|
|
// This method reads the FileStreamInformation from the ContentStream. It
|
|
// puts this buffer into a member pointer, for use in Next, etc.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsSTATSTGArray::ReadFileStreamInfo( HANDLE hFile )
|
|
{
|
|
nffITrace( "CNtfsStorage::ReadFileStreamInfo" );
|
|
|
|
PFILE_STREAM_INFORMATION pStreamInfo=NULL;
|
|
PFILE_STREAM_INFORMATION pFSI=NULL;
|
|
ULONG cbBuffer=0;
|
|
ULONG cStreams=0;
|
|
HRESULT sc=S_OK;
|
|
|
|
sc = EnumNtStreams( hFile, &pStreamInfo, &cbBuffer, TRUE );
|
|
if( FAILED(sc) )
|
|
return sc;
|
|
|
|
for(pFSI=pStreamInfo ; NULL != pFSI; pFSI=NextFSI( pFSI ) )
|
|
cStreams++;
|
|
|
|
_pFileStreamInformation = pStreamInfo;
|
|
_cFileStreamInformation = cStreams;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsSTATSTGArray::Init (Internal method)
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsSTATSTGArray::Init( HANDLE hFile )
|
|
{
|
|
nffITrace( "CNtfsSTATSTGArray::Init" );
|
|
HRESULT hr = S_OK;
|
|
|
|
DfpAssert( NULL != _pBlockingLock );
|
|
_pBlockingLock->Lock( INFINITE );
|
|
|
|
if( NULL != _pFileStreamInformation )
|
|
{
|
|
CoTaskMemFree( _pFileStreamInformation );
|
|
_pFileStreamInformation = NULL;
|
|
_cFileStreamInformation = 0;
|
|
}
|
|
|
|
// Snapshot the stream information in _pFileStreamInformation
|
|
hr = ReadFileStreamInfo( hFile );
|
|
if( FAILED(hr) ) goto Exit;
|
|
|
|
Exit:
|
|
|
|
_pBlockingLock->Unlock();
|
|
return( hr );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Method: CNtfsSTATSTGArray::NextAt
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CNtfsSTATSTGArray::NextAt( ULONG iNext, STATSTG *prgstatstg, ULONG *pcFetched )
|
|
{
|
|
nffITrace( "CNtfsSTATSTGArray::NextAt" );
|
|
HRESULT sc=S_OK;
|
|
ULONG cFetched=0;
|
|
ULONG cVisibleDataStreams=0;
|
|
PFILE_STREAM_INFORMATION pFSI=NULL;
|
|
const WCHAR* pwName=NULL;
|
|
ULONG cbAlloc=0, cchLength=0;
|
|
|
|
_pBlockingLock->Lock( INFINITE );
|
|
|
|
// If there is nothing to do skip out early.
|
|
if( iNext >= _cFileStreamInformation )
|
|
{
|
|
sc = S_FALSE;
|
|
*pcFetched = 0;
|
|
goto EH_Err;
|
|
}
|
|
|
|
// Loop through the cached stream info in _pFileStreamInformation
|
|
|
|
for( pFSI=_pFileStreamInformation; NULL != pFSI; pFSI = NextFSI(pFSI) )
|
|
{
|
|
if( cFetched >= *pcFetched )
|
|
break; // We are done.
|
|
|
|
// We only handle data streams
|
|
//
|
|
if( !IsDataStream( pFSI ) )
|
|
continue;
|
|
|
|
// We hide some of the streams (like the Control Stream)
|
|
//
|
|
if( IsHiddenStream( pFSI ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We are counting up to the requested streams.
|
|
//
|
|
if( iNext > cVisibleDataStreams++)
|
|
continue;
|
|
|
|
// Now lets unmangle the name no memory is allocated yet we just
|
|
// move the pointer past the first ':' and return a shortened length.
|
|
// Must be a $DATA Stream. Also invent "CONTENTS" if necessary.
|
|
// pwName is not null terminated.
|
|
//
|
|
GetNtfsUnmangledNameInfo(pFSI, &pwName, &cchLength);
|
|
|
|
// Yes, this is a data stream that we need to return.
|
|
|
|
// Allocate a buffer for the stream name in the statstg. If this is
|
|
// the unnamed stream, then we'll return it to the caller with the
|
|
// name "Contents".
|
|
|
|
cbAlloc = (cchLength + 1) * sizeof(WCHAR);
|
|
|
|
// Allocate memory, copy and null terminate the string from the FSI.
|
|
//
|
|
nffMem( prgstatstg[cFetched].pwcsName = (WCHAR*) CoTaskMemAlloc( cbAlloc ) );
|
|
memcpy( prgstatstg[cFetched].pwcsName, pwName, cchLength*sizeof(WCHAR) );
|
|
prgstatstg[cFetched].pwcsName[ cchLength ] = L'\0';
|
|
|
|
|
|
// But Wait !!!
|
|
// If this stream is really a non-simple property set, it's actually
|
|
// a docfile, so let's return it as a STGTY_STORAGE, without the
|
|
// name munging.
|
|
|
|
if( IsDocfileStream( prgstatstg[cFetched].pwcsName ))
|
|
{
|
|
StringCbCopy( prgstatstg[cFetched].pwcsName,
|
|
cbAlloc,
|
|
UnmangleDocfileStreamName( prgstatstg[cFetched].pwcsName ));
|
|
prgstatstg[cFetched].type = STGTY_STORAGE;
|
|
}
|
|
else
|
|
prgstatstg[cFetched].type = STGTY_STREAM;
|
|
|
|
// Fill in the rest of the stream information.
|
|
prgstatstg[cFetched].cbSize.QuadPart = static_cast<ULONGLONG>(pFSI->StreamSize.QuadPart);
|
|
|
|
// streams don't support timestamps yet
|
|
prgstatstg[cFetched].mtime = prgstatstg[cFetched].ctime = prgstatstg[cFetched].atime = CFILETIME(0);
|
|
prgstatstg[cFetched].grfMode = 0;
|
|
prgstatstg[cFetched].grfLocksSupported = 0; // no locks supported
|
|
prgstatstg[cFetched].grfStateBits = 0;
|
|
prgstatstg[cFetched].clsid = CLSID_NULL;
|
|
prgstatstg[cFetched].reserved = 0;
|
|
|
|
// Advance the index of the next index the caller wants retrieved
|
|
iNext++;
|
|
|
|
// Advance the count of entries read
|
|
cFetched++;
|
|
|
|
}
|
|
|
|
// ----
|
|
// Exit
|
|
// ----
|
|
|
|
if( cFetched == *pcFetched )
|
|
sc = S_OK;
|
|
else
|
|
sc = S_FALSE;
|
|
|
|
*pcFetched = cFetched;
|
|
|
|
EH_Err:
|
|
|
|
_pBlockingLock->Unlock();
|
|
return( sc );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Routine GetDriveLetter
|
|
// Return the drive letter from a path, or '\' for UNC paths.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
WCHAR GetDriveLetter (WCHAR const *pwcsName)
|
|
{
|
|
nffITrace( "GetDriveLetter" );
|
|
|
|
if( pwcsName == NULL )
|
|
return( L'\0' );
|
|
|
|
if( pwcsName[0] != L'\0' )
|
|
{
|
|
if( 0 == dfwcsnicmp( pwcsName, L"\\\\?\\", 4 )
|
|
&&
|
|
pwcsName[4] != L'\0' )
|
|
{
|
|
if( pwcsName[5] == L':' )
|
|
return( pwcsName[4] );
|
|
|
|
else if( 0 == dfwcsnicmp( pwcsName, L"\\\\?\\UNC\\", -1 ))
|
|
return( L'\\' );
|
|
|
|
}
|
|
|
|
if( pwcsName[1] == L':'
|
|
||
|
|
pwcsName[0] == L'\\' && pwcsName[1] == L'\\' )
|
|
{
|
|
return( pwcsName[0] );
|
|
}
|
|
|
|
}
|
|
|
|
// No drive letter in pathname, get current drive instead
|
|
|
|
WCHAR wcsPath[MAX_PATH];
|
|
NTSTATUS nts = RtlGetCurrentDirectory_U (MAX_PATH*sizeof(WCHAR),wcsPath);
|
|
if (NT_SUCCESS(nts))
|
|
return( wcsPath[0] );
|
|
|
|
|
|
return( L'\0' );
|
|
};
|
|
|
|
|
|
|
|
|
|
#if DBG
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsStorage::UseNTFS4Streams( BOOL fUseNTFS4Streams )
|
|
{
|
|
return( E_NOTIMPL );
|
|
}
|
|
#endif // #if DBG
|
|
|
|
#if DBG
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsStorage::GetFormatVersion(WORD *pw)
|
|
{
|
|
return( E_NOTIMPL );
|
|
}
|
|
#endif // #if DBG
|
|
|
|
#if DBG
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsStorage::SimulateLowMemory( BOOL fSimulate )
|
|
{
|
|
return( E_NOTIMPL );
|
|
}
|
|
#endif // #if DBG
|
|
|
|
#if DBG
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsStorage::GetLockCount()
|
|
{
|
|
return( _pTreeMutex->GetLockCount() );
|
|
}
|
|
#endif // #if DBG
|
|
|
|
#if DBG
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNtfsStorage::IsDirty()
|
|
{
|
|
return( E_NOTIMPL );
|
|
}
|
|
#endif // #if DBG
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: EnumNtStreams
|
|
//
|
|
// Synopsis: Enumerate NT stream information
|
|
//
|
|
// Arguments: [h] -- Handle to rename
|
|
// [ppfsi] -- buffer to hold stream information
|
|
// [pulBufferSize] -- size of output buffer
|
|
// [fGrow] -- FALSE for fixed size buffer
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Notes :
|
|
//
|
|
// History: 1-Apr-98 HenryLee Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT EnumNtStreams (HANDLE h,
|
|
FILE_STREAM_INFORMATION ** ppfsi,
|
|
ULONG *pulBufferSize,
|
|
BOOL fGrow)
|
|
{
|
|
HRESULT sc = S_OK;
|
|
NTSTATUS nts;
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
nffAssert (pulBufferSize != NULL);
|
|
ULONG ulStreamInfoSize = 2048;
|
|
FILE_STREAM_INFORMATION *pfsi;
|
|
|
|
*ppfsi = NULL;
|
|
*pulBufferSize = 0;
|
|
|
|
do
|
|
{
|
|
nffMem (pfsi = (FILE_STREAM_INFORMATION*) new BYTE[ulStreamInfoSize]);
|
|
|
|
nts = NtQueryInformationFile(h,
|
|
&iosb,
|
|
(VOID*) pfsi,
|
|
ulStreamInfoSize - sizeof (L'\0'),
|
|
FileStreamInformation
|
|
);
|
|
|
|
if ( !NT_SUCCESS(nts) )
|
|
{
|
|
// We failed the call. Free up the previous buffer and set up
|
|
// for another pass with a buffer twice as large
|
|
|
|
delete [] (BYTE *)pfsi;
|
|
pfsi = NULL;
|
|
ulStreamInfoSize *= 2;
|
|
}
|
|
if (fGrow == FALSE)
|
|
break;
|
|
|
|
} while (nts == STATUS_BUFFER_OVERFLOW || nts == STATUS_BUFFER_TOO_SMALL);
|
|
|
|
if (NT_SUCCESS(nts))
|
|
{
|
|
if (iosb.Information == 0) // no data returned
|
|
{
|
|
delete [] (BYTE *) pfsi;
|
|
*ppfsi = NULL;
|
|
*pulBufferSize = 0;
|
|
}
|
|
else
|
|
{
|
|
*ppfsi = pfsi;
|
|
*pulBufferSize = iosb.Status;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sc = NtStatusToScode(nts);
|
|
}
|
|
|
|
EH_Err:
|
|
return sc;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: FindStreamInFSI
|
|
//
|
|
// Synopsis: Find a Stream name in a provided Enumeration Buffer
|
|
//
|
|
// Arguments: [ppfsi] -- buffer that holds the stream enumeration.
|
|
// [pwszNtStreamName] -- Name to look for, in :*:$DATA form.
|
|
//
|
|
// Returns: Pointer to the found element, or NULL otherwise
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
const FILE_STREAM_INFORMATION *
|
|
FindStreamInFSI( IN const FILE_STREAM_INFORMATION *pfsi,
|
|
IN const WCHAR *pwszNtStreamName // In :*:$data format
|
|
)
|
|
{
|
|
ULONG cchLength = (ULONG)wcslen(pwszNtStreamName);
|
|
|
|
for( ; NULL != pfsi; pfsi= NextFSI(pfsi) )
|
|
{
|
|
if( cchLength*sizeof(WCHAR) != pfsi->StreamNameLength )
|
|
continue;
|
|
|
|
if( 0 == dfwcsnicmp( pwszNtStreamName, pfsi->StreamName, cchLength ))
|
|
break;
|
|
}
|
|
|
|
return( pfsi );
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: IsStreamPrefixInFSI
|
|
//
|
|
// Synopsis: Find a Stream with the given prefix in a provided
|
|
// Enumeration Buffer
|
|
//
|
|
// Arguments: [ppfsi] -- buffer that holds the stream enumeration.
|
|
// [pwszPrefix] -- Prefix to find.
|
|
//
|
|
// Returns: TRUE if it finds it, FALSE otherwise.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL
|
|
FindStreamPrefixInFSI( IN const FILE_STREAM_INFORMATION *pfsi,
|
|
IN const WCHAR *pwszPrefix
|
|
)
|
|
{
|
|
ULONG cchLength = (ULONG)wcslen(pwszPrefix);
|
|
|
|
for( ; NULL != pfsi; pfsi= NextFSI(pfsi) )
|
|
{
|
|
if( cchLength*sizeof(WCHAR) > pfsi->StreamNameLength )
|
|
continue;
|
|
|
|
if( 0 == dfwcsnicmp( pwszPrefix, pfsi->StreamName, cchLength ))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|