/* * D A V M B . C P P * * DAV metabase * * Copyright 1986-1998 Microsoft Corporation, All Rights Reserved */ #include <_davprs.h> #include // IContentTypeMap #include // ICustomErrorMap #include // 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 pcp; auto_ref_ptr 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(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 pcp; auto_ref_ptr 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(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 m_pContentTypeMap; auto_ref_ptr m_pCustomErrorMap; auto_ref_ptr m_pScriptMap; // // Buffer for the raw metadata and count of // metadata records in that buffer. // auto_heap_ptr 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& 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( 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 { // // Friend declarations required by Singleton template // friend class Singleton; // // The IMSAdminBase interface to the real metabase // auto_ref_ptr m_pMSAdminBase; // // Cache of metadata objects keyed by data set number // and a reader/writer lock to protect it. // typedef CCache > 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& 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& pMDData ); VOID Configure( LPCWSTR pwszMDPathNotify ) { // Reset our current entry index. // m_iCacheEntry = 0; m_pwszMDPathNotify = pwszMDPathNotify; m_cchMDPathNotify = static_cast(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 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 * 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& 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::CreateInstance; using Singleton::DestroyInstance; using Singleton::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(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(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 pwsz; UINT cch; if (m_pwszPath) { cchBase = static_cast(wcslen(m_pwszPath)); } if (pwszPath) { cchPath = static_cast(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 pwsz; UINT cch; if (m_pwszPath) { cchBase = static_cast(wcslen(m_pwszPath)); } if (pwszPath) { cchPath = static_cast(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(wcslen(pwszMDPathAccess) + 1); auto_heap_ptr pbBuf(static_cast(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(pbBuf + cbBuf + (cchMDPathAccess - 2) * sizeof(WCHAR)); if ( L'/' != pwszT[0] ) { pwszT[1] = L'/'; pwszT[2] = L'\0'; } // // Return the path // *ppwszMDPathDataSet = reinterpret_cast(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& 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(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(pvRecordData); break; } case MD_ACCESS_PERM: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != DWORD_METADATA ) return FALSE; m_dwAccessPerms = *static_cast(pvRecordData); break; } case MD_IS_CONTENT_INDEXED: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != DWORD_METADATA ) return FALSE; m_dwIsIndexed = *static_cast(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(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(pvRecordData); break; } case MD_AUTHORIZATION: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != DWORD_METADATA ) return FALSE; m_dwAuthorization = *static_cast(pvRecordData); break; } case MD_DEFAULT_LOAD_FILE: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != STRING_METADATA ) return FALSE; m_pwszDefaultDocList = static_cast(pvRecordData); break; } case MD_CUSTOM_ERROR: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != MULTISZ_METADATA ) return FALSE; m_pCustomErrorMap.take_ownership( NewCustomErrorMap(static_cast(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(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(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(pvRecordData); break; } case MD_VR_PASSWORD: { if ( mdrec.dwMDDataType != STRING_METADATA ) return FALSE; m_pwszVRPassword = static_cast(pvRecordData); break; } case MD_HTTP_EXPIRES: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != STRING_METADATA ) return FALSE; m_pwszExpires = static_cast(pvRecordData); break; } case MD_SERVER_BINDINGS: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != MULTISZ_METADATA ) return FALSE; m_pwszBindings = static_cast(pvRecordData); break; } case MD_VR_PATH: { Assert( mdrec.dwMDDataTag == NULL ); if ( mdrec.dwMDDataType != STRING_METADATA ) return FALSE; m_pwszVRPath = static_cast(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 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& 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(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 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(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 pMDDataRet; auto_heap_ptr 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 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(&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& 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 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(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 pwszMDPathURI; auto_heap_ptr 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(ExAlloc(CbMDPathW(ecb, pwszURI))); if (NULL == pwszMDPathURI.get()) { sc = E_OUTOFMEMORY; goto ret; } MDPathFromURIW(ecb, pwszURI, pwszMDPathURI); pwszMDPathOpen = const_cast(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(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(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 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(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(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(pwchDataEnd - pwszData + 1); return iField == cFields; }