Leaked source code of windows server 2003
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

//+============================================================================
//
// 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;
}