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.
2893 lines
73 KiB
2893 lines
73 KiB
/*
|
|
* D A V M B . C P P
|
|
*
|
|
* DAV metabase
|
|
*
|
|
* Copyright 1986-1998 Microsoft Corporation, All Rights Reserved
|
|
*/
|
|
|
|
#include <_davprs.h>
|
|
#include <content.h> // IContentTypeMap
|
|
#include <custerr.h> // ICustomErrorMap
|
|
#include <scrptmps.h> // IScriptMap
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CNotifSink
|
|
//
|
|
// Metabase change notification advise sink. Provides IMSAdminBaseSink
|
|
// interface to the real metabase so that we can be informed of
|
|
// all changes to it.
|
|
//
|
|
class CNotifSink : public EXO, public IMSAdminBaseSink
|
|
{
|
|
//
|
|
// Shutdown notification event that we signal when we are
|
|
// done -- i.e. when we get destroyed.
|
|
//
|
|
CEvent& m_evtShutdown;
|
|
|
|
HRESULT STDMETHODCALLTYPE SinkNotify(
|
|
/* [in] */ DWORD dwMDNumElements,
|
|
/* [size_is][in] */ MD_CHANGE_OBJECT_W __RPC_FAR pcoChangeList[ ]);
|
|
|
|
HRESULT STDMETHODCALLTYPE ShutdownNotify()
|
|
{
|
|
MBTrace ("MB: CNotifSink: shutdown\n");
|
|
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
|
}
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
CNotifSink& operator=(const CNotifSink&);
|
|
CNotifSink(const CNotifSink&);
|
|
|
|
public:
|
|
EXO_INCLASS_DECL(CNotifSink);
|
|
|
|
// CREATORS
|
|
//
|
|
CNotifSink(CEvent& evtShutdown) :
|
|
m_evtShutdown(evtShutdown)
|
|
{
|
|
}
|
|
|
|
~CNotifSink()
|
|
{
|
|
//
|
|
// We cannot process any more notifications at this point.
|
|
// Signal our shutdown event.
|
|
//
|
|
m_evtShutdown.Set();
|
|
}
|
|
|
|
// Wrapper for all the work that needs to be done on the notification
|
|
//
|
|
static VOID OnNotify( DWORD cCO,
|
|
MD_CHANGE_OBJECT_W rgCO[] );
|
|
};
|
|
|
|
BEGIN_INTERFACE_TABLE(CNotifSink)
|
|
INTERFACE_MAP(CNotifSink, IMSAdminBaseSink)
|
|
END_INTERFACE_TABLE(CNotifSink);
|
|
EXO_GLOBAL_DATA_DECL(CNotifSink, EXO);
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrAdviseSink()
|
|
//
|
|
HRESULT
|
|
HrAdviseSink( IMSAdminBase& msAdminBase,
|
|
IMSAdminBaseSink * pMSAdminBaseSink,
|
|
DWORD * pdwCookie )
|
|
{
|
|
auto_ref_ptr<IConnectionPoint> pcp;
|
|
auto_ref_ptr<IConnectionPointContainer> pcpc;
|
|
SCODE sc = S_OK;
|
|
|
|
Assert( !IsBadReadPtr(&msAdminBase, sizeof(IMSAdminBase)) );
|
|
Assert( !IsBadWritePtr(pMSAdminBaseSink, sizeof(IMSAdminBaseSink)) );
|
|
|
|
// First see if a connection point container is supported
|
|
//
|
|
sc = msAdminBase.QueryInterface (IID_IConnectionPointContainer,
|
|
reinterpret_cast<LPVOID *>(pcpc.load()));
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace( "HrAdviseSink() - QI to IConnectionPointContainer() failed 0x%08lX\n", sc );
|
|
goto ret;
|
|
}
|
|
|
|
// Find the required connection point
|
|
//
|
|
sc = pcpc->FindConnectionPoint (IID_IMSAdminBaseSink, pcp.load());
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace( "HrAdviseSink() - FindConnectionPoint() failed 0x%08lX\n", sc );
|
|
goto ret;
|
|
}
|
|
|
|
// Advise on the sink
|
|
//
|
|
sc = pcp->Advise (pMSAdminBaseSink, pdwCookie);
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace( "HrAdviseSink() - Advise() failed 0x%08lX\n", sc );
|
|
goto ret;
|
|
}
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// UnadviseSink()
|
|
//
|
|
VOID
|
|
UnadviseSink( IMSAdminBase& msAdminBase,
|
|
DWORD dwCookie )
|
|
{
|
|
auto_ref_ptr<IConnectionPoint> pcp;
|
|
auto_ref_ptr<IConnectionPointContainer> pcpc;
|
|
SCODE sc = S_OK;
|
|
|
|
Assert( !IsBadReadPtr(&msAdminBase, sizeof(IMSAdminBase)) );
|
|
Assert( dwCookie );
|
|
|
|
// First see if a connection point container is supported
|
|
//
|
|
sc = msAdminBase.QueryInterface (IID_IConnectionPointContainer,
|
|
reinterpret_cast<LPVOID *>(pcpc.load()));
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace( "UnadviseSink() - QI to IConnectionPointContainer() failed 0x%08lX\n", sc );
|
|
goto ret;
|
|
}
|
|
|
|
// Find the required connection point
|
|
//
|
|
sc = pcpc->FindConnectionPoint (IID_IMSAdminBaseSink, pcp.load());
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace( "UnadviseSink() - FindConnectionPoint() failed 0x%08lX\n", sc );
|
|
goto ret;
|
|
}
|
|
|
|
// Unadvise
|
|
//
|
|
sc = pcp->Unadvise (dwCookie);
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace( "UnadviseSink() - Unadvise() failed 0x%08lX\n", sc );
|
|
goto ret;
|
|
}
|
|
|
|
ret:
|
|
|
|
return;
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CMDData
|
|
//
|
|
// Encapsulates access to a resource's metadata.
|
|
//
|
|
class CMDData :
|
|
public IMDData,
|
|
public CMTRefCounted
|
|
{
|
|
//
|
|
// Object metadata
|
|
//
|
|
auto_ref_ptr<IContentTypeMap> m_pContentTypeMap;
|
|
auto_ref_ptr<ICustomErrorMap> m_pCustomErrorMap;
|
|
auto_ref_ptr<IScriptMap> m_pScriptMap;
|
|
|
|
//
|
|
// Buffer for the raw metadata and count of
|
|
// metadata records in that buffer.
|
|
//
|
|
auto_heap_ptr<BYTE> m_pbData;
|
|
DWORD m_dwcMDRecords;
|
|
|
|
//
|
|
// String metadata
|
|
//
|
|
LPCWSTR m_pwszDefaultDocList;
|
|
LPCWSTR m_pwszVRUserName;
|
|
LPCWSTR m_pwszVRPassword;
|
|
LPCWSTR m_pwszExpires;
|
|
LPCWSTR m_pwszBindings;
|
|
LPCWSTR m_pwszVRPath;
|
|
|
|
DWORD m_dwAccessPerms;
|
|
DWORD m_dwDirBrowsing;
|
|
BOOL m_fFrontPage;
|
|
DWORD m_cbIPRestriction;
|
|
BYTE* m_pbIPRestriction;
|
|
BOOL m_fHasApp;
|
|
DWORD m_dwAuthorization;
|
|
DWORD m_dwIsIndexed;
|
|
|
|
//
|
|
// Metabase path from which the data for this data set was loaded.
|
|
// Used in metabase notifications. See CMetabase::OnNotify() below.
|
|
// The string pointed to is at the end of m_pbData.
|
|
//
|
|
LPCWSTR m_pwszMDPathDataSet;
|
|
DWORD m_dwDataSet;
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
CMDData& operator=(const CMDData&);
|
|
CMDData(const CMDData&);
|
|
|
|
public:
|
|
// CREATORS
|
|
//
|
|
CMDData(LPCWSTR pwszMDPathDataSet, DWORD dwDataSet);
|
|
~CMDData();
|
|
BOOL FInitialize( auto_heap_ptr<BYTE>& pbData, DWORD dwcRecords );
|
|
|
|
// Implementation of IRefCounted members
|
|
// Simply route them to our own CMTRefCounted members.
|
|
//
|
|
void AddRef()
|
|
{ CMTRefCounted::AddRef(); }
|
|
void Release()
|
|
{ CMTRefCounted::Release(); }
|
|
|
|
// ACCESSORS
|
|
//
|
|
LPCWSTR PwszMDPathDataSet() const { return m_pwszMDPathDataSet; }
|
|
DWORD DwDataSet() const { return m_dwDataSet; }
|
|
IContentTypeMap * GetContentTypeMap() const { return m_pContentTypeMap.get(); }
|
|
const ICustomErrorMap * GetCustomErrorMap() const { return m_pCustomErrorMap.get(); }
|
|
const IScriptMap * GetScriptMap() const { return m_pScriptMap.get(); }
|
|
|
|
LPCWSTR PwszDefaultDocList() const { return m_pwszDefaultDocList; }
|
|
LPCWSTR PwszVRUserName() const { return m_pwszVRUserName; }
|
|
LPCWSTR PwszVRPassword() const { return m_pwszVRPassword; }
|
|
LPCWSTR PwszExpires() const { return m_pwszExpires; }
|
|
LPCWSTR PwszBindings() const { return m_pwszBindings; }
|
|
LPCWSTR PwszVRPath() const { return m_pwszVRPath; }
|
|
|
|
DWORD DwDirBrowsing() const { return m_dwDirBrowsing; }
|
|
DWORD DwAccessPerms() const { return m_dwAccessPerms; }
|
|
BOOL FAuthorViaFrontPage() const { return m_fFrontPage; }
|
|
BOOL FHasIPRestriction() const { return !!m_cbIPRestriction; }
|
|
BOOL FSameIPRestriction( const IMDData* pIMDD ) const
|
|
{
|
|
const CMDData* prhs = static_cast<const CMDData*>( pIMDD );
|
|
|
|
//$ REVIEW: theoretically, there is no need for
|
|
// a memcmp. However, in the rare case where
|
|
// the sizes are the same and the pointers are
|
|
// different, we might still try this.
|
|
//
|
|
// This way, if/when we go to not using the
|
|
// METADATA_REFERNCE flag when getting the
|
|
// data, there should be no difference.
|
|
//
|
|
if ( m_pbIPRestriction == prhs->m_pbIPRestriction )
|
|
{
|
|
Assert( m_cbIPRestriction == prhs->m_cbIPRestriction );
|
|
return TRUE;
|
|
}
|
|
else if ( m_cbIPRestriction == prhs->m_cbIPRestriction )
|
|
{
|
|
if ( !memcmp (m_pbIPRestriction,
|
|
prhs->m_pbIPRestriction,
|
|
prhs->m_cbIPRestriction))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
//
|
|
//$ REVIEW: end.
|
|
return FALSE;
|
|
}
|
|
BOOL FHasApp() const { return m_fHasApp; }
|
|
DWORD DwAuthorization() const { return m_dwAuthorization; }
|
|
BOOL FIsIndexed() const { return (0 != m_dwIsIndexed); }
|
|
BOOL FSameStarScriptmapping( const IMDData* pIMDD ) const
|
|
{
|
|
return m_pScriptMap->FSameStarScriptmapping( pIMDD->GetScriptMap() );
|
|
}
|
|
};
|
|
|
|
|
|
// ========================================================================
|
|
//
|
|
// STRUCT SCullInfo
|
|
//
|
|
// Structure used in culling cached metabase data once the cache reaches
|
|
// a certain threshold size.
|
|
//
|
|
struct SCullInfo
|
|
{
|
|
//
|
|
// Data set number of the entry to be considered for culling.
|
|
//
|
|
DWORD dwDataSet;
|
|
|
|
//
|
|
// Number of hits to that entry.
|
|
//
|
|
DWORD dwcHits;
|
|
|
|
//
|
|
// Comparison function used by qsort() to sort the array of
|
|
// SCullInfo structures in determining which ones to cull.
|
|
//
|
|
static int __cdecl Compare( const SCullInfo * pCullInfo1,
|
|
const SCullInfo * pCullInfo2 );
|
|
};
|
|
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CMetabase
|
|
//
|
|
// Encapsulates access to the metabase through a cache. The cache
|
|
// provides O(hash) lookup and addition and keeps the cache from
|
|
// growing without bound using a LFU (Least Frequently Used) culling
|
|
// mechanism whenever the size of the cache exceeds a preset threshold.
|
|
//
|
|
class CMetabase : public Singleton<CMetabase>
|
|
{
|
|
//
|
|
// Friend declarations required by Singleton template
|
|
//
|
|
friend class Singleton<CMetabase>;
|
|
|
|
//
|
|
// The IMSAdminBase interface to the real metabase
|
|
//
|
|
auto_ref_ptr<IMSAdminBase> m_pMSAdminBase;
|
|
|
|
//
|
|
// Cache of metadata objects keyed by data set number
|
|
// and a reader/writer lock to protect it.
|
|
//
|
|
typedef CCache<DwordKey, auto_ref_ptr<CMDData> > CDataSetCache;
|
|
CDataSetCache m_cache;
|
|
CMRWLock m_mrwCache;
|
|
|
|
//
|
|
// Cookie for metabase advise sink registration and an event that is
|
|
// signalled when the sink associated with that registration has been
|
|
// shut down and is no longer processing any notifications.
|
|
//
|
|
DWORD m_dwSinkRegCookie;
|
|
CEvent m_evtSinkShutdown;
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS COpGatherCullInfo
|
|
//
|
|
// Cache ForEach() operator class for gathering info needed in determining
|
|
// which entries to cull from the cache when the cache size reaches the
|
|
// culling threshold.
|
|
//
|
|
class COpGatherCullInfo : public CDataSetCache::IOp
|
|
{
|
|
//
|
|
// Array of cull info
|
|
//
|
|
SCullInfo * m_rgci;
|
|
|
|
//
|
|
// Current item in the array above
|
|
//
|
|
int m_ici;
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
COpGatherCullInfo( const COpGatherCullInfo& );
|
|
COpGatherCullInfo& operator=( const COpGatherCullInfo& );
|
|
|
|
public:
|
|
COpGatherCullInfo( SCullInfo * rgci ) :
|
|
m_rgci(rgci),
|
|
m_ici(0)
|
|
{
|
|
Assert( m_rgci );
|
|
}
|
|
|
|
BOOL operator()( const DwordKey& key,
|
|
const auto_ref_ptr<CMDData>& pMDData );
|
|
};
|
|
|
|
//
|
|
// Interlockable flag to prevent simultaneous culling by multiple threads.
|
|
//
|
|
LONG m_lfCulling;
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS COpNotify
|
|
//
|
|
// Cache ForEach() operator class for gathering info needed in determining
|
|
// which entries to blow from the cache when a notification comes in
|
|
// from the metabase that the metadata for a path changed.
|
|
//
|
|
class COpNotify : public CDataSetCache::IOp
|
|
{
|
|
//
|
|
// Array of data set IDs. For entries with a value other than 0,
|
|
// the data set with that ID is flagged to be blown from the cache.
|
|
// The array is guaranteed to be as big as there are entries in
|
|
// the cache.
|
|
//
|
|
DWORD m_cCacheEntry;
|
|
DWORD * m_rgdwDataSets;
|
|
|
|
//
|
|
// Flag set to TRUE if there are any data sets flagged in m_rgdwDataSets.
|
|
//
|
|
BOOL m_fDataSetsFlagged;
|
|
|
|
//
|
|
// Current item in the array above
|
|
//
|
|
UINT m_iCacheEntry;
|
|
|
|
//
|
|
// Path being notified and its length in characters
|
|
// (set via the Configure() method below).
|
|
//
|
|
LPCWSTR m_pwszMDPathNotify;
|
|
UINT m_cchMDPathNotify;
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
COpNotify( const COpNotify& );
|
|
COpNotify& operator=( const COpNotify& );
|
|
|
|
public:
|
|
// CREATORS
|
|
//
|
|
COpNotify( DWORD cce, DWORD * rgdwDataSets ) :
|
|
m_rgdwDataSets(rgdwDataSets),
|
|
m_cCacheEntry(cce),
|
|
m_iCacheEntry(0),
|
|
m_fDataSetsFlagged(FALSE)
|
|
{
|
|
}
|
|
|
|
// ACCESSORS
|
|
//
|
|
BOOL FDataSetsFlagged() const { return m_fDataSetsFlagged; }
|
|
|
|
// MANIPULATORS
|
|
//
|
|
BOOL operator()( const DwordKey& key,
|
|
const auto_ref_ptr<CMDData>& pMDData );
|
|
|
|
VOID Configure( LPCWSTR pwszMDPathNotify )
|
|
{
|
|
// Reset our current entry index.
|
|
//
|
|
m_iCacheEntry = 0;
|
|
|
|
m_pwszMDPathNotify = pwszMDPathNotify;
|
|
m_cchMDPathNotify = static_cast<UINT>(wcslen(pwszMDPathNotify));
|
|
}
|
|
};
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS COpMatchExactPath
|
|
//
|
|
// ForEachMatch() operator that fetches the cache entry whose path matches
|
|
// a desired path. Used when inheritance bits are important.
|
|
//
|
|
class COpMatchExactPath : public CDataSetCache::IOp
|
|
{
|
|
// The path to match
|
|
//
|
|
LPCWSTR m_pwszMDPathToMatch;
|
|
|
|
// The data for the matched path
|
|
//
|
|
auto_ref_ptr<CMDData> m_pMDDataMatched;
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
COpMatchExactPath( const COpMatchExactPath& );
|
|
COpMatchExactPath& operator=( const COpMatchExactPath& );
|
|
|
|
public:
|
|
// CREATORS
|
|
//
|
|
COpMatchExactPath( LPCWSTR pwszMDPath ) :
|
|
m_pwszMDPathToMatch(pwszMDPath)
|
|
{
|
|
}
|
|
|
|
// MANIPULATORS
|
|
//
|
|
VOID Invoke( CDataSetCache& cache,
|
|
DWORD dwDataSet,
|
|
auto_ref_ptr<CMDData> * ppMDData )
|
|
{
|
|
// Do the ForEachMatch()
|
|
//
|
|
(VOID) cache.ForEachMatch( dwDataSet, *this );
|
|
|
|
// Returned the matched data (if any)
|
|
//
|
|
(*ppMDData).take_ownership(m_pMDDataMatched.relinquish());
|
|
}
|
|
|
|
BOOL operator()( const DwordKey&,
|
|
const auto_ref_ptr<CMDData>& pMDData )
|
|
{
|
|
// If the data's data set number path matches the
|
|
// path we are looking for, then return this data set.
|
|
// If not then do nothing and keep looking.
|
|
//
|
|
//$OPT Can we guarantee that all MD paths are one case?
|
|
//
|
|
if (_wcsicmp(pMDData->PwszMDPathDataSet(), m_pwszMDPathToMatch))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_pMDDataMatched = pMDData;
|
|
return FALSE;
|
|
}
|
|
}
|
|
};
|
|
|
|
// CREATORS
|
|
//
|
|
CMetabase() :
|
|
m_lfCulling(FALSE),
|
|
m_dwSinkRegCookie(0)
|
|
{
|
|
}
|
|
~CMetabase();
|
|
|
|
// MANIPULATORS
|
|
//
|
|
VOID CullCacheEntries();
|
|
|
|
HRESULT HrCacheData( const IEcb& ecb,
|
|
LPCWSTR pwszMDPathAccess,
|
|
LPCWSTR pwszMDPathOpen,
|
|
CMDData ** ppMDData );
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
CMetabase& operator=( const CMetabase& );
|
|
CMetabase( const CMetabase& );
|
|
|
|
public:
|
|
enum
|
|
{
|
|
//
|
|
// Number of entries allowed in cache before culling.
|
|
//
|
|
//$REVIEW Not based on emperical data
|
|
//
|
|
C_MAX_CACHE_ENTRIES = 100,
|
|
|
|
//
|
|
// Number of entries to cull from cache when culling.
|
|
//
|
|
//$REVIEW Not based on emperical data
|
|
//$REVIEW Consider expressing culling as a factor (percentage)
|
|
//$REVIEW rather than an absolute number.
|
|
//
|
|
C_CULL_CACHE_ENTRIES = 20,
|
|
|
|
//
|
|
// Size of the metadata for the average cache entry.
|
|
// This one is based on experential data. 9K is just
|
|
// enough to hold all of an object's inherited metadata.
|
|
//
|
|
CCH_AVG_CACHE_ENTRY = 9 * 1024
|
|
};
|
|
|
|
// CREATORS
|
|
//
|
|
// Instance creating/destroying routines provided
|
|
// by the Singleton template.
|
|
//
|
|
using Singleton<CMetabase>::CreateInstance;
|
|
using Singleton<CMetabase>::DestroyInstance;
|
|
using Singleton<CMetabase>::Instance;
|
|
BOOL FInitialize();
|
|
|
|
VOID OnNotify( DWORD dwcChangeObjects,
|
|
MD_CHANGE_OBJECT_W rgCO[] );
|
|
|
|
HRESULT HrGetData( const IEcb& ecb,
|
|
LPCWSTR pwszMDPathAccess,
|
|
LPCWSTR pwszMDPathOpen,
|
|
IMDData ** ppMDData );
|
|
|
|
DWORD DwChangeNumber( const IEcb * pecb);
|
|
|
|
HRESULT HrOpenObject( LPCWSTR pwszMDPath,
|
|
DWORD dwAccess,
|
|
DWORD dwMsecTimeout,
|
|
CMDObjectHandle * pmdoh );
|
|
|
|
HRESULT HrOpenLowestNodeObject( LPWSTR pwszMDPath,
|
|
DWORD dwAccess,
|
|
LPWSTR * ppwszMDPath,
|
|
CMDObjectHandle * pmdoh );
|
|
|
|
HRESULT HrIsAuthorViaFrontPageNeeded( const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
BOOL * pfFrontPageWeb );
|
|
|
|
};
|
|
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CMDObjectHandle
|
|
//
|
|
// Encapsulates access to a metabase object through an open handle,
|
|
// ensuring that the handle is always propery closed.
|
|
//
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrOpen()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrOpen( IMSAdminBase * pMSAdminBase,
|
|
LPCWSTR pwszPath,
|
|
DWORD dwAccess,
|
|
DWORD dwMsecTimeout )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
Assert(pMSAdminBase);
|
|
Assert (NULL == m_pMSAdminBase || pMSAdminBase == m_pMSAdminBase);
|
|
|
|
// METADATA_MASTER_ROOT_HANDLE must be set
|
|
//
|
|
Assert (METADATA_MASTER_ROOT_HANDLE == m_hMDObject);
|
|
|
|
// Save the pointer to the interface, so we could use
|
|
// it any time later in spite of the fact if opening
|
|
// of the key succeeded or not
|
|
//
|
|
m_pMSAdminBase = pMSAdminBase;
|
|
hr = pMSAdminBase->OpenKey(METADATA_MASTER_ROOT_HANDLE,
|
|
pwszPath,
|
|
dwAccess,
|
|
dwMsecTimeout,
|
|
&m_hMDObject);
|
|
|
|
if (ERROR_SUCCESS != hr)
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrOpen() - IMSAdminBase::OpenKey() failed 0x%08lX\n", hr );
|
|
}
|
|
else
|
|
{
|
|
m_pwszPath = pwszPath;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrOpenLowestNode()
|
|
//
|
|
// Purpose:
|
|
//
|
|
// Opens the LOWEST possible metabase node along the given path.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// pMSAdminBase [in] IMSAdminBase interface pointer.
|
|
//
|
|
// pwszPath [in] A full metabase path. This function opens
|
|
// the lowest possible node along this path,
|
|
// by working backward from the full path
|
|
// up to the root of the metabase until a
|
|
// path specifying an existing node is opened.
|
|
//
|
|
// dwAccess [in] Permissions with which we want to open the
|
|
// node.
|
|
//
|
|
// ppwszPath [out] Points to the remainder of the initial path
|
|
// relative to the opened node. This value is
|
|
// NULL if the initial path was openable.
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrOpenLowestNode( IMSAdminBase * pMSAdminBase,
|
|
LPWSTR pwszPath,
|
|
DWORD dwAccess,
|
|
LPWSTR * ppwszPath)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
WCHAR * pwch;
|
|
|
|
Assert (pMSAdminBase);
|
|
Assert (pwszPath);
|
|
Assert (L'/' == pwszPath[0]);
|
|
Assert (ppwszPath);
|
|
Assert (!IsBadWritePtr(ppwszPath, sizeof(LPWSTR)) );
|
|
|
|
*ppwszPath = NULL;
|
|
|
|
pwch = pwszPath + wcslen(pwszPath);
|
|
while ( pwch > pwszPath )
|
|
{
|
|
//
|
|
// Split off the path from the root at the current position
|
|
//
|
|
*pwch = L'\0';
|
|
|
|
//
|
|
// Attempt to open a node at the resulting root
|
|
//
|
|
hr = HrOpen(pMSAdminBase,
|
|
pwszPath,
|
|
dwAccess,
|
|
METADATA_TIMEOUT);
|
|
|
|
//
|
|
// If we successfully open the node or failed for any reason
|
|
// other than that the node wasn't there, we're done.
|
|
//
|
|
if ( SUCCEEDED(hr) ||
|
|
HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) != hr )
|
|
{
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// If there was no node, then restore the slash separator
|
|
// that we previously nulled out and scan backward to the
|
|
// next possible split location.
|
|
//
|
|
if ( *ppwszPath )
|
|
{
|
|
*pwch = L'/';
|
|
}
|
|
|
|
pwch--;
|
|
while (*pwch != L'/')
|
|
{
|
|
pwch--;
|
|
}
|
|
|
|
*ppwszPath = pwch + 1;
|
|
}
|
|
|
|
ret:
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrEnumKeys()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrEnumKeys( LPCWSTR pwszPath,
|
|
LPWSTR pwszChild,
|
|
DWORD dwIndex ) const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
//
|
|
// METADATA_MASTER_ROOT_HANDLE is valid for this operation so no assert
|
|
//
|
|
|
|
Assert (m_pMSAdminBase);
|
|
|
|
hr = m_pMSAdminBase->EnumKeys(m_hMDObject,
|
|
pwszPath,
|
|
pwszChild,
|
|
dwIndex);
|
|
if (ERROR_SUCCESS != hr )
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrEnumKeys() - IMSAdminBase::EnumKeys() failed 0x%08lX\n", hr );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrGetDataPaths()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrGetDataPaths( LPCWSTR pwszPath,
|
|
DWORD dwPropID,
|
|
DWORD dwDataType,
|
|
LPWSTR pwszDataPaths,
|
|
DWORD * pcchDataPaths ) const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
//
|
|
// METADATA_MASTER_ROOT_HANDLE is valid for this operation so no assert
|
|
//
|
|
|
|
Assert (pwszPath);
|
|
Assert (!IsBadReadPtr(pcchDataPaths, sizeof(DWORD)));
|
|
Assert (!IsBadWritePtr(pcchDataPaths, sizeof(DWORD)));
|
|
Assert (!IsBadWritePtr(pwszDataPaths, *pcchDataPaths * sizeof(WCHAR)));
|
|
|
|
Assert (m_pMSAdminBase);
|
|
|
|
hr = m_pMSAdminBase->GetDataPaths(m_hMDObject,
|
|
pwszPath,
|
|
dwPropID,
|
|
dwDataType,
|
|
*pcchDataPaths,
|
|
pwszDataPaths,
|
|
pcchDataPaths);
|
|
|
|
if (ERROR_SUCCESS != hr )
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrGetDataPaths() - IMSAdminBase::GetDataPaths() failed 0x%08lX\n", hr );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrGetMetaData()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrGetMetaData( LPCWSTR pwszPath,
|
|
METADATA_RECORD * pmdrec,
|
|
DWORD * pcbBufRequired ) const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
//
|
|
// METADATA_MASTER_ROOT_HANDLE is valid for this operation so no assert
|
|
//
|
|
|
|
Assert (m_pMSAdminBase);
|
|
|
|
hr = m_pMSAdminBase->GetData(m_hMDObject,
|
|
const_cast<LPWSTR>(pwszPath),
|
|
pmdrec,
|
|
pcbBufRequired);
|
|
if (ERROR_SUCCESS != hr )
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrGetMetaData() - IMSAdminBase::GetData() failed 0x%08lX\n", hr );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrGetAllMetaData()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrGetAllMetaData( LPCWSTR pwszPath,
|
|
DWORD dwAttributes,
|
|
DWORD dwUserType,
|
|
DWORD dwDataType,
|
|
DWORD * pdwcRecords,
|
|
DWORD * pdwDataSet,
|
|
DWORD cbBuf,
|
|
LPBYTE pbBuf,
|
|
DWORD * pcbBufRequired ) const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
//
|
|
// METADATA_MASTER_ROOT_HANDLE is valid for this operation so no assert
|
|
//
|
|
|
|
Assert (m_pMSAdminBase);
|
|
|
|
hr = m_pMSAdminBase->GetAllData(m_hMDObject,
|
|
pwszPath,
|
|
dwAttributes,
|
|
dwUserType,
|
|
dwDataType,
|
|
pdwcRecords,
|
|
pdwDataSet,
|
|
cbBuf,
|
|
pbBuf,
|
|
pcbBufRequired);
|
|
if (ERROR_SUCCESS != hr )
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrGetAllMetaData() - IMSAdminBase::GetAllData() failed 0x%08lX\n", hr );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrSetMetaData()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrSetMetaData( LPCWSTR pwszPath,
|
|
const METADATA_RECORD * pmdrec ) const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
Assert (pmdrec);
|
|
|
|
// METADATA_MASTER_ROOT_HANDLE not valid for this operation
|
|
//
|
|
Assert (METADATA_MASTER_ROOT_HANDLE != m_hMDObject);
|
|
Assert (m_pMSAdminBase);
|
|
|
|
hr = m_pMSAdminBase->SetData(m_hMDObject,
|
|
pwszPath,
|
|
const_cast<METADATA_RECORD *>(pmdrec));
|
|
if (ERROR_SUCCESS != hr)
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrSetMetaData() - IMSAdminBase::SetData() failed 0x%08lX\n", hr );
|
|
}
|
|
else
|
|
{
|
|
// Notify the sinks registered on this IMSAdminBase
|
|
// will not get notified, so we need to do all the
|
|
// invalidation ourselves. The only sink currently
|
|
// being registered is CChildVRCache.
|
|
//
|
|
SCODE scT;
|
|
MD_CHANGE_OBJECT_W mdChObjW;
|
|
UINT cchBase = 0;
|
|
UINT cchPath = 0;
|
|
|
|
CStackBuffer<WCHAR,MAX_PATH> pwsz;
|
|
UINT cch;
|
|
|
|
if (m_pwszPath)
|
|
{
|
|
cchBase = static_cast<UINT>(wcslen(m_pwszPath));
|
|
}
|
|
if (pwszPath)
|
|
{
|
|
cchPath = static_cast<UINT>(wcslen(pwszPath));
|
|
}
|
|
|
|
// Allocate enough space for constructed path:
|
|
// base, '/' separator,
|
|
// path, '/' termination, '\0' termination...
|
|
//
|
|
cch = cchBase + 1 + cchPath + 2;
|
|
if (!pwsz.resize(cch * sizeof(WCHAR)))
|
|
return E_OUTOFMEMORY;
|
|
|
|
scT = ScBuildChangeObject(m_pwszPath,
|
|
cchBase,
|
|
pwszPath,
|
|
cchPath,
|
|
MD_CHANGE_TYPE_SET_DATA,
|
|
&pmdrec->dwMDIdentifier,
|
|
pwsz.get(),
|
|
&cch,
|
|
&mdChObjW);
|
|
|
|
// Function above returns S_FALSE when it is short in buffer,
|
|
// otherwise it always returns S_OK. The buffer we gave is
|
|
// sufficient, so assert that we succeeded
|
|
//
|
|
Assert( S_OK == scT );
|
|
CNotifSink::OnNotify( 1, &mdChObjW );
|
|
goto ret;
|
|
}
|
|
|
|
ret:
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrDeleteMetaData()
|
|
//
|
|
HRESULT
|
|
CMDObjectHandle::HrDeleteMetaData( LPCWSTR pwszPath,
|
|
DWORD dwPropID,
|
|
DWORD dwDataType ) const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
safe_revert sr(m_ecb.HitUser());
|
|
|
|
// METADATA_MASTER_ROOT_HANDLE not valid for this operation
|
|
//
|
|
Assert (METADATA_MASTER_ROOT_HANDLE != m_hMDObject);
|
|
Assert (m_pMSAdminBase);
|
|
|
|
hr = m_pMSAdminBase->DeleteData(m_hMDObject,
|
|
pwszPath,
|
|
dwPropID,
|
|
dwDataType);
|
|
if (ERROR_SUCCESS != hr)
|
|
{
|
|
if (!FAILED(hr))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
}
|
|
|
|
MBTrace("MB: CMDObjectHandle::HrDeleteMetaData() - IMSAdminBase::DeleteData() failed 0x%08lX\n", hr );
|
|
}
|
|
else
|
|
{
|
|
// Notify the sinks registered on this IMSAdminBase
|
|
// will not get notified, so we need to do all the
|
|
// invalidation ourselves. The only sink currently
|
|
// being registered is CChildVRCache.
|
|
//
|
|
SCODE scT;
|
|
MD_CHANGE_OBJECT_W mdChObjW;
|
|
UINT cchBase = 0;
|
|
UINT cchPath = 0;
|
|
|
|
CStackBuffer<WCHAR,MAX_PATH> pwsz;
|
|
UINT cch;
|
|
|
|
if (m_pwszPath)
|
|
{
|
|
cchBase = static_cast<UINT>(wcslen(m_pwszPath));
|
|
}
|
|
if (pwszPath)
|
|
{
|
|
cchPath = static_cast<UINT>(wcslen(pwszPath));
|
|
}
|
|
|
|
// Allocate enough space for constructed path:
|
|
// base, '/' separator,
|
|
// path, '/' termination, '\0' termination...
|
|
//
|
|
cch = cchBase + 1 + cchPath + 2;
|
|
if (!pwsz.resize(cch * sizeof(WCHAR)))
|
|
return E_OUTOFMEMORY;
|
|
|
|
scT = ScBuildChangeObject(m_pwszPath,
|
|
cchBase,
|
|
pwszPath,
|
|
cchPath,
|
|
MD_CHANGE_TYPE_DELETE_DATA,
|
|
&dwPropID,
|
|
pwsz.get(),
|
|
&cch,
|
|
&mdChObjW);
|
|
|
|
// Function above returns S_FALSE when it is short in buffer,
|
|
// otherwise it always returns S_OK. The buffer we gave is
|
|
// sufficient, so assert that we succeeded
|
|
//
|
|
Assert( S_OK == scT );
|
|
CNotifSink::OnNotify( 1, &mdChObjW );
|
|
goto ret;
|
|
}
|
|
|
|
ret:
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// Close()
|
|
//
|
|
VOID
|
|
CMDObjectHandle::Close()
|
|
{
|
|
if ( METADATA_MASTER_ROOT_HANDLE != m_hMDObject )
|
|
{
|
|
Assert (m_pMSAdminBase);
|
|
|
|
m_pMSAdminBase->CloseKey( m_hMDObject );
|
|
m_hMDObject = METADATA_MASTER_ROOT_HANDLE;
|
|
m_pwszPath = NULL;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// ~CMDObjectHandle()
|
|
//
|
|
CMDObjectHandle::~CMDObjectHandle()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrReadMetaData()
|
|
//
|
|
// Reads in the raw metadata from the metabase.
|
|
//
|
|
HrReadMetaData( const IEcb& ecb,
|
|
IMSAdminBase * pMSAdminBase,
|
|
LPCWSTR pwszMDPathAccess,
|
|
LPCWSTR pwszMDPathOpen,
|
|
LPBYTE * ppbData,
|
|
DWORD * pdwDataSet,
|
|
DWORD * pdwcRecords,
|
|
LPCWSTR * ppwszMDPathDataSet )
|
|
{
|
|
CMDObjectHandle mdoh(ecb);
|
|
HRESULT hr;
|
|
|
|
Assert( ppwszMDPathDataSet );
|
|
|
|
//
|
|
// We should never open the root node of the metabase.
|
|
// It's prohibitively expensive.
|
|
//
|
|
Assert( pwszMDPathOpen );
|
|
|
|
//
|
|
// If the open path is not the path we are trying to access
|
|
// then the former must be a proper prefix of the latter.
|
|
//
|
|
Assert( pwszMDPathAccess == pwszMDPathOpen ||
|
|
!_wcsnicmp(pwszMDPathOpen, pwszMDPathAccess, wcslen(pwszMDPathOpen)) );
|
|
|
|
//
|
|
// Open the specified "open" path. Note that we do not simply open
|
|
// the full path because it may not exist and we don't necessarily
|
|
// want to try opening successive "parent" paths as each attempt
|
|
// costs us a trip through a global critical section in the metabase.
|
|
//
|
|
hr = mdoh.HrOpen( pMSAdminBase,
|
|
pwszMDPathOpen,
|
|
METADATA_PERMISSION_READ,
|
|
200 ); // timeout in msec (0.2 sec)
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
DebugTrace( "HrReadMetaData() - Error opening vroot for read 0x%08lX\n", hr );
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Get all of the metadata. We should go through this loop at most twice --
|
|
// if our inital guess is too small to hold all the data the first time
|
|
// through, we'll go through it again with a buffer of the adequate size.
|
|
//
|
|
// Note that we reserve space at the end of the buffer for a copy of the
|
|
// access path including a slash at the end (to make subpath detection
|
|
// easier).
|
|
//
|
|
DWORD cbBuf = CMetabase::CCH_AVG_CACHE_ENTRY * sizeof(WCHAR);
|
|
DWORD cchMDPathAccess = static_cast<DWORD>(wcslen(pwszMDPathAccess) + 1);
|
|
auto_heap_ptr<BYTE> pbBuf(static_cast<LPBYTE>(ExAlloc(cbBuf + CbSizeWsz(cchMDPathAccess))));
|
|
|
|
//
|
|
// Get all the metadata. Include inherited data (METADATA_INHERIT).
|
|
// Return whether the data for a given path was inherited (METADATA_ISINHERITED).
|
|
// If the path does not exist, return the inherited data
|
|
// anyway (METADATA_PARTIAL_PATH).
|
|
//
|
|
hr = mdoh.HrGetAllMetaData( (pwszMDPathOpen == pwszMDPathAccess) ?
|
|
NULL :
|
|
pwszMDPathAccess + wcslen(pwszMDPathOpen),
|
|
METADATA_INHERIT |
|
|
METADATA_ISINHERITED |
|
|
METADATA_PARTIAL_PATH,
|
|
ALL_METADATA,
|
|
ALL_METADATA,
|
|
pdwcRecords,
|
|
pdwDataSet,
|
|
cbBuf,
|
|
pbBuf.get(),
|
|
&cbBuf );
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
if ( HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) != hr )
|
|
{
|
|
DebugTrace( "HrReadMetaData() - Error getting all metadata 0x%08lX\n", hr );
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// We couldn't read all the metadata because our initial
|
|
// guess was too small so allocate a buffer that is as
|
|
// big as the metabase told us we needed and use that
|
|
// buffer the next time around.
|
|
//
|
|
pbBuf.realloc(cbBuf + CbSizeWsz(cchMDPathAccess));
|
|
hr = mdoh.HrGetAllMetaData( (pwszMDPathOpen == pwszMDPathAccess) ?
|
|
NULL :
|
|
pwszMDPathAccess + wcslen(pwszMDPathOpen),
|
|
METADATA_INHERIT |
|
|
METADATA_PARTIAL_PATH,
|
|
ALL_METADATA,
|
|
ALL_METADATA,
|
|
pdwcRecords,
|
|
pdwDataSet,
|
|
cbBuf,
|
|
pbBuf.get(),
|
|
&cbBuf );
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
DebugTrace( "HrReadMetaData() - Error getting all metadata 0x%08lX\n", hr );
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy the access path (including the null terminator) to the end
|
|
// of the buffer.
|
|
//
|
|
Assert( L'\0' == pwszMDPathAccess[cchMDPathAccess - 1] );
|
|
memcpy( pbBuf + cbBuf, pwszMDPathAccess, cchMDPathAccess * sizeof(WCHAR) );
|
|
|
|
//
|
|
// Tack on a final slash and null terminate.
|
|
// Note: pwszMDPathAccess may or may not already have a terminating slash
|
|
// ** depending on how this function was called **
|
|
// (specifically, deep MOVE/COPY/DELETEs will have slashes on
|
|
// sub-directory URLs)
|
|
//
|
|
LPWSTR pwszT = reinterpret_cast<LPWSTR>(pbBuf + cbBuf + (cchMDPathAccess - 2) * sizeof(WCHAR));
|
|
if ( L'/' != pwszT[0] )
|
|
{
|
|
pwszT[1] = L'/';
|
|
pwszT[2] = L'\0';
|
|
}
|
|
|
|
//
|
|
// Return the path
|
|
//
|
|
*ppwszMDPathDataSet = reinterpret_cast<LPWSTR>(pbBuf.get() + cbBuf);
|
|
|
|
//
|
|
// And the data
|
|
//
|
|
*ppbData = pbBuf.relinquish();
|
|
return S_OK;
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CMDData
|
|
//
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMDData::CMDData()
|
|
//
|
|
CMDData::CMDData( LPCWSTR pwszMDPathDataSet,
|
|
DWORD dwDataSet ) :
|
|
m_pwszMDPathDataSet(pwszMDPathDataSet),
|
|
m_dwDataSet(dwDataSet),
|
|
// Defaults not handled by auto_xxx classes:
|
|
m_pwszDefaultDocList(NULL), // No default doc list
|
|
m_pwszVRUserName(NULL), // No VRoot user name
|
|
m_pwszVRPassword(NULL), // No VRoot password
|
|
m_pwszExpires(NULL), // No expiration
|
|
m_pwszBindings(NULL), // No custom bindings
|
|
m_pwszVRPath(NULL), // No VRoot physical path
|
|
m_dwAccessPerms(0), // Deny all access
|
|
m_dwDirBrowsing(0), // No default dir browsing
|
|
m_fFrontPage(FALSE), // No FrontPage authoring
|
|
m_cbIPRestriction(0), // --
|
|
m_pbIPRestriction(NULL), // --
|
|
m_fHasApp(FALSE), // No registered app
|
|
m_dwAuthorization(0), // No specific authorization method
|
|
m_dwIsIndexed(1) // Indexing is on by default
|
|
{
|
|
Assert(pwszMDPathDataSet);
|
|
Assert(dwDataSet != 0);
|
|
m_cRef = 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMDData::~CMDData()
|
|
//
|
|
CMDData::~CMDData()
|
|
{
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMDData::FInitialize()
|
|
//
|
|
// Populates a metadata object from metadata obtained through an accessor.
|
|
//
|
|
BOOL
|
|
CMDData::FInitialize( auto_heap_ptr<BYTE>& pbData,
|
|
DWORD dwcMDRecords )
|
|
{
|
|
Assert(!IsBadReadPtr(pbData.get(), dwcMDRecords * sizeof(METADATA_RECORD)));
|
|
|
|
for ( DWORD iRec = 0; iRec < dwcMDRecords; iRec++ )
|
|
{
|
|
//
|
|
// Locate the metadata record and its data. Note that the
|
|
// pbMDData field of METADATA_RECORD is actually an offset
|
|
// to the data -- not a pointer to it -- from the start of
|
|
// the buffer.
|
|
//
|
|
const METADATA_GETALL_RECORD& mdrec =
|
|
reinterpret_cast<const METADATA_GETALL_RECORD *>(pbData.get())[iRec];
|
|
|
|
LPVOID pvRecordData =
|
|
pbData.get() + mdrec.dwMDDataOffset;
|
|
|
|
//
|
|
// !!!IMPORTANT!!! The list of identifiers below must be kept up to date
|
|
// with the list in FHasCachedIDs().
|
|
//
|
|
switch ( mdrec.dwMDIdentifier )
|
|
{
|
|
case MD_IP_SEC:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != BINARY_METADATA )
|
|
return FALSE;
|
|
|
|
m_cbIPRestriction = mdrec.dwMDDataLen;
|
|
m_pbIPRestriction = static_cast<LPBYTE>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_ACCESS_PERM:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != DWORD_METADATA )
|
|
return FALSE;
|
|
|
|
m_dwAccessPerms = *static_cast<LPDWORD>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_IS_CONTENT_INDEXED:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != DWORD_METADATA )
|
|
return FALSE;
|
|
|
|
m_dwIsIndexed = *static_cast<LPDWORD>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_FRONTPAGE_WEB:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != DWORD_METADATA )
|
|
return FALSE;
|
|
|
|
//
|
|
// Set the frontpage flag if MD_FRONTPAGE_WEB is
|
|
// explicitly set on this metabase node and not
|
|
// inherited.
|
|
//
|
|
m_fFrontPage = *static_cast<LPDWORD>(pvRecordData) &&
|
|
!(mdrec.dwMDAttributes & METADATA_ISINHERITED);
|
|
break;
|
|
}
|
|
|
|
case MD_DIRECTORY_BROWSING:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != DWORD_METADATA )
|
|
return FALSE;
|
|
|
|
m_dwDirBrowsing = *static_cast<LPDWORD>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_AUTHORIZATION:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != DWORD_METADATA )
|
|
return FALSE;
|
|
|
|
m_dwAuthorization = *static_cast<LPDWORD>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_DEFAULT_LOAD_FILE:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != STRING_METADATA )
|
|
return FALSE;
|
|
|
|
m_pwszDefaultDocList = static_cast<LPWSTR>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_CUSTOM_ERROR:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != MULTISZ_METADATA )
|
|
return FALSE;
|
|
|
|
m_pCustomErrorMap.take_ownership(
|
|
NewCustomErrorMap(static_cast<LPWSTR>(pvRecordData)));
|
|
|
|
//
|
|
// Bail if we cannot create the map.
|
|
// This means the record data was malformed.
|
|
//
|
|
if ( !m_pCustomErrorMap.get() )
|
|
return FALSE;
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_MIME_MAP:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != MULTISZ_METADATA )
|
|
return FALSE;
|
|
|
|
m_pContentTypeMap.take_ownership(
|
|
NewContentTypeMap(static_cast<LPWSTR>(pvRecordData),
|
|
!!(mdrec.dwMDAttributes & METADATA_ISINHERITED)));
|
|
|
|
//
|
|
// Bail if we cannot create the map.
|
|
// This means the record data was malformed.
|
|
//
|
|
if ( !m_pContentTypeMap.get() )
|
|
return FALSE;
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_SCRIPT_MAPS:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != MULTISZ_METADATA )
|
|
return FALSE;
|
|
|
|
m_pScriptMap.take_ownership(
|
|
NewScriptMap(static_cast<LPWSTR>(pvRecordData)));
|
|
|
|
//
|
|
// Bail if we cannot create the map.
|
|
// This means the record data was malformed.
|
|
//
|
|
if ( !m_pScriptMap.get() )
|
|
return FALSE;
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_APP_ISOLATED:
|
|
{
|
|
//
|
|
// If this property exists on this node at all
|
|
// (i.e. it is not inherited) then we want to
|
|
// know, regardless of its value.
|
|
//
|
|
if ( mdrec.dwMDAttributes & METADATA_ISINHERITED )
|
|
m_fHasApp = TRUE;
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_VR_USERNAME:
|
|
{
|
|
if ( mdrec.dwMDDataType != STRING_METADATA )
|
|
return FALSE;
|
|
|
|
m_pwszVRUserName = static_cast<LPWSTR>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_VR_PASSWORD:
|
|
{
|
|
if ( mdrec.dwMDDataType != STRING_METADATA )
|
|
return FALSE;
|
|
|
|
m_pwszVRPassword = static_cast<LPWSTR>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_HTTP_EXPIRES:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != STRING_METADATA )
|
|
return FALSE;
|
|
|
|
m_pwszExpires = static_cast<LPWSTR>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_SERVER_BINDINGS:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != MULTISZ_METADATA )
|
|
return FALSE;
|
|
|
|
m_pwszBindings = static_cast<LPWSTR>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
case MD_VR_PATH:
|
|
{
|
|
Assert( mdrec.dwMDDataTag == NULL );
|
|
if ( mdrec.dwMDDataType != STRING_METADATA )
|
|
return FALSE;
|
|
|
|
m_pwszVRPath = static_cast<LPWSTR>(pvRecordData);
|
|
break;
|
|
}
|
|
|
|
//
|
|
//$REVIEW Do we need to worry about any of these?
|
|
//
|
|
case MD_VR_PASSTHROUGH:
|
|
case MD_SSL_ACCESS_PERM:
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If all goes well we take ownership of the data buffer passed in.
|
|
//
|
|
m_pbData = pbData.relinquish();
|
|
m_dwcMDRecords = dwcMDRecords;
|
|
return TRUE;
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CMetabase
|
|
//
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::~CMetabase()
|
|
//
|
|
CMetabase::~CMetabase()
|
|
{
|
|
//
|
|
// If we ever advised a notification sink then unadvise it now.
|
|
//
|
|
if ( m_dwSinkRegCookie )
|
|
{
|
|
//
|
|
// Unadvise the sink
|
|
//
|
|
UnadviseSink(*m_pMSAdminBase.get(), m_dwSinkRegCookie);
|
|
|
|
//
|
|
// Wait for the sink to shutdown
|
|
//
|
|
m_evtSinkShutdown.Wait();
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::FInitialize()
|
|
//
|
|
BOOL
|
|
CMetabase::FInitialize()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Init the cache
|
|
//
|
|
if ( !m_cache.FInit() )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
|
|
// Init its reader/writer lock
|
|
//
|
|
if ( !m_mrwCache.FInitialize() )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
|
|
// Create an instance of the com-base metabase interface.
|
|
// Again, we expect that somebody above us has already done
|
|
// this, so it should be fairly cheap as well.
|
|
//
|
|
// Note that we do not init COM at any point. IIS should
|
|
// have already done that for us.
|
|
//
|
|
hr = CoCreateInstance (CLSID_MSAdminBase,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
|
|
IID_IMSAdminBase,
|
|
(LPVOID *)m_pMSAdminBase.load());
|
|
if ( FAILED(hr) )
|
|
{
|
|
DebugTrace( "CMetabase::FInitialize() - CoCreateInstance(CLSID_MDCOM) failed 0x%08lX\n", hr );
|
|
goto ret;
|
|
}
|
|
|
|
// Register for metabase change notifications
|
|
//
|
|
{
|
|
auto_ref_ptr<CNotifSink> pSinkNew;
|
|
|
|
// First, set up an empty security descriptor and attributes
|
|
// so that the event can be created with no security
|
|
// (i.e. accessible from any security context).
|
|
//
|
|
SECURITY_DESCRIPTOR* psdAllAccess = PsdCreateWorld();
|
|
SECURITY_ATTRIBUTES saAllAccess;
|
|
|
|
saAllAccess.nLength = sizeof(saAllAccess);
|
|
saAllAccess.lpSecurityDescriptor = psdAllAccess;
|
|
saAllAccess.bInheritHandle = FALSE;
|
|
|
|
//
|
|
// Create the sink shutdown event
|
|
//
|
|
if ( !m_evtSinkShutdown.FCreate( &saAllAccess, // no security
|
|
TRUE, // manual access
|
|
FALSE, // initially non-signalled
|
|
NULL ))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
DebugTrace( "CMetabase::FInitialize() - m_evtSinkShutdown.FCreate() failed 0x%08lX\n", hr );
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// Create the sink
|
|
//
|
|
pSinkNew.take_ownership(new CNotifSink(m_evtSinkShutdown));
|
|
|
|
//
|
|
// Advise the sink
|
|
//
|
|
hr = HrAdviseSink(*m_pMSAdminBase.get(),
|
|
pSinkNew.get(),
|
|
&m_dwSinkRegCookie);
|
|
if ( FAILED(hr) )
|
|
{
|
|
DebugTrace( "CMetabase::FInitialize() - HrAdviseSink() failed 0x%08lX\n", hr );
|
|
goto ret;
|
|
}
|
|
|
|
LocalFree(psdAllAccess);
|
|
}
|
|
|
|
ret:
|
|
return SUCCEEDED(hr);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::DwChangeNumber()
|
|
//
|
|
|
|
DWORD
|
|
CMetabase::DwChangeNumber(const IEcb * pecb)
|
|
{
|
|
Assert(pecb);
|
|
|
|
DWORD dw = 0;
|
|
safe_revert sr(pecb->HitUser());
|
|
|
|
Assert(m_pMSAdminBase.get());
|
|
|
|
// Note: this function can fail. We are not checking the return value
|
|
// because we need to generate an etag regardless of failure.
|
|
//
|
|
(void) m_pMSAdminBase->GetSystemChangeNumber(&dw);
|
|
return dw;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::COpGatherCullInfo::operator()
|
|
//
|
|
BOOL
|
|
CMetabase::COpGatherCullInfo::operator()( const DwordKey& key,
|
|
const auto_ref_ptr<CMDData>& pMDData )
|
|
{
|
|
//
|
|
// Gather and reset the access count of the current metadata object
|
|
//
|
|
m_rgci[m_ici].dwDataSet = key.Dw();
|
|
m_rgci[m_ici].dwcHits = pMDData->LFUData().DwGatherAndResetHitCount();
|
|
++m_ici;
|
|
|
|
//
|
|
// ForEach() operators can cancel the iteration by returning FALSE.
|
|
// We always want to iterate over everything so return TRUE
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// SCullInfo::Compare()
|
|
//
|
|
// Cull info comparison function used by qsort() to sort an array
|
|
// of SCullInfo structures.
|
|
//
|
|
int __cdecl
|
|
SCullInfo::Compare( const SCullInfo * pCullInfo1,
|
|
const SCullInfo * pCullInfo2 )
|
|
{
|
|
return static_cast<int>(pCullInfo1->dwcHits - pCullInfo2->dwcHits);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::CullCacheEntries()
|
|
//
|
|
// Called by HrCacheData() when the number of entries in the metabase
|
|
// cache reaches a preset threshold. This function removes those entries
|
|
// that have been used least frequently since the last time the cache
|
|
// was culled.
|
|
//
|
|
VOID
|
|
CMetabase::CullCacheEntries()
|
|
{
|
|
CStackBuffer<SCullInfo,128> rgci;
|
|
int cCacheEntries;
|
|
|
|
//
|
|
// Gather cull info for all of the cache entries. We need to do
|
|
// this in a read block so that the cache stays stable (i.e. does
|
|
// not have entries added or removed) while we are in the ForEach()
|
|
// operation.
|
|
//
|
|
{
|
|
//
|
|
// Lock out writers -- anyone trying to add or remove cache entries
|
|
//
|
|
CSynchronizedReadBlock sb(m_mrwCache);
|
|
|
|
//
|
|
// Now that the count of cache entries is stable (because
|
|
// we are in the read block) check once again that we
|
|
// are over the culling threshold. If we are not (because
|
|
// enough entries were removed before we got the lock) then
|
|
// don't cull.
|
|
//
|
|
cCacheEntries = m_cache.CItems();
|
|
if ( cCacheEntries < C_CULL_CACHE_ENTRIES )
|
|
return;
|
|
|
|
//
|
|
// We need to cull. Run through the cache gathering the access
|
|
// frequency information for each entry.
|
|
//
|
|
if (!rgci.resize(cCacheEntries * sizeof(SCullInfo)))
|
|
return;
|
|
|
|
COpGatherCullInfo opGatherCullInfo(rgci.get());
|
|
m_cache.ForEach( opGatherCullInfo );
|
|
}
|
|
|
|
//
|
|
// Now that we are out of the reader block, cache entries can be
|
|
// freely added and removed, so there's no guarantee that any of
|
|
// the cull info we just gathered is still accurate at this point.
|
|
// It's important to remember that culling is a hint-driven process.
|
|
// More strict methods would require holding locks longer, increasing
|
|
// the probability of contention.
|
|
//
|
|
//
|
|
// Sort the cull info by increasing number of cache entry hits.
|
|
//
|
|
qsort( rgci.get(),
|
|
cCacheEntries,
|
|
sizeof(SCullInfo),
|
|
reinterpret_cast<int (__cdecl *)(const void *, const void *)>(SCullInfo::Compare) );
|
|
|
|
// Run through the sorted cull info and cull entries from the cache
|
|
//
|
|
Assert( cCacheEntries >= C_CULL_CACHE_ENTRIES );
|
|
{
|
|
CSynchronizedWriteBlock sb(m_mrwCache);
|
|
|
|
for ( int iCacheEntry = 0;
|
|
iCacheEntry < C_CULL_CACHE_ENTRIES;
|
|
iCacheEntry++ )
|
|
{
|
|
m_cache.Remove( DwordKey(rgci[iCacheEntry].dwDataSet) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::HrCacheData()
|
|
//
|
|
// Add a new cache entry for the metadata for the object at the given
|
|
// access path.
|
|
//
|
|
HRESULT
|
|
CMetabase::HrCacheData( const IEcb& ecb,
|
|
LPCWSTR pwszMDPathAccess,
|
|
LPCWSTR pwszMDPathOpen,
|
|
CMDData ** ppMDData )
|
|
{
|
|
auto_ref_ptr<CMDData> pMDDataRet;
|
|
auto_heap_ptr<BYTE> pbData;
|
|
LPCWSTR pwszMDPathDataSet;
|
|
DWORD dwDataSet;
|
|
DWORD dwcMDRecords;
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Read in the raw metadata from the metabase
|
|
//
|
|
hr = HrReadMetaData( ecb,
|
|
m_pMSAdminBase.get(),
|
|
pwszMDPathAccess,
|
|
pwszMDPathOpen,
|
|
&pbData,
|
|
&dwDataSet,
|
|
&dwcMDRecords,
|
|
&pwszMDPathDataSet );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DebugTrace( "CMetabase::HrCacheData() - HrReadMetaData() failed 0x%08lX\n", hr );
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// Digest it into a new metadata object
|
|
//
|
|
pMDDataRet.take_ownership(new CMDData(pwszMDPathDataSet, dwDataSet));
|
|
if ( !pMDDataRet->FInitialize(pbData, dwcMDRecords) )
|
|
{
|
|
//
|
|
//$REVIEW We should probably log this in the event log since
|
|
//$REVIEW there is no other indication to the server admin
|
|
//$REVIEW what is wrong and this is something that an admin
|
|
//$REVIEW could fix.
|
|
//
|
|
hr = E_INVALIDARG;
|
|
DebugTrace( "CMetabase::HrCacheData() - Metadata is malformed\n" );
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// Add the new data object to the cache. Note: we don't care
|
|
// if we can't add to the cache. We already have a metadata
|
|
// object that we can return to the caller.
|
|
//
|
|
{
|
|
CSynchronizedWriteBlock sb(m_mrwCache);
|
|
|
|
if ( !m_cache.Lookup( DwordKey(dwDataSet) ) )
|
|
(void) m_cache.FAdd( DwordKey(dwDataSet), pMDDataRet );
|
|
}
|
|
|
|
//
|
|
// If the cache size has exceeded the expiration threshold then
|
|
// start culling entries until it goes below the minimum
|
|
// threshold. The ICE ensures that only the first thread to
|
|
// see the threshold exceeded will do the culling.
|
|
//
|
|
if ( (m_cache.CItems() > C_MAX_CACHE_ENTRIES) &&
|
|
TRUE == InterlockedCompareExchange(&m_lfCulling, TRUE, FALSE) )
|
|
{
|
|
//
|
|
//$REVIEW Consider culling asynchronously. I believe the current
|
|
//$REVIEW mechanism still allows us to hang onto a very large
|
|
//$REVIEW cache which is never reduced if we get hit with a
|
|
//$REVIEW burst of new entries simultaneously.
|
|
//
|
|
CullCacheEntries();
|
|
|
|
m_lfCulling = FALSE;
|
|
}
|
|
|
|
Assert( pMDDataRet.get() );
|
|
*ppMDData = pMDDataRet.relinquish();
|
|
|
|
ret:
|
|
|
|
return hr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::HrGetData()
|
|
//
|
|
// Fetch data from the metabase cache. See comment in \cal\src\inc\davmb.h
|
|
// for the distinction between pszMDPathAccess and pszMDPathOpen.
|
|
//
|
|
HRESULT
|
|
CMetabase::HrGetData( const IEcb& ecb,
|
|
LPCWSTR pwszMDPathAccess,
|
|
LPCWSTR pwszMDPathOpen,
|
|
IMDData ** ppMDData )
|
|
{
|
|
auto_ref_ptr<CMDData> pMDDataRet;
|
|
DWORD dwDataSet;
|
|
HRESULT hr;
|
|
|
|
// Fetch the data set number for this path directly from the metabase.
|
|
// Items in the metabase with the same data set number have the same data.
|
|
//
|
|
{
|
|
safe_revert sr(ecb.HitUser());
|
|
|
|
hr = m_pMSAdminBase->GetDataSetNumber(METADATA_MASTER_ROOT_HANDLE,
|
|
pwszMDPathAccess,
|
|
&dwDataSet);
|
|
if ( FAILED(hr) )
|
|
{
|
|
MBTrace( "CMetabase::HrGetData() - GetDataSetNumber() failed 0x%08lX\n", hr );
|
|
return hr;
|
|
}
|
|
|
|
MBTrace("MB: CMetabase::HrGetData() - TID %3d: Retrieved data set number 0x%08lX for path '%S'\n", GetCurrentThreadId(), dwDataSet, pwszMDPathAccess );
|
|
}
|
|
|
|
//
|
|
// If we don't care about the exact path then look for any entry
|
|
// in the cache with this data set number. If we do care then
|
|
// look for an entry in the cache with this data set number AND
|
|
// a matching path.
|
|
//
|
|
// Note: a pointer comparison here is sufficient. Callers are expected
|
|
// to use the single path version of HrMDGetData() if they want
|
|
// an metadata for an exact path. That version passes the same
|
|
// string for both pszMDPathAccess and pszMDPathOpen.
|
|
//
|
|
// Why does anyone care about an exact path match? Inheritance.
|
|
//
|
|
{
|
|
CSynchronizedReadBlock sb(m_mrwCache);
|
|
|
|
if (pwszMDPathAccess == pwszMDPathOpen)
|
|
{
|
|
MBTrace("MB: CMetabase::HrGetData() - TID %3d: Exact path match! Trying to get CMDData, dataset 0x%08lX\n", GetCurrentThreadId(), dwDataSet);
|
|
|
|
COpMatchExactPath(pwszMDPathAccess).Invoke(m_cache, dwDataSet, &pMDDataRet);
|
|
}
|
|
else
|
|
{
|
|
MBTrace("MB: CMetabase::HrGetData() - TID %3d: Not exact path match! Trying to get CMDData, dataset 0x%08lX\n", GetCurrentThreadId(), dwDataSet);
|
|
|
|
(void) m_cache.FFetch( dwDataSet, &pMDDataRet );
|
|
}
|
|
}
|
|
|
|
if ( pMDDataRet.get() )
|
|
{
|
|
MBTrace("MB: CMetabase::HrGetData() - TID %3d: Retrieved cached CMDData, data set number 0x%08lX, path '%S'\n", GetCurrentThreadId(), dwDataSet, pwszMDPathAccess );
|
|
|
|
pMDDataRet->LFUData().Touch();
|
|
}
|
|
else
|
|
{
|
|
|
|
MBTrace("MB: CMetabase::HrGetData() - TID %3d: No cached data CMDData, data set number 0x%08lX, path '%S'\n", GetCurrentThreadId(), dwDataSet, pwszMDPathAccess );
|
|
|
|
//
|
|
// We didn't find an entry in the cache, so create one.
|
|
//
|
|
// Note: nothing here prevents multiple threads from getting here
|
|
// simultaneously and attempting to cache duplicate entries. That
|
|
// is done within HrCacheData().
|
|
//
|
|
hr = HrCacheData( ecb,
|
|
pwszMDPathAccess,
|
|
pwszMDPathOpen,
|
|
pMDDataRet.load() );
|
|
if ( FAILED(hr) )
|
|
{
|
|
MBTrace( "MB: CMetabase::HrGetData() - HrCacheData() failed 0x%08lX\n", hr );
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return the data object
|
|
//
|
|
Assert( pMDDataRet.get() );
|
|
*ppMDData = pMDDataRet.relinquish();
|
|
return S_OK;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::HrOpenObject()
|
|
//
|
|
HRESULT
|
|
CMetabase::HrOpenObject( LPCWSTR pwszMDPath,
|
|
DWORD dwAccess,
|
|
DWORD dwMsecTimeout,
|
|
CMDObjectHandle * pmdoh )
|
|
{
|
|
Assert(pwszMDPath);
|
|
Assert(pmdoh);
|
|
|
|
return pmdoh->HrOpen( m_pMSAdminBase.get(),
|
|
pwszMDPath,
|
|
dwAccess,
|
|
dwMsecTimeout );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::HrOpenLowestNodeObject()
|
|
//
|
|
|
|
HRESULT
|
|
CMetabase::HrOpenLowestNodeObject( LPWSTR pwszMDPath,
|
|
DWORD dwAccess,
|
|
LPWSTR * ppwszMDPath,
|
|
CMDObjectHandle * pmdoh )
|
|
{
|
|
Assert(pwszMDPath);
|
|
Assert(ppwszMDPath);
|
|
Assert(pmdoh);
|
|
|
|
return pmdoh->HrOpenLowestNode( m_pMSAdminBase.get(),
|
|
pwszMDPath,
|
|
dwAccess,
|
|
ppwszMDPath );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::HrIsAuthorViaFrontPageNeeded()
|
|
//
|
|
// Description: Function goes directly to the metabase and checks if
|
|
// the given path is configured as "FrontPageWeb". We need
|
|
// to do that via direct read from the metabase rather than
|
|
// going through dataset cache, as as that does not work very
|
|
// well due to the fact, that we are reading inherited
|
|
// metadata and get stuck with it.
|
|
// Parameters:
|
|
//
|
|
// ecb - interface to ecb object, that will be needed for
|
|
// fetching the impersonation token for that we will need
|
|
// to impersonate as as soon as read from metabase is finished
|
|
// pwszMDPath - metabase path that we want to check out
|
|
// pfFrontPageWeb - pointer to the booleanin which the result of operation is
|
|
// returned
|
|
//
|
|
HRESULT
|
|
CMetabase::HrIsAuthorViaFrontPageNeeded(const IEcb& ecb,
|
|
LPCWSTR pwszMDPath,
|
|
BOOL * pfFrontPageWeb)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
CMDObjectHandle mdoh(ecb, m_pMSAdminBase.get());
|
|
|
|
BOOL fFrontPageWeb = FALSE;
|
|
DWORD cbData = sizeof(BOOL);
|
|
|
|
METADATA_RECORD mdrec;
|
|
|
|
Assert( pwszMDPath );
|
|
Assert( pfFrontPageWeb );
|
|
|
|
// Assume that we do not have "FrontPageWeb" set to TRUE
|
|
//
|
|
*pfFrontPageWeb = FALSE;
|
|
|
|
// We want just explicitely set data, not inherited one
|
|
//
|
|
mdrec.dwMDIdentifier = MD_FRONTPAGE_WEB;
|
|
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
|
|
mdrec.dwMDUserType = IIS_MD_UT_SERVER;
|
|
mdrec.dwMDDataType = DWORD_METADATA;
|
|
mdrec.dwMDDataLen = cbData;
|
|
mdrec.pbMDData = reinterpret_cast<PBYTE>(&fFrontPageWeb);
|
|
|
|
hr = mdoh.HrGetMetaData(pwszMDPath,
|
|
&mdrec,
|
|
&cbData);
|
|
if (FAILED(hr))
|
|
{
|
|
MBTrace( "MB: CMetabase::HrIsAuthorViaFrontPageNeeded() - CMDObjectHandle::HrGetMetaData() failed 0x%08lX\n", hr );
|
|
goto ret;
|
|
}
|
|
|
|
// If we succeeded then we should have the value in our hands
|
|
//
|
|
*pfFrontPageWeb = fFrontPageWeb;
|
|
|
|
ret:
|
|
|
|
return hr;
|
|
}
|
|
|
|
// The way IID_IMSAdminBaseSinkW is defined in IADMW.H does
|
|
// not work well with EXO. So it needs to be redefined
|
|
// here in such a way that it will work.
|
|
//
|
|
const IID IID_IMSAdminBaseSinkW = {
|
|
|
|
0xa9e69612,
|
|
0xb80d,
|
|
0x11d0,
|
|
{
|
|
0xb9, 0xb9, 0x0, 0xa0,
|
|
0xc9, 0x22, 0xe7, 0x50
|
|
}
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// FHasCachedIDs()
|
|
//
|
|
// Returns TRUE if any one of the IDs rgdwDataIDs is one of the IDs that
|
|
// we care about in CMDData::FInitialize().
|
|
//
|
|
// !!!IMPORTANT!!! The list of IDs in this function *MUST* be kept up to
|
|
// date with the cases in CMDData::FInitialize().
|
|
//
|
|
__inline BOOL
|
|
FHasCachedIDs( DWORD dwcDataIDs,
|
|
DWORD * rgdwDataIDs )
|
|
{
|
|
for ( DWORD iID = 0; iID < dwcDataIDs; iID++ )
|
|
{
|
|
switch ( rgdwDataIDs[iID] )
|
|
{
|
|
case MD_IP_SEC:
|
|
case MD_ACCESS_PERM:
|
|
case MD_IS_CONTENT_INDEXED:
|
|
case MD_FRONTPAGE_WEB:
|
|
case MD_DIRECTORY_BROWSING:
|
|
case MD_AUTHORIZATION:
|
|
case MD_DEFAULT_LOAD_FILE:
|
|
case MD_CUSTOM_ERROR:
|
|
case MD_MIME_MAP:
|
|
case MD_SCRIPT_MAPS:
|
|
case MD_APP_ISOLATED:
|
|
case MD_VR_USERNAME:
|
|
case MD_VR_PASSWORD:
|
|
case MD_HTTP_EXPIRES:
|
|
case MD_SERVER_BINDINGS:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::COpNotify::operator()
|
|
//
|
|
BOOL
|
|
CMetabase::COpNotify::operator()( const DwordKey& key,
|
|
const auto_ref_ptr<CMDData>& pMDData )
|
|
{
|
|
//
|
|
// If the path for this cache entry is a child of the path
|
|
// being notified, then set this entry's data set ID in the
|
|
// array of IDs to blow from the cache.
|
|
//
|
|
if ( !_wcsnicmp( m_pwszMDPathNotify,
|
|
pMDData->PwszMDPathDataSet(),
|
|
m_cchMDPathNotify ) )
|
|
{
|
|
Assert (m_iCacheEntry < m_cCacheEntry);
|
|
m_rgdwDataSets[m_iCacheEntry] = pMDData->DwDataSet();
|
|
m_fDataSetsFlagged = TRUE;
|
|
}
|
|
|
|
++m_iCacheEntry;
|
|
|
|
//
|
|
// ForEach() operators can cancel the iteration by returning FALSE.
|
|
// We always want to iterate over everything so return TRUE
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMetabase::OnNotify()
|
|
//
|
|
VOID
|
|
CMetabase::OnNotify( DWORD cCO,
|
|
MD_CHANGE_OBJECT_W rgCO[] )
|
|
{
|
|
INT cCacheEntries;
|
|
CStackBuffer<DWORD> rgdwDataSets;
|
|
BOOL fDataSetsFlagged;
|
|
|
|
//
|
|
// Grab a read lock on the cache and go through it
|
|
// figuring out which items we want to blow away.
|
|
//
|
|
{
|
|
CSynchronizedReadBlock sb(m_mrwCache);
|
|
|
|
cCacheEntries = m_cache.CItems();
|
|
if (!rgdwDataSets.resize(sizeof(DWORD) * cCacheEntries))
|
|
return;
|
|
|
|
memset(rgdwDataSets.get(), 0, sizeof(DWORD) * cCacheEntries);
|
|
COpNotify opNotify(cCacheEntries, rgdwDataSets.get());
|
|
for ( DWORD iCO = 0; iCO < cCO; iCO++ )
|
|
{
|
|
LPWSTR pwszMDPath = reinterpret_cast<LPWSTR>(rgCO[iCO].pszMDPath);
|
|
|
|
// Quick litmus test: ignore any change that is not
|
|
// related to anything that we would ever cache -- i.e.
|
|
// anything that is not one of the following:
|
|
//
|
|
// - The global mimemap (LM/MimeMap)
|
|
// - Anything in the W3SVC tree (LM/W3SVC/...)
|
|
//
|
|
// Also ignore MD_CHANGE_TYPE_ADD_OBJECT notifications --
|
|
// even in combination with other notifications. We simply
|
|
// don't care when something is added because we always
|
|
// read from the metabase when we don't find an item in
|
|
// the cache.
|
|
//
|
|
// Finally, ignore changes to any data that isn't interesting
|
|
// to us -- i.e. that we don't cache.
|
|
//
|
|
if ( (!_wcsnicmp(gc_wsz_Lm_MimeMap, pwszMDPath, gc_cch_Lm_MimeMap) ||
|
|
!_wcsnicmp(gc_wsz_Lm_W3Svc, pwszMDPath, gc_cch_Lm_W3Svc - 1)) &&
|
|
|
|
!(rgCO[iCO].dwMDChangeType & MD_CHANGE_TYPE_ADD_OBJECT) &&
|
|
|
|
FHasCachedIDs( rgCO[iCO].dwMDNumDataIDs,
|
|
rgCO[iCO].pdwMDDataIDs ) )
|
|
{
|
|
//
|
|
// Flag each entry in the cache whose data set corresponds
|
|
// to a path that is a child of the one being notified.
|
|
//
|
|
MBTrace ("MB: cache: flagging '%S' as dirty\n", pwszMDPath);
|
|
opNotify.Configure( pwszMDPath );
|
|
|
|
m_cache.ForEach( opNotify );
|
|
}
|
|
}
|
|
|
|
fDataSetsFlagged = opNotify.FDataSetsFlagged();
|
|
}
|
|
|
|
//
|
|
// If any data sets were flagged in our pass above then
|
|
// grab a write lock now and blow `em away.
|
|
//
|
|
// Note: we don't care about any change to the cache between the
|
|
// time we sweep above and now. If data sets are culled,
|
|
// and even re-added, between then and now, that's fine.
|
|
// The worst thing that this does is cause them to be
|
|
// faulted in again. On the flip side, any new data sets
|
|
// brought into the cache after our pass above by definition
|
|
// has more recent data, so there is no possibility of
|
|
// missing any cached entries and ending up with stale data.
|
|
//
|
|
if ( fDataSetsFlagged )
|
|
{
|
|
CSynchronizedWriteBlock sb(m_mrwCache);
|
|
|
|
for ( INT iCacheEntry = 0;
|
|
iCacheEntry < cCacheEntries;
|
|
iCacheEntry++ )
|
|
{
|
|
if ( rgdwDataSets[iCacheEntry] )
|
|
m_cache.Remove( DwordKey(rgdwDataSets[iCacheEntry]) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CNotifSink
|
|
//
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CNotifSink::SinkNotify()
|
|
//
|
|
// Metabase change notification callback
|
|
//
|
|
HRESULT STDMETHODCALLTYPE
|
|
CNotifSink::SinkNotify(/* [in] */ DWORD dwMDNumElements,
|
|
/* [size_is][in] */ MD_CHANGE_OBJECT_W __RPC_FAR pcoChangeList[ ])
|
|
{
|
|
OnNotify( dwMDNumElements,
|
|
pcoChangeList );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
VOID
|
|
CNotifSink::OnNotify( DWORD cCO,
|
|
MD_CHANGE_OBJECT_W rgCO[] )
|
|
{
|
|
// Trace out the information with which we have been called
|
|
//
|
|
#ifdef DBG
|
|
|
|
MBTrace("MB: CNotifSink::OnNotify() - TID %3d: MD_CHANGE_OBJECT_W array length 0x%08lX\n", GetCurrentThreadId(), cCO );
|
|
|
|
for ( DWORD idwElem = 0; idwElem < cCO; idwElem++ )
|
|
{
|
|
MBTrace(" Element %d:\n", idwElem );
|
|
MBTrace(" pszMDPath '%S'\n", rgCO[idwElem].pszMDPath );
|
|
MBTrace(" dwMDChangeType 0x%08lX\n", rgCO[idwElem].dwMDChangeType );
|
|
MBTrace(" dwMDNumDataIDs 0x%08lX\n", rgCO[idwElem].dwMDNumDataIDs );
|
|
for (DWORD idwID = 0; idwID < rgCO[idwElem].dwMDNumDataIDs; idwID++)
|
|
{
|
|
MBTrace(" pdwMDDataIDs[%d] is 0x%08lX\n", idwID, rgCO[idwElem].pdwMDDataIDs[idwID] );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
CMetabase::Instance().OnNotify( cCO,
|
|
rgCO );
|
|
|
|
CChildVRCache::Instance().OnNotify( cCO,
|
|
rgCO );
|
|
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// FREE Functions
|
|
//
|
|
|
|
BOOL
|
|
FMDInitialize()
|
|
{
|
|
// Instantiate the CMetabase object and initialize it.
|
|
// Note that if initialization fails, we don't destroy
|
|
// the instance. MDDeinitialize() must still be called.
|
|
//
|
|
return CMetabase::CreateInstance().FInitialize();
|
|
}
|
|
|
|
VOID
|
|
MDDeinitialize()
|
|
{
|
|
CMetabase::DestroyInstance();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// In the future we might need Copy/Rename/Delete operations
|
|
// on metabase objects.
|
|
// For Copy following steps should apply:
|
|
// a) Lock dst
|
|
// b) Kick dst and children out of cache
|
|
// c) Copy the raw metadata
|
|
// d) Unlock dst
|
|
// e) Send update notifications
|
|
//
|
|
// For Rename:
|
|
// a) Lock common parent of src and dst
|
|
// b) Kick dst and children out of cache
|
|
// c) Rename src to dst
|
|
// d) Kick src and children out of cache
|
|
// e) Unlock common parent of src and dst
|
|
// f) Send update notifications
|
|
// For Delete:
|
|
// a) Lock path
|
|
// b) Kick path and children out of cache
|
|
// c) Unlock path
|
|
// d) Send update notifications
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrMDGetData()
|
|
//
|
|
// Intended primarily for use by impls. This call fetches the metadata
|
|
// for the specified URI. If the URI is the request URI then this
|
|
// function uses the copy of the metadata cached on the ecb. This saves
|
|
// a cache lookup (and read lock) in the majority of cases.
|
|
//
|
|
HRESULT
|
|
HrMDGetData( const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
IMDData ** ppMDData )
|
|
{
|
|
SCODE sc = S_OK;
|
|
auto_heap_ptr<WCHAR> pwszMDPathURI;
|
|
auto_heap_ptr<WCHAR> pwszMDPathOpenOnHeap;
|
|
LPWSTR pwszMDPathOpen;
|
|
|
|
//
|
|
// If the URI is the request URI then we already have the data cached.
|
|
//
|
|
// Note that we only test for pointer equality here because
|
|
// typically callers will pass in THE request URI from
|
|
// the ECB rather than a copy of it.
|
|
//
|
|
if ( ecb.LpwszRequestUrl() == pwszURI )
|
|
{
|
|
*ppMDData = &ecb.MetaData();
|
|
|
|
Assert (*ppMDData);
|
|
(*ppMDData)->AddRef();
|
|
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// Map the URI to its equivalent metabase path, and make sure
|
|
// the URL is stripped before we call into the MDPath processing
|
|
//
|
|
Assert (pwszURI == PwszUrlStrippedOfPrefix (pwszURI));
|
|
|
|
pwszMDPathURI = static_cast<LPWSTR>(ExAlloc(CbMDPathW(ecb, pwszURI)));
|
|
if (NULL == pwszMDPathURI.get())
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
|
|
MDPathFromURIW(ecb, pwszURI, pwszMDPathURI);
|
|
pwszMDPathOpen = const_cast<LPWSTR>(ecb.PwszMDPathVroot());
|
|
|
|
// If the URI requested is in NOT in the current request's vroot,
|
|
// start the metabase search from the virtual server root.
|
|
//
|
|
if (_wcsnicmp(pwszMDPathURI, pwszMDPathOpen, wcslen(pwszMDPathOpen)))
|
|
{
|
|
pwszMDPathOpenOnHeap = static_cast<LPWSTR>(ExAlloc(CbMDPathW(ecb, L"")));
|
|
if (NULL == pwszMDPathOpenOnHeap.get())
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
|
|
pwszMDPathOpen = pwszMDPathOpenOnHeap.get();
|
|
|
|
MDPathFromURIW(ecb, L"", pwszMDPathOpen);
|
|
}
|
|
|
|
//
|
|
// Fetch and return the metadata
|
|
//
|
|
sc = CMetabase::Instance().HrGetData( ecb,
|
|
pwszMDPathURI,
|
|
pwszMDPathOpen,
|
|
ppMDData );
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrMDGetData()
|
|
//
|
|
// Fetch metadata for the specified metabase path.
|
|
//
|
|
HRESULT
|
|
HrMDGetData( const IEcb& ecb,
|
|
LPCWSTR pwszMDPathAccess,
|
|
LPCWSTR pwszMDPathOpen,
|
|
IMDData ** ppMDData )
|
|
{
|
|
return CMetabase::Instance().HrGetData( ecb,
|
|
pwszMDPathAccess,
|
|
pwszMDPathOpen,
|
|
ppMDData );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// DwMDChangeNumber()
|
|
//
|
|
// Get the metabase change number .
|
|
//
|
|
DWORD
|
|
DwMDChangeNumber(const IEcb * pecb)
|
|
{
|
|
return CMetabase::Instance().DwChangeNumber(pecb);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrMDOpenMetaObject()
|
|
//
|
|
// Open a metadata object, given a path
|
|
//
|
|
HRESULT
|
|
HrMDOpenMetaObject( LPCWSTR pwszMDPath,
|
|
DWORD dwAccess,
|
|
DWORD dwMsecTimeout,
|
|
CMDObjectHandle * pmdoh )
|
|
{
|
|
return CMetabase::Instance().HrOpenObject( pwszMDPath,
|
|
dwAccess,
|
|
dwMsecTimeout,
|
|
pmdoh );
|
|
}
|
|
|
|
HRESULT
|
|
HrMDOpenLowestNodeMetaObject( LPWSTR pwszMDPath,
|
|
DWORD dwAccess,
|
|
LPWSTR * ppwszMDPath,
|
|
CMDObjectHandle * pmdoh )
|
|
{
|
|
return CMetabase::Instance().HrOpenLowestNodeObject( pwszMDPath,
|
|
dwAccess,
|
|
ppwszMDPath,
|
|
pmdoh );
|
|
}
|
|
|
|
|
|
HRESULT
|
|
HrMDIsAuthorViaFrontPageNeeded(const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
BOOL * pfFrontPageWeb)
|
|
{
|
|
return CMetabase::Instance().HrIsAuthorViaFrontPageNeeded( ecb,
|
|
pwszURI,
|
|
pfFrontPageWeb );
|
|
}
|
|
|
|
// class CMetaOp -------------------------------------------------------------
|
|
//
|
|
SCODE __fastcall
|
|
CMetaOp::ScEnumOp (LPWSTR pwszMetaPath, UINT cch)
|
|
{
|
|
Assert (cch <= METADATA_MAX_NAME_LEN);
|
|
|
|
DWORD dwIndex = 0;
|
|
LPWSTR pwszKey;
|
|
SCODE sc = S_OK;
|
|
|
|
// First and formost, call out on the key handed in
|
|
//
|
|
MBTrace ("MB: CMetaOp::ScEnumOp(): calling op() on '%S'\n", pwszMetaPath);
|
|
sc = ScOp (pwszMetaPath, cch);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// If the Op() returns S_FALSE, that means the operation
|
|
// knows enough that it does not have to be called for any
|
|
// more metabase paths.
|
|
//
|
|
if (S_FALSE == sc)
|
|
goto ret;
|
|
|
|
// Then enumerate all the child nodes and recurse. To do
|
|
// this, we are going use the fact that we have been passed
|
|
// a buffer big enough to handle CCH_BUFFER_SIZE chars.
|
|
//
|
|
Assert ((cch + 1 + METADATA_MAX_NAME_LEN) <= CCH_BUFFER_SIZE);
|
|
pwszKey = pwszMetaPath + cch;
|
|
*(pwszKey++) = L'/';
|
|
*pwszKey = L'\0';
|
|
|
|
while (TRUE)
|
|
{
|
|
// Enum the next key in the set of child keys, and process it.
|
|
//
|
|
sc = m_mdoh.HrEnumKeys (pwszMetaPath, pwszKey, dwIndex);
|
|
if (FAILED (sc))
|
|
{
|
|
sc = S_OK;
|
|
break;
|
|
}
|
|
|
|
// Recurse on the new path.
|
|
//
|
|
Assert (wcslen(pwszKey) <= METADATA_MAX_NAME_LEN);
|
|
sc = ScEnumOp (pwszMetaPath, cch + 1 + static_cast<UINT>(wcslen(pwszKey)));
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// If the EnumOp() returns S_FALSE, that means the operation
|
|
// knows enough that it does not have to be called for any
|
|
// more metabase paths.
|
|
//
|
|
if (S_FALSE == sc)
|
|
goto ret;
|
|
|
|
// Increment the index to make sure the traversing continues
|
|
//
|
|
dwIndex++;
|
|
|
|
// Truncate the metapath again
|
|
//
|
|
*pwszKey = 0;
|
|
}
|
|
|
|
ret:
|
|
return sc;
|
|
}
|
|
|
|
SCODE __fastcall
|
|
CMetaOp::ScMetaOp()
|
|
{
|
|
auto_heap_ptr<WCHAR> prgwchMDPaths;
|
|
SCODE sc = S_OK;
|
|
|
|
// Initialize the metabase
|
|
//
|
|
sc = HrMDOpenMetaObject( m_pwszMetaPath,
|
|
m_fWrite ? METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE : METADATA_PERMISSION_READ,
|
|
5000,
|
|
&m_mdoh );
|
|
if (FAILED (sc))
|
|
{
|
|
// If the path is not found, then it really is
|
|
// not a problem...
|
|
//
|
|
if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == sc)
|
|
{
|
|
MBTrace ("MB: CMetaOp::ScMetaOp(): '%S' does not exist\n", m_pwszMetaPath);
|
|
return S_OK;
|
|
}
|
|
|
|
DebugTrace ("Dav: MCD: unable to initialize metabase\n");
|
|
return sc;
|
|
}
|
|
|
|
// Get the set of paths for which the metabase property
|
|
// is explicitly specified.
|
|
//
|
|
// Since the size of the buffer needed to hold the paths
|
|
// is initially unknown, guess at a reasonable size. If
|
|
// it's not large enough, then then we will fallback to
|
|
// iterating throught the tree.
|
|
//
|
|
// Either way, for each directory where the value is set
|
|
// explicitly, the call to ScOp() will be called (and in
|
|
// the fallback scenario, sometimes it won't be set).
|
|
//
|
|
prgwchMDPaths = static_cast<LPWSTR>(g_heap.Alloc(CCH_BUFFER_SIZE * sizeof(WCHAR)));
|
|
DWORD cchMDPaths = CCH_BUFFER_SIZE;
|
|
|
|
sc = m_mdoh.HrGetDataPaths( L"",
|
|
m_dwId,
|
|
m_dwType,
|
|
prgwchMDPaths,
|
|
&cchMDPaths );
|
|
if (FAILED(sc))
|
|
{
|
|
// Ok, this is the fallback position...
|
|
//
|
|
MBTrace ("MB: CMetaOp::ScMetaOp(): falling back to enumeration for op()\n");
|
|
//
|
|
// We want to enumerate all the possible metabase paths and call
|
|
// the sub-op for each. In this scenario, the sub-op is must be
|
|
// able to handle the case where the value is not explicitly set.
|
|
//
|
|
// We are first going to copy the metapath into our buffer from
|
|
// above and pass it in so that we can use it and not have to
|
|
// do any real allocations.
|
|
//
|
|
*prgwchMDPaths = 0;
|
|
sc = ScEnumOp (prgwchMDPaths, 0);
|
|
|
|
// Error or failure - we are done with processing this
|
|
// request.
|
|
//
|
|
goto ret;
|
|
}
|
|
else
|
|
{
|
|
// Woo hoo. The number/size of the paths all fit within
|
|
// the initial buffer!
|
|
//
|
|
// Go ahead and call the sub-op for each of these paths
|
|
//
|
|
LPCWSTR pwsz = prgwchMDPaths;
|
|
while (*pwsz)
|
|
{
|
|
MBTrace ("MB: CMetaOp::ScMetaOp(): calling op() on '%S'\n", pwsz);
|
|
|
|
// Call the sub-op. The sub-op is responsible for
|
|
// handling all possible errors, but can pass back
|
|
// any terminating errors.
|
|
//
|
|
UINT cch = static_cast<UINT>(wcslen (pwsz));
|
|
sc = ScOp (pwsz, cch);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// If the Op() returns S_FALSE, that means the operation
|
|
// knows enough that it does not have to be called for any
|
|
// more metabase paths.
|
|
//
|
|
if (S_FALSE == sc)
|
|
goto ret;
|
|
|
|
// Move to the next metapath
|
|
//
|
|
pwsz += cch + 1;
|
|
}
|
|
|
|
// All the explict paths have been processed. We are done
|
|
// with processing this request.
|
|
//
|
|
goto ret;
|
|
}
|
|
|
|
ret:
|
|
|
|
// Close up the metabase regardless
|
|
//
|
|
m_mdoh.Close();
|
|
return sc;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// FParseMDData()
|
|
//
|
|
// Parses a comma-delimited metadata string into fields. Any whitespace
|
|
// around the delimiters is considered insignificant and removed.
|
|
//
|
|
// Returns TRUE if the data parsed into the expected number of fields
|
|
// and FALSE otherwise.
|
|
//
|
|
// Pointers to the parsed are returned in rgpwszFields. If a string
|
|
// parses into fewer than the expected number of fields, NULLs are
|
|
// returned for all of the fields beyond the last one parsed.
|
|
//
|
|
// If a string parses into the expected number of fields then
|
|
// the last field is always just the remainder of the string beyond
|
|
// the second to last field, regardless whether the string could be
|
|
// parsed into additional fields. For example " foo , bar , baz "
|
|
// parses into three fields as "foo", "bar" and "baz", but parses
|
|
// into two fields as "foo" and "bar , baz"
|
|
//
|
|
// The total number of characters in pwszData, including the null
|
|
// terminator, is also returned in *pcchData.
|
|
//
|
|
// Note: this function MODIFIES pwszData.
|
|
//
|
|
BOOL
|
|
FParseMDData( LPWSTR pwszData,
|
|
LPWSTR rgpwszFields[],
|
|
UINT cFields,
|
|
UINT * pcchData )
|
|
{
|
|
Assert( pwszData );
|
|
Assert( pcchData );
|
|
Assert( cFields > 0 );
|
|
Assert( !IsBadWritePtr(rgpwszFields, cFields * sizeof(LPWSTR)) );
|
|
|
|
// Clear our "out" parameter
|
|
//
|
|
memset(rgpwszFields, 0, sizeof(LPWSTR) * cFields);
|
|
|
|
WCHAR * pwchDataEnd = NULL;
|
|
LPWSTR pwszField = pwszData;
|
|
BOOL fLastField = FALSE;
|
|
|
|
UINT iField = 0;
|
|
|
|
while (!fLastField)
|
|
{
|
|
WCHAR * pwch;
|
|
|
|
//
|
|
// Strip leading WS
|
|
//
|
|
while ( *pwszField && L' ' == *pwszField )
|
|
++pwszField;
|
|
|
|
//
|
|
// Locate the delimiter following the field.
|
|
// For all fields but the last field the delimiter
|
|
// is a ','. For the last field, the "delimiter"
|
|
// is the terminating null.
|
|
//
|
|
if ( cFields - 1 == iField )
|
|
{
|
|
pwch = pwszField + wcslen(pwszField);
|
|
fLastField = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pwch = wcschr(pwszField, L',');
|
|
if ( NULL == pwch )
|
|
{
|
|
//
|
|
// If we don't find a comma after the field
|
|
// then it is the last field.
|
|
//
|
|
pwch = pwszField + wcslen(pwszField);
|
|
fLastField = TRUE;
|
|
}
|
|
}
|
|
|
|
// At this point we should have found a comma
|
|
// or the null terimator after the field.
|
|
//
|
|
Assert( pwch );
|
|
|
|
pwchDataEnd = pwch;
|
|
|
|
//
|
|
// Eat trailing whitespace at the end of the
|
|
// field up to the delimiter we just found
|
|
// by backing up from the delimiter's position
|
|
// and null-terminating the field after the
|
|
// last non-whitespace character.
|
|
//
|
|
while ( pwch-- > pwszField && L' ' == *pwch )
|
|
;
|
|
|
|
*++pwch = '\0';
|
|
|
|
//
|
|
// Fill in the pointer to this field
|
|
//
|
|
rgpwszFields[iField] = pwszField;
|
|
|
|
//
|
|
// Proceed to the next field
|
|
//
|
|
pwszField = pwchDataEnd + 1;
|
|
++iField;
|
|
}
|
|
|
|
Assert( pwchDataEnd > pwszData );
|
|
|
|
*pcchData = static_cast<UINT>(pwchDataEnd - pwszData + 1);
|
|
|
|
return iField == cFields;
|
|
}
|