//----------------------------------------------------------------------------- // // // File: domain.cpp // // Description: Implementation of CDomainMapping, CDomainEntry, and // CDomainMappingTable. // // The DomainMappingTable is a domain name hash table that contains the // mappings from final destination to queues. // // Author: mikeswa // // Copyright (C) 1997 Microsoft Corporation // //----------------------------------------------------------------------------- #include "aqprecmp.h" #include "aqroute.h" #include "localq.h" #include "asyncq.h" #include "mailadmq.h" #include "tran_evntlog.h" const DWORD LOCAL_DOMAIN_NAME_SIZE = 512; //Max mislabled queues in empty list, before we will clean the list const DWORD MAX_MISPLACED_QUEUES_IN_EMPTY_LIST = 100; //Callback for retry void CDomainMappingTable::SpecialRetryCallback(PVOID pvContext) { CDomainMappingTable *pdnt = (CDomainMappingTable *) pvContext; _ASSERT(pdnt); _ASSERT(DOMAIN_MAPPING_TABLE_SIG == pdnt->m_dwSignature); dwInterlockedUnsetBits(&(pdnt->m_dwFlags), DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); pdnt->ProcessSpecialLinks(0, FALSE); } //---[ ReUnreachableErrorToAqueueError ]--------------------------------------- // // // Description: // Translates a HRESULT returned from GetNextHop to one that is meaningful // to aqueue DSN generation. // Parameters: // IN HRESULT reErr -- Error from routing. // IN OUT HRESULT aqErr -- Corresponding aqueue error code. // Returns: // Nothing. // History: // GPulla created. // //----------------------------------------------------------------------------- void ReUnreachableErrorToAqueueError(HRESULT reErr, HRESULT *aqErr) { if(E_ACCESSDENIED == reErr) *aqErr = AQUEUE_E_ACCESS_DENIED; else if(HRESULT_FROM_WIN32(ERROR_MESSAGE_EXCEEDS_MAX_SIZE) == reErr) *aqErr = AQUEUE_E_MESSAGE_TOO_LARGE; else *aqErr = AQUEUE_E_NO_ROUTE; } //---[ DeinitDomainEntryIteratorFn ]-------------------------------------------- // // // Description: // Deletes and releases all internal domain info objects in table // Parameters: // IN pvContext - pointer to context (ignored) // IN pvData - data entry to look at // IN fWildcardData - TRUE if data is a wildcard entry (ignored) // OUT pfContinue - TRUE if iterator should continue to the next entry // OUT pfRemoveEntry - TRUE if entry should be deleted // Returns: // - // History: // 6/17/98 - MikeSwa Created // //----------------------------------------------------------------------------- VOID DeinitDomainEntryIteratorFn(PVOID pvContext, PVOID pvData, BOOL fWildcard, BOOL *pfContinue, BOOL *pfDelete) { CDomainEntry *pdentry = (CDomainEntry *) pvData; *pfDelete = FALSE; *pfContinue = TRUE; pdentry->HrDeinitialize(); } //---[ ReleaseDomainEntryIteratorFn ]------------------------------------------ // // // Description: // Deletes and releases all internal domain info objects in table // Parameters: // IN pvContext - pointer to context (ignored) // IN pvData - data entry to look at // IN fWildcardData - TRUE if data is a wildcard entry (ignored) // OUT pfContinue - TRUE if iterator should continue to the next entry // OUT pfRemoveEntry - TRUE if entry should be deleted // Returns: // - // History: // 6/17/98 - MikeSwa Created // //----------------------------------------------------------------------------- VOID ReleaseDomainEntryIteratorFn(PVOID pvContext, PVOID pvData, BOOL fWildcard, BOOL *pfContinue, BOOL *pfDelete) { ULONG cRefs; CDomainEntry *pdentry = (CDomainEntry *) pvData; *pfDelete = TRUE; *pfContinue = TRUE; cRefs = pdentry->Release(); _ASSERT(!cRefs && "leaking domain entries"); } //***[ CDomainMapping Methods ]************************************************ //---[ CDomainMapping::Clone ]------------------------------------------------- // // // Description: Fills the current mapping with data from another DomainMapping // // Parameters: // IN pdmap CDomainMapping to clone // // Returns: // - // //----------------------------------------------------------------------------- void CDomainMapping::Clone(IN CDomainMapping *pdmap) { TraceFunctEnterEx((LPARAM) this, "CDomainMapping::Clone"); Assert(pdmap); m_pdentryDomainID = pdmap->m_pdentryDomainID; m_pdentryQueueID = pdmap->m_pdentryQueueID; TraceFunctLeave(); } //---[ CDomainMapping::HrGetDestMsgQueue ]------------------------------------- // // // Description: Returns a pointer to the queue that this mapping points to // // Parameters: // IN paqmt Message Type to get queue for // OUT ppdmq pointer returned // // Returns: // S_OK on success // AQUEUE_E_INVALID_DOMAIN // //----------------------------------------------------------------------------- HRESULT CDomainMapping::HrGetDestMsgQueue(IN CAQMessageType *paqmt, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMapping::HrGetDestMsgQueue"); HRESULT hr = S_OK; Assert(ppdmq); if (m_pdentryQueueID == NULL) { hr = AQUEUE_E_INVALID_DOMAIN; goto Exit; } hr = m_pdentryQueueID->HrGetDestMsgQueue(paqmt, ppdmq); Exit: TraceFunctLeave(); return hr; } //***[ CDomainEntry Methods ]************************************************** //---[ CDomainEntry::CDomainEntry() ]------------------------------------------ // // // Description: CDomainEntry constructor // // Parameters: // paqinst - ptr to the virtual server object // // Returns: // - // //----------------------------------------------------------------------------- CDomainEntry::CDomainEntry(CAQSvrInst *paqinst) : m_slPrivateData("CDomainEntry") { _ASSERT(paqinst); TraceFunctEnterEx((LPARAM) this, "CDomainEntry::CDomainEntry"); //Create a mapping that is not compressed m_dmap.m_pdentryDomainID = this; m_dmap.m_pdentryQueueID = this; m_dwSignature = DOMAIN_ENTRY_SIG; //init pointers m_szDomainName = NULL; m_cbDomainName = 0; InitializeListHead(&m_liDestQueues); InitializeListHead(&m_liLinks); m_cLinks = 0; m_cQueues = 0; m_paqinst = paqinst; m_paqinst->AddRef(); TraceFunctLeave(); } //---[ CDomainEntry::~CDomainEntry() ]----------------------------------------- // // // Description: CDomainEntry destructor // // Parameters: // - // // Returns: // - // //----------------------------------------------------------------------------- CDomainEntry::~CDomainEntry() { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::~CDomainEntry"); PLIST_ENTRY pli = NULL; //used to iterate over lists CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; //Remove all DestMsgQueues from list while (!IsListEmpty(&m_liDestQueues)) { pli = m_liDestQueues.Flink; _ASSERT((pli != &m_liDestQueues) && "List Macros are broken"); pdmq = CDestMsgQueue::pdmqGetDMQFromDomainListEntry(pli); pdmq->RemoveQueueFromDomainList(); pdmq->Release(); m_cQueues--; } //Remove all links from list while (!IsListEmpty(&m_liLinks)) { pli = m_liLinks.Flink; plmq = CLinkMsgQueue::plmqGetLinkMsgQueue(pli); plmq->fRemoveLinkFromList(); plmq->Release(); m_cLinks--; _ASSERT((pli != &m_liLinks) && "List Macros are broken"); } FreePv(m_szDomainName); if (m_paqinst) m_paqinst->Release(); TraceFunctLeave(); } //---[ CDomainEntry::HrInitialize ]-------------------------------------------- // // // Description: Initilizer for CDomainEntry. This should be called BEFORE the // entry is inserted into the DMT where other threads can access it. // // Parameters: // szDomainName string of domain name for entry, will *NOT* be copied, this // object will take control of this. This will save a unneeded // buffer copy and allocation per domain entry // pdentryQueueID ptr to the primary entry for this domain (usually this) // pdmq ptr to DestMsgQueue // plmq ptr to LinkMsgQueue to allocate // Returns: // S_OK on success // E_OUTOFMEMORY if any allocation fails // // It is expected that this is only called by the DMT while creating an entry //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrInitialize(DWORD cbDomainName, LPSTR szDomainName, CDomainEntry *pdentryQueueID, CDestMsgQueue *pdmq, CLinkMsgQueue *plmq) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrInitialize"); Assert(szDomainName); Assert((pdentryQueueID == this) || (pdmq == NULL)); HRESULT hr = S_OK; m_cbDomainName = cbDomainName; m_szDomainName = szDomainName; //write domain mapping m_dmap.m_pdentryDomainID = this; m_dmap.m_pdentryQueueID = pdentryQueueID; //add the queue to our list of queues if (pdmq) { m_slPrivateData.ExclusiveLock(); m_cQueues++; pdmq->AddRef(); pdmq->InsertQueueInDomainList(&m_liDestQueues); m_slPrivateData.ExclusiveUnlock(); } if (plmq) { m_slPrivateData.ExclusiveLock(); m_cLinks++; plmq->AddRef(); plmq->InsertLinkInList(&m_liLinks); m_slPrivateData.ExclusiveUnlock(); } TraceFunctLeave(); return hr; } //---[ CDomainEntry::HrDeinitialize ]------------------------------------------ // // // Description: Deinitializer for CDomainEntry // // Parameters: // - // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrDeinitialize() { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrDeinitialize"); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; //used to iterate over lists CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; m_slPrivateData.ExclusiveLock(); while (!IsListEmpty(&m_liDestQueues)) { pli = m_liDestQueues.Flink; _ASSERT((pli != &m_liDestQueues) && "List Macros are broken"); pdmq = CDestMsgQueue::pdmqGetDMQFromDomainListEntry(pli); pdmq->HrDeinitialize(); pdmq->RemoveQueueFromDomainList(); pdmq->Release(); m_cQueues--; pdmq = NULL; } //Remove all links from list while (!IsListEmpty(&m_liLinks)) { pli = m_liLinks.Flink; plmq = CLinkMsgQueue::plmqGetLinkMsgQueue(pli); plmq->HrDeinitialize(); plmq->fRemoveLinkFromList(); plmq->Release(); m_cLinks--; _ASSERT((pli != &m_liLinks) && "List Macros are broken"); } if (m_paqinst) { m_paqinst->Release(); m_paqinst = NULL; } m_slPrivateData.ExclusiveUnlock(); TraceFunctLeave(); return hr; } //---[ CDomainEntry::HrGetDomainMapping ]-------------------------------------- // // // Description: Returns Domain Mapping for this object // // Parameters: // OUT pdmap CDomainMapping for return information // // Returns: // S_OK on success //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrGetDomainMapping(OUT CDomainMapping *pdmap) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetDomainMapping"); HRESULT hr = S_OK; _ASSERT(pdmap); pdmap->Clone(&m_dmap); TraceFunctLeave(); return S_OK; } //---[ CDomainEntry::HrGetDomainName ]---------------------------------------------- // // // Description: Copies Domain Name. Caller is responsible for freeing string // // Parameters: // OUT pszDomainName string of domain name for entry, will be copied // // Returns: // S_OK on success // E_OUTOFMEMORY if any allocation fails // // //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrGetDomainName(OUT LPSTR *pszDomainName) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetDomainName"); HRESULT hr = S_OK; Assert(pszDomainName); if (m_szDomainName == NULL) { *pszDomainName = NULL; goto Exit; } //Copy domain name *pszDomainName = (LPSTR) pvMalloc(m_cbDomainName + sizeof(CHAR)); if (*pszDomainName == NULL) { hr = E_OUTOFMEMORY; goto Exit; } strcpy(*pszDomainName, m_szDomainName); Exit: TraceFunctLeave(); return hr; } //---[ CDomainEntry::HrGetDestMsgQueue ]--------------------------------------- // // // Description: Returns a pointer to the queue that this entry points to // // Parameters: // IN paqmt Message Type to get domain for // OUT ppdmq pointer returned // // Returns: // S_OK on success // E_FAIL no queue matching message type is found // //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrGetDestMsgQueue(IN CAQMessageType *paqmt, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetDestMsgQueue"); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CDestMsgQueue *pdmq = NULL; _ASSERT(ppdmq); _ASSERT(m_dmap.m_pdentryDomainID == this); DEBUG_DO_IT(DWORD cQueues = 0); if (m_dmap.m_pdentryQueueID == m_dmap.m_pdentryDomainID) { //this must be the primary entry... scan our own list of queues m_slPrivateData.ShareLock(); pli = m_liDestQueues.Flink; while (pli != &m_liDestQueues) { _ASSERT(m_cQueues >= cQueues); DEBUG_DO_IT(cQueues++); pdmq = CDestMsgQueue::pdmqIsSameMessageType(paqmt, pli); if (pdmq) break; pli = pli->Flink; } m_slPrivateData.ShareUnlock(); if (!pdmq) hr = E_FAIL; //no such queue else { pdmq->AddRef(); *ppdmq = pdmq; } } else { //we are not primary _ASSERT(0 && "Non-primary domain entry... currently only primary entries are supported"); _ASSERT(IsListEmpty(&m_liDestQueues)); //make sure it matches the profile hr = m_dmap.m_pdentryQueueID->HrGetDestMsgQueue(paqmt, ppdmq); } TraceFunctLeave(); return hr; } //---[ CDomainEntry::HrAddUniqueDestMsgQueue ]--------------------------------- // // // Description: // Adds a queue to this entry's list of queues if a queue with the same // message type does not already exist. // // Will appropriately AddRef domain. // Parameters: // IN pdmqNew - CDestMsgQueue to add // OUT ppdmqCurrent - Set to curent CDestMsgQueue on failure // Returns: // S_OK on success // E_FAIL if a CDestMsgQueue with same Message type alread exists. // History: // 5/28/98 - MikeSwa Created // 9/8/98 - MikeSwa Modified to use AddRef/Relase for queues // //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrAddUniqueDestMsgQueue(IN CDestMsgQueue *pdmqNew, OUT CDestMsgQueue **ppdmqCurrent) { _ASSERT(pdmqNew); _ASSERT(ppdmqCurrent); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CDestMsgQueue *pdmq = NULL; CAQMessageType *paqmt = pdmqNew->paqmtGetMessageType(); DEBUG_DO_IT(DWORD cQueues = 0); *ppdmqCurrent = NULL; m_slPrivateData.ExclusiveLock(); pli = m_liDestQueues.Flink; //First look through list and make sure that there isn't already a //queue with this message type while (pli != &m_liDestQueues) { _ASSERT(m_cQueues >= cQueues); pdmq = CDestMsgQueue::pdmqIsSameMessageType(paqmt, pli); if (pdmq) { hr = E_FAIL; pdmq->AddRef(); *ppdmqCurrent = pdmq; goto Exit; } DEBUG_DO_IT(cQueues++); pli = pli->Flink; } pdmqNew->AddRef(); pdmqNew->InsertQueueInDomainList(&m_liDestQueues); m_cQueues++; Exit: m_slPrivateData.ExclusiveUnlock(); return hr; } //---[ CDomainEntry::HrGetLinkMsgQueue ]--------------------------------------- // // // Description: // Gets a link for the given schedule id // Parameters: // IN paqsched - ScheduleID to search for // OUT pplmq - returned queue // Returns: // S_OK on success // E_FAIL if no link matching the schudule ID can be found // History: // 6/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrGetLinkMsgQueue(IN CAQScheduleID *paqsched, OUT CLinkMsgQueue **pplmq) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetLinkMsgQueue"); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CLinkMsgQueue *plmq = NULL; _ASSERT(pplmq); _ASSERT(m_dmap.m_pdentryDomainID == this); DEBUG_DO_IT(DWORD cLinks = 0); m_slPrivateData.ShareLock(); pli = m_liLinks.Flink; while (pli != &m_liLinks) { _ASSERT(m_cLinks >= cLinks); DEBUG_DO_IT(cLinks++); plmq = CLinkMsgQueue::plmqIsSameScheduleID(paqsched, pli); if (plmq) { plmq->AddRef(); break; } pli = pli->Flink; } m_slPrivateData.ShareUnlock(); if (!plmq) hr = E_FAIL; //no such queue else *pplmq = plmq; TraceFunctLeave(); return hr; } //---[ CDomainEntry::HrAddUniqueLinkMsgQueue ]--------------------------------- // // // Description: // Inserts a link with a unique schedule ID // Parameters: // IN plmqNew New link to insert // OUT plmqCurrent Current link with schedule ID on insert failure // Returns: // S_OK if insert succeeds // E_FAIL if insert fails // History: // 6/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainEntry::HrAddUniqueLinkMsgQueue(IN CLinkMsgQueue *plmqNew, OUT CLinkMsgQueue **pplmqCurrent) { _ASSERT(plmqNew); _ASSERT(pplmqCurrent); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CLinkMsgQueue *plmq = NULL; CAQScheduleID *paqsched = plmqNew->paqschedGetScheduleID(); DEBUG_DO_IT(DWORD cLinks = 0); *pplmqCurrent = NULL; m_slPrivateData.ExclusiveLock(); pli = m_liLinks.Flink; //First look through list and make sure that there isn't already a //queue with this schedule ID while (pli != &m_liLinks) { _ASSERT(m_cLinks >= cLinks); plmq = CLinkMsgQueue::plmqIsSameScheduleID(paqsched, pli); if (plmq) { hr = E_FAIL; *pplmqCurrent = plmq; plmq->AddRef(); goto Exit; } DEBUG_DO_IT(cLinks++); pli = pli->Flink; } plmqNew->InsertLinkInList(&m_liLinks); plmqNew->AddRef(); m_cLinks++; Exit: m_slPrivateData.ExclusiveUnlock(); return hr; } //---[ CDomainEntry::RemoveDestMsgQueue ]-------------------------------------- // // // Description: // Removes empty DMQ from entry. // Parameters: // IN pdmq DMQ to remove from domain entry // Returns: // - // History: // 9/14/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDomainEntry::RemoveDestMsgQueue(IN CDestMsgQueue *pdmq) { _ASSERT(pdmq && "INVALID Param for internal function"); m_slPrivateData.ExclusiveLock(); pdmq->RemoveQueueFromDomainList(); pdmq->HrDeinitialize(); pdmq->Release(); m_cQueues--; m_slPrivateData.ExclusiveUnlock(); } //---[ CDomainEntry::RemoveLinkMsgQueue ]-------------------------------------- // // // Description: // Removes an empty LinkMsgQueue from the domain entry // Parameters: // IN plmq Link to remove // Returns: // - // History: // 9/14/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDomainEntry::RemoveLinkMsgQueue(IN CLinkMsgQueue *plmq) { _ASSERT(plmq && "INVALID Param for internal function"); m_slPrivateData.ExclusiveLock(); BOOL fRemove = plmq->fRemoveLinkFromList(); if (fRemove) m_cLinks--; m_slPrivateData.ExclusiveUnlock(); if (fRemove) { //do *NOT* call HrDeinitialize here since it will deadlock plmq->RemovedFromDMT(); plmq->Release(); } } //***[ CDomainMappingTable Methods ]******************************************* //---[ CDomainMappingTable::CDomainMappingTable ]------------------------------ // // // Description: CDomainMappingTable constructor // // Parameters: - // // Returns: - // // //----------------------------------------------------------------------------- CDomainMappingTable::CDomainMappingTable() : m_slPrivateData("CDomainMappingTable") { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::CDomainMappingTable"); m_paqinst = NULL; m_dwSignature = DOMAIN_MAPPING_TABLE_SIG; m_dwInternalVersion = 0; m_cOutstandingExternalShareLocks = 0; m_cThreadsForEmptyDMQList = 0; m_plmqLocal = NULL; m_plmqCurrentlyUnreachable = NULL; m_plmqUnreachable = NULL; m_pmmaqPreCategorized = NULL; m_pmmaqPreRouting = NULL; m_cSpecialRetryMinutes = 0; m_cResetRoutesRetriesPending = 0; m_dwFlags = 0; InitializeListHead(&m_liEmptyDMQHead); TraceFunctLeave(); } //---[ CDomainMappingTable::~CDomainMappingTable ]------------------------------------------------------------ // // // Description: CDomainMappingTable destructor // // Parameters: - // // Returns: - // // //----------------------------------------------------------------------------- CDomainMappingTable::~CDomainMappingTable() { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::~CDomainMappingTable"); //Remove everything from the table m_dnt.HrIterateOverSubDomains(NULL, ReleaseDomainEntryIteratorFn, NULL); if (m_paqinst) { m_paqinst->Release(); m_paqinst = NULL; } if (m_plmqLocal) m_plmqLocal->Release(); if (m_plmqCurrentlyUnreachable) m_plmqCurrentlyUnreachable->Release(); if (m_plmqUnreachable) m_plmqUnreachable->Release(); if (m_pmmaqPreCategorized) m_pmmaqPreCategorized->Release(); if (m_pmmaqPreRouting) m_pmmaqPreRouting->Release(); if (m_pmmaqPreSubmission) m_pmmaqPreSubmission->Release(); _ASSERT(!m_cOutstandingExternalShareLocks); //there should be no outstanding sharelocks TraceFunctLeave(); } //---[ CDomainMappingTable::HrInitialize ]------------------------------------- // // // Description: Performs initialization that may return an error code // // Parameters: // IN paqinst AQ Svr Inst // IN paradmq Local Async Queue (passed to local link as part // Returns: S_OK on success // // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrInitialize(CAQSvrInst *paqinst, CAsyncAdminMsgRefQueue *paradmq, CAsyncAdminMailMsgQueue *pammqPreCatQ, CAsyncAdminMailMsgQueue *pammqPreRoutingQ, CAsyncAdminMailMsgQueue *pammqPreSubmissionQ) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrInitialize"); HRESULT hr = S_OK; HRESULT hrCurrent = S_OK; _ASSERT(paqinst); m_paqinst = paqinst; m_paqinst->AddRef(); //------Link for local Queue----------------------------------------------- m_plmqLocal = new CLocalLinkMsgQueue(paradmq, g_sGuidLocalLink, m_paqinst); if (!m_plmqLocal) hr = E_OUTOFMEMORY; hrCurrent = HrInializeGlobalLink(LOCAL_LINK_NAME, sizeof(LOCAL_LINK_NAME) - sizeof(CHAR), (CLinkMsgQueue **) &m_plmqLocal, LA_KICK | LA_FREEZE | LA_THAW, LI_TYPE_LOCAL_DELIVERY); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //------Link for currently unreachable Queue------------------------------- hrCurrent = HrInializeGlobalLink(CURRENTLY_UNREACHABLE_LINK_NAME, sizeof(CURRENTLY_UNREACHABLE_LINK_NAME) - sizeof(CHAR), &m_plmqCurrentlyUnreachable, 0, LI_TYPE_CURRENTLY_UNREACHABLE); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; hrCurrent = HrInializeGlobalLink(UNREACHABLE_LINK_NAME, sizeof(UNREACHABLE_LINK_NAME) - sizeof(CHAR), &m_plmqUnreachable, 0, LI_TYPE_INTERNAL); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //-------Link for precat Queue--------------------------------------------- m_pmmaqPreCategorized = new CMailMsgAdminLink(g_sGuidPrecatLink, PRECAT_QUEUE_NAME, pammqPreCatQ, LI_TYPE_PENDING_CAT, m_paqinst); if (!m_pmmaqPreCategorized) hr = E_OUTOFMEMORY; if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //-------Link for prerouting Queue----------------------------------------- m_pmmaqPreRouting = new CMailMsgAdminLink(g_sGuidPreRoutingLink, PREROUTING_QUEUE_NAME, pammqPreRoutingQ, LI_TYPE_PENDING_ROUTING, m_paqinst); if (!m_pmmaqPreRouting) hr = E_OUTOFMEMORY; if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //-------Link for presubmissionQueue-------------------------------------- m_pmmaqPreSubmission = new CMailMsgAdminLink(g_sGuidPreSubmissionLink, PRESUBMISSION_QUEUE_NAME, pammqPreSubmissionQ, LI_TYPE_PENDING_SUBMIT, m_paqinst); if (!m_pmmaqPreSubmission) hr = E_OUTOFMEMORY; if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //------------------------------------------------------------------------- hrCurrent = m_dnt.HrInit(); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::HrDeinitialize ]----------------------------------- // // // Description: Performs cleanup that may return an error code // // Parameters: - // // Returns: S_OK on success // // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrDeinitialize() { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrDeinitialize"); HRESULT hr = S_OK; HRESULT hrCurrent = S_OK; hr = m_dnt.HrIterateOverSubDomains(NULL, DeinitDomainEntryIteratorFn, NULL); //Deinitialize global special links hrCurrent = HrDeinitializeGlobalLink((CLinkMsgQueue **) &m_plmqLocal); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; hrCurrent = HrDeinitializeGlobalLink(&m_plmqCurrentlyUnreachable); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; hrCurrent = HrDeinitializeGlobalLink(&m_plmqUnreachable); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //NOTE: This *must* come after Deinitialize of entries if (m_paqinst) { m_paqinst->Release(); m_paqinst = NULL; } TraceFunctLeave(); return hr; } //---[ HrInitialize(m_paqinst, NULL, cbLinkName, (LPSTR) szLinkName, eLinkFlagsAQSpecialLinkInfo, NULL); //Set flags so no connections will be made for this link (*pplmq)->dwModifyLinkState( LINK_STATE_PRIV_NO_NOTIFY | LINK_STATE_PRIV_NO_CONNECTION, LINK_STATE_NO_ACTION ); (*pplmq)->SetSupportedActions(dwSupportedActions); (*pplmq)->SetLinkType(dwLinkType); } else hr = E_OUTOFMEMORY; return hr; } //---[ CDomainMappingTable::HrDeinitializeGlobalLink ]------------------------- // // // Description: // Deinitializes a single global link for the DMT // Parameters: // IN OUT pplmq Link to deinitialze / set to NULL // Returns: // S_OK on success // ERROR code from CLinkMsgQueue::HrDeinitialize(); // History: // 1/27/99 - MikeSwa Created // 7/21/99 - MikeSwa Modified - removed free of link domain // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrDeinitializeGlobalLink(IN OUT CLinkMsgQueue **pplmq) { HRESULT hr = S_OK; _ASSERT(pplmq); if (pplmq && *pplmq) { hr = (*pplmq)->HrDeinitialize(); (*pplmq)->Release(); *pplmq = NULL; } return hr; } //---[ CDomainMappingTable::HrMapDomainName ]---------------------------------- // // // Description: // Looks up a DomainName in the DMT. Will create a new entry if necessary // // Parameters: // IN szDomainName Domain Name to map // IN paqmtMessageType Message type as returned by routing // IN pIMessageRouter IMessageRouter for this message // OUT pdmap Mapping returned - allocated by caller // OUT ppdmq ptr to Queue // Returns: // S_OK on success // E_OUTOFMEMORY if an allocation fails // HRESULT_FROM_WIN32(ERROR_RETRY) if mapping data changes and entire // message should be re-mapped // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrMapDomainName( IN LPSTR szDomainName, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, OUT CDomainMapping *pdmap, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrMapDomainName"); _ASSERT(pdmap); _ASSERT(ppdmq); _ASSERT(szDomainName); _ASSERT(szDomainName[0] && "unsupported config - RAID #68208"); _ASSERT(pIMessageRouter); HRESULT hr = S_OK; CDomainEntry *pdentryResult = NULL; CDomainEntry *pdentryExisting = NULL; DWORD cbDomainName = 0; CInternalDomainInfo *pIntDomainInfo= NULL; BOOL fLocal = FALSE; //Is delivery local? BOOL fWalkEmptyList= FALSE; DOMAIN_STRING strDomain; //allows quicker lookups/inserts CLinkMsgQueue *plmq = NULL; *ppdmq = NULL; cbDomainName = strlen(szDomainName)*sizeof(CHAR); INIT_DOMAIN_STRING(strDomain, cbDomainName, szDomainName); m_slPrivateData.ShareLock(); hr = m_dnt.HrFindDomainName(&strDomain, (PVOID *) &pdentryResult); // // If succeeded aquire usage lock before we give up DMT lock. // Handle failure cases after releasing lock. // if (SUCCEEDED(hr)) { pdentryResult->AddRef(); } fWalkEmptyList = fNeedToWalkEmptyQueueList(); m_slPrivateData.ShareUnlock(); // // Check and see if we need to delete empty queues. // if (fWalkEmptyList) { if (fDeleteExpiredQueues()) { //something has changes hr = HRESULT_FROM_WIN32(ERROR_RETRY); goto Exit; } } if (hr == DOMHASH_E_NO_SUCH_DOMAIN) //gotta create a new entry { DebugTrace((LPARAM) this, "Creating new DMT entry"); pdentryResult = new CDomainEntry(m_paqinst); if (NULL == pdentryResult) { hr = E_OUTOFMEMORY; goto Exit; } _ASSERT(m_paqinst); hr = m_paqinst->HrGetInternalDomainInfo(cbDomainName, szDomainName, &pIntDomainInfo); if (FAILED(hr)) { //It must match the "*" domain at least _ASSERT(AQUEUE_E_INVALID_DOMAIN != hr); goto Exit; } else { _ASSERT(pIntDomainInfo); if (pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_LOCAL_MAILBOX) { DebugTrace((LPARAM) NULL, "INFO: Local delivery queued."); fLocal = TRUE; } } //perform initialization of domain entry... create queues if needed if (fLocal) { hr = HrInitLocalDomain(pdentryResult, &strDomain, paqmtMessageType, pdmap); } else { hr = HrInitRemoteDomain(pdentryResult, &strDomain, pIntDomainInfo, paqmtMessageType, pIMessageRouter, pdmap, ppdmq, &plmq); } if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Initializing %s domain %s - hr 0x%08X", (fLocal ? "local" : "remote"), szDomainName, hr); goto Exit; } m_slPrivateData.ExclusiveLock(); hr = HrPrvInsertDomainEntry(&strDomain, pdentryResult, FALSE, &pdentryExisting); //Need to release exclusive lock in if/else clause if (SUCCEEDED(hr)) //the insertion succeeded { pdentryResult->AddRef(); m_slPrivateData.ExclusiveUnlock(); DebugTrace((LPARAM) szDomainName, "INFO: Creating new entry in DMT for domain %s", szDomainName); _ASSERT((fLocal || *ppdmq) && "Out param should be set here!"); //skip past getting value from table if (!fLocal) { hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) { // Remove DMQ from link w/o notify since we never added it (*ppdmq)->RemoveDMQFromLink(FALSE); // Release the link we couldn't use and get the // currently unreachable link plmq->Release(); plmq = plmqGetCurrentlyUnreachable(); if (plmq) { (*ppdmq)->SetRouteInfo(plmq); hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) (*ppdmq)->RemoveDMQFromLink(FALSE); } // Schedule a reset routes to clean up the currently // unreachable queue m_paqinst->ResetRoutes(RESET_NEXT_HOPS); } } goto Exit; } else if (DOMHASH_E_DOMAIN_EXISTS == hr) //another inserted first { hr = S_OK; //not really a failure DebugTrace((LPARAM) this, "Another thread inserted in the the DMT before us"); pdentryExisting->AddRef(); m_slPrivateData.ExclusiveUnlock(); _ASSERT(pdentryExisting != pdentryResult); //release entry that we could not insert and replace with entry currently //in the table pdentryResult->HrDeinitialize(); pdentryResult->Release(); pdentryResult = NULL; pdentryResult = pdentryExisting; //Release queue if we have one if (*ppdmq) { (*ppdmq)->Release(); *ppdmq = NULL; } } else { m_slPrivateData.ExclusiveUnlock(); //general failure to insert //We must deinitialize the entry to force it to release any //queues and links associated with it. pdentryResult->HrDeinitialize(); goto Exit; } } if (!*ppdmq & !fLocal) //we did not create entry in the table { _ASSERT(pdentryResult); // // Prefix wants us to to more than assert. // If HrFindDomainName() fails silently or // fails with an error other than AQUEUE_E_INVALID_DOMAIN, // will will hit this code path // if (!pdentryResult) { // // Make sure HR is set. // if (SUCCEEDED(hr)) hr = E_FAIL; goto Exit; } //Domain Name already exists in table //At this point, we need to pull an existing entry from the mapping //get domain mapping hr = pdentryResult->HrGetDomainMapping(pdmap); if (FAILED(hr)) goto Exit; //get queue hr = pdentryResult->HrGetDestMsgQueue(paqmtMessageType, ppdmq); if (FAILED(hr)) { //entry exists, but no queue for our message type _ASSERT(NULL == *ppdmq); //cannot fail if we create queue //$$TODO cache domain config on entry if (!pIntDomainInfo) { hr = m_paqinst->HrGetInternalDomainInfo(cbDomainName, szDomainName, &pIntDomainInfo); if (FAILED(hr)) { //It must match the "*" domain at least _ASSERT(AQUEUE_E_INVALID_DOMAIN != hr); goto Exit; } } _ASSERT(pIntDomainInfo); if (!(pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_LOCAL_MAILBOX)) { //this is a not a local domain entry hr = HrCreateQueueForEntry(pdentryResult, &strDomain, pIntDomainInfo, paqmtMessageType, pIMessageRouter, pdmap, ppdmq); if (FAILED(hr)) goto Exit; } else { fLocal = TRUE; } } } _ASSERT((*ppdmq || fLocal) && "Non-local domains must have queue ptrs!"); Exit: if (FAILED(hr)) //cleanup { if (pdentryResult) { pdentryResult->Release(); } if (*ppdmq) { (*ppdmq)->Release(); *ppdmq = NULL; } } else { if (*ppdmq) { // send link state notification saying that the link has // been created (*ppdmq)->SendLinkStateNotification(); } if (pdentryResult) pdentryResult->Release(); } if (plmq) plmq->Release(); if (pIntDomainInfo) pIntDomainInfo->Release(); TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::HrPrvGetDomainEntry ]------------------------------ // // // Description: // Internal private function to lookup Domain entry for given domain // Parameters: // IN cbDomainnameLength Length of string to search for // IN szDomainName Domain Name to search for // IN fDMTLocked TRUE if locks are already // OUT ppdentry Domain Entry for domain (from DMT) // Returns: // S_OK on success // AQUEUE_E_INVALID_DOMAIN if domain is not found // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrPrvGetDomainEntry(IN DWORD cbDomainNameLength, IN LPSTR szDomainName, BOOL fDMTLocked, OUT CDomainEntry **ppdentry) { HRESULT hr = S_OK; BOOL fLocked = FALSE; DOMAIN_STRING strDomain; _ASSERT(cbDomainNameLength); _ASSERT(szDomainName); _ASSERT(ppdentry); INIT_DOMAIN_STRING(strDomain, cbDomainNameLength, szDomainName); if (!fDMTLocked) { m_slPrivateData.ShareLock(); fLocked = TRUE; } hr = m_dnt.HrFindDomainName(&strDomain, (PVOID *) ppdentry); if (FAILED(hr)) { if (DOMHASH_E_NO_SUCH_DOMAIN == hr) hr = AQUEUE_E_INVALID_DOMAIN; _ASSERT(NULL == *ppdentry); goto Exit; } (*ppdentry)->AddRef(); Exit: if (fLocked) m_slPrivateData.ShareUnlock(); return hr; } //---[ CDomainMappingTable::HrPrvInsertDomainEntry ]--------------------------- // // // Description: // Private wrapper function for HrInsertDomainName // Parameters: // IN pstrDomainName Domain Name to insert in DNT // IN pdnetryNew DomainEntry to insert // IN fTreadAsWildcard TRUE if DNT to be told to treat as wildcard // OUT pdentryOld Existing DomainEntry if there is one // Returns: // // History: // 10/5/1999 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrPrvInsertDomainEntry( IN PDOMAIN_STRING pstrDomainName, IN CDomainEntry *pdentryNew, IN BOOL fTreatAsWildcard, OUT CDomainEntry **ppdentryOld) { HRESULT hr = S_OK; hr = m_dnt.HrInsertDomainName(pstrDomainName, pdentryNew, fTreatAsWildcard, (PVOID *) ppdentryOld); if (E_INVALIDARG == hr) hr = PHATQ_BAD_DOMAIN_SYNTAX; return hr; } //---[ CDomainMappingTable::HrInitLocalDomain ]-------------------------------- // // // Description: // Performs initialization needed for a local domain when an entry is // created in the DMT // Parameters: // IN OUT pdentry - entry to init // IN pStrDomain - domain name of entry // IN paqmtMessageType Message Type of message // OUT pdmap - Domain Mapping for domain // Returns: // S_OK - when all succeeds // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrInitLocalDomain( IN CDomainEntry *pdentry, IN DOMAIN_STRING *pStrDomain, IN CAQMessageType *paqmtMessageType, OUT CDomainMapping *pdmap) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrInitLocalDomain"); HRESULT hr = S_OK; LPSTR szKey = NULL; _ASSERT(pdentry); _ASSERT(pStrDomain); _ASSERT(pdmap); _ASSERT('\0' == pStrDomain->Buffer[pStrDomain->Length]); //make copy of string to store in domain entry szKey = (LPSTR) pvMalloc(pStrDomain->Length + sizeof(CHAR)); if (szKey == NULL) { hr = E_OUTOFMEMORY; goto Exit; } strcpy(szKey, pStrDomain->Buffer); //passes ownership of szKey hr = pdentry->HrInitialize(pStrDomain->Length, szKey, pdentry, NULL, NULL); if (FAILED(hr)) goto Exit; hr = pdentry->HrGetDomainMapping(pdmap); if (FAILED(hr)) goto Exit; Exit: if (FAILED(hr) && szKey) FreePv(szKey); TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::HrInitRemoteDomain ]-------------------------------- // // // Description: // Performs initialization needed for a remote domain when an entry is // created in the DMT. // Parameters: // IN pdentry - entry to init // IN pStrDomain - domain name of entry // IN pIntDomainInfo - Internal config info for domain // IN paqmtMessageType - Message type returned by routing // IN pIMessageRouter - Message Router interface for this message // OUT pdmap - Domain Mapping for domain // OUT ppdmq - destmsgqueue for domain // OUT pplmq - LinkMsgQueue that this queue should be associated with // caller should call HrAddQueue once entry is in DMT // Returns: // S_OK on success. // E_OUTOFMEMORY when allocations fail // History: // 6/24/98 - Mikeswa Modified... added pplmq param and removed call to // HrAddQueue //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrInitRemoteDomain( IN CDomainEntry *pdentry, IN DOMAIN_STRING *pStrDomain, IN CInternalDomainInfo *pIntDomainInfo, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, OUT CDomainMapping *pdmap, OUT CDestMsgQueue **ppdmq, OUT CLinkMsgQueue **pplmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrInitRemoteDomain"); HRESULT hr = S_OK; HRESULT hrRoutingDiag = S_OK; LPSTR szKey = NULL; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; BOOL fEntryInit = FALSE; _ASSERT(ppdmq); _ASSERT(pplmq); _ASSERT(pdentry); _ASSERT(pStrDomain); _ASSERT(pdmap); _ASSERT(pIMessageRouter); _ASSERT('\0' == pStrDomain->Buffer[pStrDomain->Length]); //Initialze out params *ppdmq = NULL; *pplmq = NULL; //make copy of string to store in domain entry szKey = (LPSTR) pvMalloc(pStrDomain->Length + sizeof(CHAR)); if (szKey == NULL) { hr = E_OUTOFMEMORY; goto Exit; } strcpy(szKey, pStrDomain->Buffer); hr = HrGetNextHopLink(pdentry, szKey, pStrDomain->Length, pIntDomainInfo, paqmtMessageType, pIMessageRouter, FALSE, &plmq, &hrRoutingDiag); if (FAILED(hr)) goto Exit; pdmq = new CDestMsgQueue(m_paqinst, paqmtMessageType, pIMessageRouter); if (!pdmq) { hr = E_OUTOFMEMORY; goto Exit; } //passes ownership of szKey & pdmq hr = pdentry->HrInitialize(pStrDomain->Length, szKey, pdentry, pdmq, NULL); if (FAILED(hr)) goto Exit; fEntryInit = TRUE; //we cannot delete pdmq or szKey now //get the newly created domain mapping so we can initialize the queue hr = pdentry->HrGetDomainMapping(pdmap); if (FAILED(hr)) goto Exit; //Initialize queue to use this domain mapping using the DomainMapping we just got hr = pdmq->HrInitialize(pdmap); if (FAILED(hr)) goto Exit; //Associate link with DMQ pdmq->SetRouteInfo(plmq); //Set routing error if there was one. pdmq->SetRoutingDiagnostic(hrRoutingDiag); *ppdmq = pdmq; *pplmq = plmq; Exit: //Cleanup failure cases if (FAILED(hr) && !fEntryInit) { if (szKey) FreePv(szKey); if (NULL != pdmq) { //once domain entry has been initialized, it owns pdmq pdmq->HrDeinitialize(); pdmq->Release(); _ASSERT(NULL == *ppdmq); } if (NULL != plmq) { plmq->HrDeinitialize(); } } if (plmq && !*pplmq) //we haven't passed refernce to OUT param plmq->Release(); TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::HrCreateQueueForEntry ]---------------------------- // // // Description: // Create a new queue for an already existing domain entry. // // Currently, this is done by creating a new queue and link, and // attempting to associate the queue with the domain entry. // Parameters: // IN pdentry - entry to add queue to // IN pStrDomain - domain name of entry // IN pIntDomainInfo - Internal config info for domain // IN paqmtMessageType - Message type returned by routing // IN pIMessageRouter - Message Router interface for this message // IN pdmap - Domain Mapping for domain // OUT ppdmq - destmsgqueue for domain // Returns: // S_OK on succcess // History: // 6/2/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrCreateQueueForEntry( IN CDomainEntry *pdentry, IN DOMAIN_STRING *pStrDomain, IN CInternalDomainInfo *pIntDomainInfo, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, IN CDomainMapping *pdmap, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrCreateQueueForEntry"); _ASSERT(pdentry); HRESULT hr = S_OK; HRESULT hrRoutingDiag = S_OK; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; LPSTR szKey = pdentry->szGetDomainName(); *ppdmq = NULL; _ASSERT(pStrDomain); _ASSERT(pdmap); _ASSERT(pIMessageRouter); _ASSERT('\0' == pStrDomain->Buffer[pStrDomain->Length]); hr = HrGetNextHopLink(pdentry, szKey, pStrDomain->Length, pIntDomainInfo, paqmtMessageType, pIMessageRouter, FALSE, &plmq, &hrRoutingDiag); if (FAILED(hr)) goto Exit; pdmq = new CDestMsgQueue(m_paqinst, paqmtMessageType, pIMessageRouter); if (NULL == pdmq) { hr = E_OUTOFMEMORY; goto Exit; } DebugTrace((LPARAM) szKey, "INFO: Creating new Destination Message Queue for domain %s", szKey); hr = pdmq->HrInitialize(pdmap); if (FAILED(hr)) goto Exit; //Associate link with DMQ pdmq->SetRouteInfo(plmq); //Set routing error if there was one. pdmq->SetRoutingDiagnostic(hrRoutingDiag); //Now attempt to associate newly created queue/link pair with domain entry hr = pdentry->HrAddUniqueDestMsgQueue(pdmq, ppdmq); if (SUCCEEDED(hr)) { *ppdmq = pdmq; //Only add the queue in this case... if a queue is already in the entry, then //the other thread must have already (or soon will) call HrAddQueue... we //should not call it twice. hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) { // Remove DMQ from link w/o notify since we never added it (*ppdmq)->RemoveDMQFromLink(FALSE); // Release the link we couldn't use and get the // currently unreachable link plmq->Release(); plmq = plmqGetCurrentlyUnreachable(); if (plmq) { pdmq->SetRouteInfo(plmq); hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) { (*ppdmq)->RemoveDMQFromLink(FALSE); } } // Schedule a reset routes to clean up the currently // unreachable queue m_paqinst->ResetRoutes(RESET_NEXT_HOPS); // If we still have a failed hr, goto exit if (FAILED(hr)) goto Exit; } } else { DebugTrace((LPARAM) this, "INFO: Thread swap while trying to add queue for domain %s", szKey); _ASSERT(*ppdmq != pdmq); _ASSERT(*ppdmq && "HrAddUniqueDestMsgQueue failed without returning an error code"); hr = S_OK; //return new value //Remove link from DMQ... since we will never call HrAddQueue //don't notify link since it was never added pdmq->RemoveDMQFromLink(FALSE); } Exit: //Cleanup failure cases (including if queue created is not used) if (FAILED(hr) || (*ppdmq != pdmq)) { if (NULL != pdmq) { //once domain entry has been initialized, it owns pdmq pdmq->Release(); } } if (NULL != plmq) plmq->Release(); TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::LogDomainUnreachableEvent] ------------------------ // // // Description: // Logs an event for an unreachable domain // Parameters: // IN fCurrentlyUnreachable Is the domain currently unreachable or // completely unreachable? // IN szDomain Final destination domain // History: // 3/8/99 - AWetmore Created // //----------------------------------------------------------------------------- void CDomainMappingTable::LogDomainUnreachableEvent(BOOL fCurrentlyUnreachable, LPCSTR szDomain) { DWORD dwMessageId = (fCurrentlyUnreachable) ? AQUEUE_DOMAIN_CURRENTLY_UNREACHABLE : AQUEUE_DOMAIN_UNREACHABLE; LPSTR rgszSubStrings[1]; rgszSubStrings[0] = (char*)szDomain; if (m_paqinst) { m_paqinst->HrTriggerLogEvent( dwMessageId, // Message ID TRAN_CAT_QUEUE_ENGINE, // Category 1, // Word count of substring (const char**)&rgszSubStrings[0], // Substring EVENTLOG_WARNING_TYPE, // Type of the message 0, // No error code LOGEVENT_LEVEL_MINIMUM, // Logging level "", // Key to identify this event LOGEVENT_FLAG_PERIODIC // Event logging option ); } } //---[ CDomainMappingTable::HrGetNextHopLink ]------------------------------ // // // Description: // Creates and initializes the CLinkMsgQueue object for this message // (if neccessary). Calls router to get next hop info // Parameters: // IN pdentry Entry that is being initialized for destination // IN szDomain Final destination domain // IN cbDomain string length in bytes of domain (without \0) // IN pIntDomainInfo Domain info for final destination domain // IN paqmtMessageType Message type of this message // IN pIMessageRouter Routing interface for this message // IN fDMTLocked TRUE if DMT is already locked // OUT pplmq Resulting link msg queue // OUT phrRoutingDiag If next hop is unreachable, this tells us why // Returns: // S_OK on success // History: // 6/19/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrGetNextHopLink( IN CDomainEntry *pdentry, IN LPSTR szDomain, IN DWORD cbDomain, IN CInternalDomainInfo *pIntDomainInfo, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, IN BOOL fDMTLocked, OUT CLinkMsgQueue **pplmq, OUT HRESULT *phrRoutingDiag) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrGetNextHopLink"); HRESULT hr = S_OK; BOOL fCalledGetNextHop = FALSE; BOOL fValidSMTP = FALSE; BOOL fOwnsScheduleId = FALSE; LPSTR szRouteAddressType = NULL; LPSTR szRouteAddress = NULL; LPSTR szRouteAddressClass = NULL; LPSTR szConnectorName = NULL; DWORD dwScheduleID = 0; DWORD dwNextHopType = 0; CLinkMsgQueue *plmq = NULL; CLinkMsgQueue *plmqTmp = NULL; LPSTR szOwnedDomain = NULL; // string buffer that is "owned" by an entry CDomainEntry *pdentryLink = NULL; //entry for link CDomainEntry *pdentryTmp = NULL; DOMAIN_STRING strNextHop; DWORD cbRouteAddress = 0; CAQScheduleID aqsched; IMessageRouterLinkStateNotification *pILinkStateNotify = NULL; LinkFlags lf = eLinkFlagsExternalSMTPLinkInfo; *phrRoutingDiag = S_OK; _ASSERT(pdentry); _ASSERT(szDomain); _ASSERT(pIntDomainInfo); _ASSERT(paqmtMessageType); _ASSERT(pIMessageRouter); _ASSERT(pplmq); hr = pIMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); if (FAILED(hr)) { pILinkStateNotify = NULL; hr = S_OK; } //If we can route this domain.... call router to get next hop //We do not route TURN/ETRN domains... or local drop domains //Also check to see if the domain is configured as a local domain... //if it is, return the local link if (DOMAIN_INFO_LOCAL_MAILBOX & pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags) { //The likely scenario of this happening is if a domain was previously //configured as remote and then reconfigured as local. m_plmqLocal->AddRef(); *pplmq = m_plmqLocal; goto Exit; } else if (!(pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY | DOMAIN_INFO_LOCAL_DROP))) { hr = pIMessageRouter->GetNextHop(MTI_ROUTING_ADDRESS_TYPE_SMTP, szDomain, paqmtMessageType->dwGetMessageType(), &szRouteAddressType, &szRouteAddress, &dwScheduleID, &szRouteAddressClass, &szConnectorName, &dwNextHopType); fCalledGetNextHop = TRUE; *pplmq = NULL; if(MTI_NEXT_HOP_TYPE_UNREACHABLE == dwNextHopType) { //If the next hop is unreachable, store the reason for the unreachable //error into *phrRoutingDiag (which is used by aqueue DSN generation). const char *rgszStrings[2] = { szDomain, NULL }; if (m_paqinst) { m_paqinst->HrTriggerLogEvent( PHATQ_UNREACHABLE_DOMAIN, // Message ID TRAN_CAT_QUEUE_ENGINE, // Category 2, // Word count of substring rgszStrings, // Substring EVENTLOG_WARNING_TYPE, // Type of the message hr, // error code LOGEVENT_LEVEL_FIELD_ENGINEERING, // Logging level "phatq", // key to this event LOGEVENT_FLAG_PERIODIC, // Logging option 1, // index of format message string in rgszStrings GetModuleHandle(AQ_MODULE_NAME) // module handle to format a message ); } ReUnreachableErrorToAqueueError(hr, phrRoutingDiag); hr = S_OK; } if (FAILED(hr)) { RequestResetRoutesRetryIfNecessary(); ErrorTrace((LPARAM) this, "GetNextHop failed with hr - 0x%08X", hr); //treat all failures as a routing currently unreachable hr = S_OK; dwNextHopType = MTI_NEXT_HOP_TYPE_CURRENTLY_UNREACHABLE; } if (MTI_NEXT_HOP_TYPE_CURRENTLY_UNREACHABLE == dwNextHopType) { LogDomainUnreachableEvent(TRUE, szDomain); *pplmq = m_plmqCurrentlyUnreachable; } else if (MTI_NEXT_HOP_TYPE_UNREACHABLE == dwNextHopType) { LogDomainUnreachableEvent(FALSE, szDomain); *pplmq = m_plmqUnreachable; } else if ((MTI_NEXT_HOP_TYPE_SAME_VIRTUAL_SERVER == dwNextHopType) || (szRouteAddressType && lstrcmpi(MTI_ROUTING_ADDRESS_TYPE_SMTP, szRouteAddressType))) { //Handle any cases that might be considered local delivery *pplmq = m_plmqLocal; } else if (!szRouteAddressType || ('\0' == *szRouteAddressType) || !szRouteAddress || ('\0' == *szRouteAddress)) { //This is a bogus combination of values... try try again hr = E_FAIL; goto Exit; } else { fValidSMTP = TRUE; fOwnsScheduleId = TRUE; //At this point we should have valid SMTP values for the address _ASSERT(szRouteAddressType); _ASSERT(szRouteAddress); _ASSERT(!lstrcmpi(MTI_ROUTING_ADDRESS_TYPE_SMTP, szRouteAddressType)); if (MTI_NEXT_HOP_TYPE_PEER_SMTP1_BYPASS_CONFIG_LOOKUP == dwNextHopType || MTI_NEXT_HOP_TYPE_PEER_SMTP2_BYPASS_CONFIG_LOOKUP == dwNextHopType) { lf = eLinkFlagsInternalSMTPLinkInfo; } } if (!fValidSMTP) { //Must be going to one of them-there global queues. hr = S_OK; if (*pplmq) (*pplmq)->AddRef(); else hr = E_FAIL; //Our work here is done goto Exit; } } if ((!fCalledGetNextHop) || (!lstrcmpi(szDomain, szRouteAddress))) { //final destination and next hop are the same DebugTrace((LPARAM) this, "DEBUG: Routing case 1 - same next hop and final dest"); // See if there is already a link for this schedule ID aqsched.Init(pIMessageRouter, dwScheduleID); hr = pdentry->HrGetLinkMsgQueue(&aqsched, &plmq); if (FAILED(hr)) { hr = S_OK; //link does not exist for this schedule id yet plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; } fOwnsScheduleId = FALSE; //link now owns it DebugTrace((LPARAM) szDomain, "INFO: Creating new Link for domain %s", szDomain); hr = plmq->HrInitialize(m_paqinst, pdentry, cbDomain, szDomain, lf, szConnectorName); if (FAILED(hr)) goto Exit; hr = pdentry->HrAddUniqueLinkMsgQueue(plmq, &plmqTmp); if (FAILED(hr)) { //Another link was inserted since we called get link msg queue DebugTrace((LPARAM) this, "DEBUG: Routing case 2(a) - next hop link created by other thread"); _ASSERT(plmqTmp); plmq->HrDeinitialize(); plmq->Release(); plmq = plmqTmp; hr = S_OK; } } else { DebugTrace((LPARAM) this, "DEBUG: Routing case 2(b) - next hop link created by other thread"); } } else { //next hop is different from final destination cbRouteAddress = strlen(szRouteAddress); //First see if there is an entry for this link hr = HrPrvGetDomainEntry(cbRouteAddress, szRouteAddress, fDMTLocked, &pdentryLink); if (AQUEUE_E_INVALID_DOMAIN == hr) { //an entry for this link does not exist... add one for this link hr = S_OK; DebugTrace((LPARAM) this, "DEBUG: Routing case 3 - next hop entry does not exist"); szOwnedDomain = (LPSTR) pvMalloc(sizeof(CHAR)*(cbRouteAddress+1)); if (!szOwnedDomain) { hr = E_OUTOFMEMORY; goto Exit; } lstrcpy(szOwnedDomain, szRouteAddress); pdentryLink = new CDomainEntry(m_paqinst); if (!pdentryLink) { hr = E_OUTOFMEMORY; goto Exit; } plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; } fOwnsScheduleId = FALSE; //link now owns it //passes ownership of szOwnedDomain hr = pdentryLink->HrInitialize(cbRouteAddress, szOwnedDomain, pdentryLink, NULL, plmq); if (FAILED(hr)) goto Exit; hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, lf, szConnectorName); if (FAILED(hr)) goto Exit; //insert entry in DMT strNextHop.Length = (USHORT) cbRouteAddress; strNextHop.Buffer = szOwnedDomain; strNextHop.MaximumLength = (USHORT) cbRouteAddress; if (!fDMTLocked) m_slPrivateData.ExclusiveLock(); hr = HrPrvInsertDomainEntry(&strNextHop, pdentryLink, FALSE, &pdentryTmp); if (hr == DOMHASH_E_DOMAIN_EXISTS) { DebugTrace((LPARAM) this, "DEBUG: Routing case 4 - next hop entry did not exist... inserted by other thread"); plmq->Release(); plmq = NULL; pdentryTmp->AddRef(); pdentryLink->HrDeinitialize(); pdentryLink->Release(); pdentryLink = pdentryTmp; hr = S_OK; //Will fall through to case as if an entry was found by HrGetDomainEntry } else if (SUCCEEDED(hr)) { pdentryLink->AddRef(); } if (!fDMTLocked) m_slPrivateData.ExclusiveUnlock(); if (FAILED(hr)) goto Exit; } else if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: General DMT failure - hr 0x%08X", hr); //general failure... bail goto Exit; } if (!plmq) { DebugTrace((LPARAM) this, "DEBUG: Routing case 5 - next hop entry exists"); //An entry exists for this next hop... use link if possible // 1 - Get link for this schedule ID.. if it exists use it // 2 - Create another link and attempt to insert it _ASSERT(pdentryLink); aqsched.Init(pIMessageRouter, dwScheduleID); hr = pdentryLink->HrGetLinkMsgQueue(&aqsched, &plmq); if (FAILED(hr)) { hr = S_OK; //link does not exist for this schedule id yet DebugTrace((LPARAM) this, "DEBUG: Routing case 6 - next hop link does not exist"); szOwnedDomain = pdentryLink->szGetDomainName(); plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; } fOwnsScheduleId = FALSE; //link now owns it hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, lf, szConnectorName); if (FAILED(hr)) goto Exit; hr = pdentryLink->HrAddUniqueLinkMsgQueue(plmq, &plmqTmp); if (FAILED(hr)) { //Another link was inserted since we called get link msg queue DebugTrace((LPARAM) this, "DEBUG: Routing case 7 - next hop link created by other thread"); _ASSERT(plmqTmp); plmq->Release(); plmq = plmqTmp; hr = S_OK; } } else { DebugTrace((LPARAM) this, "DEBUG: Routing case 8 - next hop link exists"); } } } _ASSERT(plmq && "We should have allocated a link by this point"); *pplmq = plmq; Exit: if (pdentryLink) pdentryLink->Release(); if (fCalledGetNextHop) { // // If we have not passed the schedule ID on to a link, we // must notify routing that we are not using it (to avoid a leak). // This needs to be be done *before* we release the strings // and routing interfaces. If we hit this case, we have // either failed to create a link, or another link has // been created by another thread. // if (fOwnsScheduleId && pILinkStateNotify && pIMessageRouter) { FILETIME ftNotUsed = {0,0}; DWORD dwSetNotUsed = LINK_STATE_NO_ACTION; DWORD dwUnsetNotUsed = LINK_STATE_NO_ACTION; pILinkStateNotify->LinkStateNotify( szDomain, pIMessageRouter->GetTransportSinkID(), dwScheduleID, szConnectorName, LINK_STATE_LINK_NO_LONGER_USED, 0, //consecutive failures &ftNotUsed, &dwSetNotUsed, &dwUnsetNotUsed); } // // Free Strings returned by GetNextHop // _VERIFY(SUCCEEDED(pIMessageRouter->GetNextHopFree( MTI_ROUTING_ADDRESS_TYPE_SMTP, szDomain, szConnectorName, szRouteAddressType, szRouteAddress, szRouteAddressClass))); } if (pILinkStateNotify) pILinkStateNotify->Release(); if (FAILED(hr) && plmq) plmq->Release(); TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::HrGetOrCreateLink ]-------------------------------- // // // Description: // Gets or creates a link object for a domain name // Parameters: // IN szRouteAddress Final destination domain // IN cbRouteAddress string length in bytes of domain (without \0) // IN dwScheduleID Schedule ID for link (used in create) // IN szConnectorName Name (stringized GUID) of connector in DS // IN pIMessageRouter Routing interface for this message (used in create) // IN fCreateIfNotExist Create the link if it doesn't exist? // OUT pplmq Resulting link msg queue // OUT pfRemoveOwnedSchedule FALSE if a new link was created, TRUE on errors // and in case the link was addref'ed. // Returns: // S_OK on success // History: // 7/6/1999 - AWetmore Created // 12/30/1999 - MikeSwa Modified to not notify until connection attempt // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrGetOrCreateLink( IN LPSTR szRouteAddress, IN DWORD cbRouteAddress, IN DWORD dwScheduleID, IN LPSTR szConnectorName, IN IMessageRouter *pIMessageRouter, IN BOOL fCreateIfNotExist, IN DWORD linkFlags, OUT CLinkMsgQueue **pplmq, OUT BOOL *pfRemoveOwnedSchedule) { TraceFunctEnter("CDomainMappingTable::HrGetOrCreateLink"); HRESULT hr = S_OK; BOOL fValidSMTP = FALSE; LPSTR szRouteAddressType = NULL; LPSTR szRouteAddressClass = NULL; DWORD dwNextHopType = 0; CLinkMsgQueue *plmq = NULL; CLinkMsgQueue *plmqTmp = NULL; LPSTR szOwnedDomain = NULL; // string buffer that is "owned" by an entry CDomainEntry *pdentryLink = NULL; //entry for link CDomainEntry *pdentryTmp = NULL; DOMAIN_STRING strNextHop; CAQScheduleID aqsched; IMessageRouterLinkStateNotification *pILinkStateNotify = NULL; CAQStats aqstats; *pfRemoveOwnedSchedule = TRUE; _ASSERT(szRouteAddress); _ASSERT(pplmq); //First see if there is an entry for this link hr = HrPrvGetDomainEntry(cbRouteAddress, szRouteAddress, FALSE, &pdentryLink); if (AQUEUE_E_INVALID_DOMAIN == hr && fCreateIfNotExist) { _ASSERT(pIMessageRouter); hr = pIMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); if (FAILED(hr)) { pILinkStateNotify = NULL; goto Exit; } //an entry for this link does not exist... add one for this link hr = S_OK; DebugTrace((LPARAM) this, "DEBUG: Routing case 3 - next hop entry does not exist"); szOwnedDomain = (LPSTR) pvMalloc(sizeof(CHAR)*(cbRouteAddress+1)); if (!szOwnedDomain) { hr = E_OUTOFMEMORY; goto Exit; } lstrcpy(szOwnedDomain, szRouteAddress); pdentryLink = new CDomainEntry(m_paqinst); if (!pdentryLink) { hr = E_OUTOFMEMORY; goto Exit; } plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; } *pfRemoveOwnedSchedule = FALSE; //passes ownership of szOwnedDomain hr = pdentryLink->HrInitialize(cbRouteAddress, szOwnedDomain, pdentryLink, NULL, plmq); if (FAILED(hr)) goto Exit; hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, (LinkFlags) linkFlags, szConnectorName); if (FAILED(hr)) goto Exit; //insert entry in DMT strNextHop.Length = (USHORT) cbRouteAddress; strNextHop.Buffer = szOwnedDomain; strNextHop.MaximumLength = (USHORT) cbRouteAddress; m_slPrivateData.ExclusiveLock(); hr = HrPrvInsertDomainEntry(&strNextHop, pdentryLink, FALSE, &pdentryTmp); if (hr == DOMHASH_E_DOMAIN_EXISTS) { DebugTrace((LPARAM) this, "DEBUG: Routing case 4 - next hop entry did not exist... inserted by other thread"); plmq->Release(); plmq = NULL; pdentryTmp->AddRef(); pdentryLink->HrDeinitialize(); pdentryLink->Release(); pdentryLink = pdentryTmp; hr = S_OK; //Will fall through to case as if an entry was found by HrGetDomainEntry } else if (SUCCEEDED(hr)) { pdentryLink->AddRef(); } m_slPrivateData.ExclusiveUnlock(); if (FAILED(hr)) goto Exit; } else if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: General DMT failure - hr 0x%08X", hr); //general failure... bail goto Exit; } if (!plmq) { _ASSERT(pdentryLink); DebugTrace((LPARAM) this, "DEBUG: Routing case 5 - next hop entry exists"); //An entry exists for this next hop... use link if possible // 1 - Get link for this schedule ID.. if it exists use it // 2 - Create another link and attempt to insert it _ASSERT(pdentryLink); aqsched.Init(pIMessageRouter, dwScheduleID); hr = pdentryLink->HrGetLinkMsgQueue(&aqsched, &plmq); if (FAILED(hr) && fCreateIfNotExist) { hr = S_OK; //link does not exist for this schedule id yet DebugTrace((LPARAM) this, "DEBUG: Routing case 6 - next hop link does not exist"); szOwnedDomain = pdentryLink->szGetDomainName(); if (!pILinkStateNotify) { _ASSERT(pIMessageRouter); hr = pIMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); if (FAILED(hr)) { pILinkStateNotify = NULL; goto Exit; } } plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; } *pfRemoveOwnedSchedule = FALSE; hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, (LinkFlags) linkFlags, szConnectorName); if (FAILED(hr)) goto Exit; hr = pdentryLink->HrAddUniqueLinkMsgQueue(plmq, &plmqTmp); if (FAILED(hr)) { //Another link was inserted since we called get link msg queue DebugTrace((LPARAM) this, "DEBUG: Routing case 7 - next hop link created by other thread"); _ASSERT(plmqTmp); plmq->Release(); plmq = plmqTmp; hr = S_OK; } } else { DebugTrace((LPARAM) this, "DEBUG: Routing case 8 - next hop link exists"); } } _ASSERT(plmq && "We should have allocated a link by this point"); if (plmq) { //We count a SetLinkState as a link state notification.... do not //notify again before attempting a connection or we will break link //state notifications. plmq->dwModifyLinkState(LINK_STATE_PRIV_HAVE_SENT_NOTIFICATION, LINK_STATE_NO_ACTION); aqstats.m_dwNotifyType = NotifyTypeNewLink | NotifyTypeLinkMsgQueue; aqstats.m_plmq = plmq; hr = m_paqinst->HrNotify(&aqstats, TRUE); } if (SUCCEEDED(hr)) *pplmq = plmq; Exit: if (pdentryLink) pdentryLink->Release(); if (pILinkStateNotify) pILinkStateNotify->Release(); if (FAILED(hr) && plmq) plmq->Release(); TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::AddDMQToEmptyList ]-------------------------------- // // // Description: // Used by DMQ to add itself to the list of empty queues. This function // will aquire (and release) the appropriate locks // Parameters: // IN pdmq DestMsgQueue to add to list // Returns: // - // History: // 9/12/98 - MikeSwa Created // 5/5/99 - MikeSwa Changed to TryExclusiveLock to avoid potential // deadlock. // //----------------------------------------------------------------------------- void CDomainMappingTable::AddDMQToEmptyList(CDestMsgQueue *pdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::AddDMQToEmptyList"); if (m_slPrivateData.TryExclusiveLock()) { pdmq->InsertQueueInEmptyQueueList(&m_liEmptyDMQHead); m_slPrivateData.ExclusiveUnlock(); } else { DebugTrace((LPARAM) this, "AddDMQToEmptyList could not get m_slPrivateData Lock"); } TraceFunctLeave(); } //---[ CDomainMappingTable::fNeedToWalkEmptyQueueList ]------------------------ // // // Description: // Checks list of Empty queues to see if we need to call // fDeleteExpiredQueues(). The caller of this function *must* have // m_slPrivateData is shared mode, if it returns TRUE, the caller *must* // release the lock and call fDeleteExpiredQueues() (which will aquire // the lock Exclusively) // Parameters: // - // Returns: // TRUE if fDeleteExpiredQueues should be called. // History: // 9/12/98 - MikeSwa Created // 6/27/2000 - MikeSwa fixed short-circuit logic // //----------------------------------------------------------------------------- BOOL CDomainMappingTable::fNeedToWalkEmptyQueueList() { BOOL fRet = FALSE; PLIST_ENTRY pli = m_liEmptyDMQHead.Flink; CDestMsgQueue *pdmq = NULL; DWORD dwDMQState = 0; DWORD cMisplacedQueues = 0; //# of queues in list that should not be _ASSERT(pli); // Only one thread at a time should be searching. if (1 != InterlockedIncrement((PLONG) &m_cThreadsForEmptyDMQList)) { InterlockedDecrement((PLONG) &m_cThreadsForEmptyDMQList); return FALSE; } if(m_dwFlags & DMT_FLAGS_RESET_ROUTES_IN_PROGRESS) return fRet; // we shouldn't remove queues during reset routes while (&m_liEmptyDMQHead != pli) { pdmq = CDestMsgQueue::pdmqGetDMQFromEmptyListEntry(pli); _ASSERT(pdmq); dwDMQState = pdmq->dwGetDMQState(); //See if it is empty and expired.. if so, we have a winner if (dwDMQState & CDestMsgQueue::DMQ_EMPTY) { if (dwDMQState & CDestMsgQueue::DMQ_EXPIRED) { fRet = TRUE; break; } } else { // // The queue is no longer empty... we will remove it // from this list the next time we have the exclusive // lock. // cMisplacedQueues++; // // If there are a large number of non-empty DMQs, we // want wish to return TRUE even though there are no DMQs to // delete... just so we can clean the list of non-empty DMQs. // if (MAX_MISPLACED_QUEUES_IN_EMPTY_LIST < cMisplacedQueues) { fRet = TRUE; break; } } pli = pli->Flink; } // If we decide to delete queues we keep this count until we have done // the work, if we decide not to then we release the count so someone // else will walk the list again if(fRet == FALSE) { InterlockedDecrement((PLONG) &m_cThreadsForEmptyDMQList); } //NOTE: The reason there may be non-empty queues in this list, is that //we cannot remove the queues from the list when we ENQUEUE a message //because it would deadlock (we already have the m_slPrivateData lock //in shared mode), and removing the queue requires this lock in exclusive //mode. return fRet; } //---[ CDomainMappingTable::fDeleteExpiredQueues ]----------------------------- // // // Description: // Removes DMQs from empty list. DMQs will be deleted if they have expired // and do not have any messages on them. Non-empty DMQs will be removed // from the list as well. // // The DMT m_slPrivateData lock should *not* be held when this is called. // This function will aquire it exclusively. // // Parameters: // - // Returns: // TRUE if any queues, links, or entries were deleted // FALSE if no queues were deleted // History: // 9/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CDomainMappingTable::fDeleteExpiredQueues() { PLIST_ENTRY pli = NULL; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; DWORD dwDMQState = 0; CDomainMapping *pdmap = NULL; CDomainEntry *pdentry = NULL; CDomainEntry *pdentryOld = NULL; BOOL fRemovedQueues = FALSE; HRESULT hr = S_OK; DOMAIN_STRING strDomain; // Don't attempt this if it would require waiting for a lock. This // decreases contention and also prevents multiple threads from trying // to do this work if (!m_slPrivateData.TryExclusiveLock()) goto Exit; pli = m_liEmptyDMQHead.Flink; while (&m_liEmptyDMQHead != pli) { _ASSERT(pli); pdmq = CDestMsgQueue::pdmqGetDMQFromEmptyListEntry(pli); _ASSERT(pdmq); dwDMQState = pdmq->dwGetDMQState(); if (!(dwDMQState & CDestMsgQueue::DMQ_EMPTY)) { //If it is not empty - remove it from the list pli = pli->Flink; pdmq->RemoveQueueFromEmptyQueueList(); continue; } else if (!(dwDMQState & CDestMsgQueue::DMQ_EXPIRED)) { //If this queue hasn't expired... check the next to see if it is empty pli = pli->Flink; continue; } else { //We need to delete this DMQ pli = pli->Flink; //get next LIST_ENTRY before we delete the queue //Add a reference to the DMQ, so we can guarantee its lifespan pdmq->AddRef(); //Remove the queue from the list of empty queues pdmq->RemoveQueueFromEmptyQueueList(); //Get the domain mapping (and domain entry) for this DMQ pdmq->GetDomainMapping(&pdmap); _ASSERT(pdmap); pdentry = pdmap->pdentryGetQueueEntry(); //Remove the DMQ from its associated link plmq = pdmq->plmqGetLink(); pdmq->RemoveDMQFromLink(TRUE); if (plmq) { //Remove the link from the DMT if it is empty plmq->RemoveLinkIfEmpty(); plmq->Release(); plmq = NULL; } //Now that we have the domain entry, we can remove it the DMQ //from it. _ASSERT(pdentry); pdentry->RemoveDestMsgQueue(pdmq); //Remove Entry if needed if (pdentry->fSafeToRemove()) { //There are no links or queues left on this entry... we //can remove it from the hash table and delete it pdentry->InitDomainString(&strDomain); hr = m_dnt.HrRemoveDomainName(&strDomain, (void **) &pdentryOld); _ASSERT(DOMHASH_E_NO_SUCH_DOMAIN != hr); if (SUCCEEDED(hr)) { _ASSERT(pdentryOld == pdentry); pdentryOld = NULL; pdentry->Release(); pdentry = NULL; } } //If there are no enqueues pending, this will be the last reference //for the DMQ. If there is a enqueue pending, then there may be //references outstanding that will be released when they see the //updated DMT version number. pdmq->Release(); fRemovedQueues = TRUE; } } //Update version number, so other threads will know that queues //have been removed from the DMT. if (fRemovedQueues) m_dwInternalVersion++; m_slPrivateData.ExclusiveUnlock(); Exit: // Release our count so someone else can search the list and possibly // remove queues ... InterlockedDecrement((PLONG) &m_cThreadsForEmptyDMQList); return fRemovedQueues; } //---[ CDomainMappingTable::RequestResetRoutesRetryIfNecessary ]--------------- // // // Description: // This is called when routing fails and we need to call reset routes // at a later time to try it. // Parameters: // - // Returns: // - // History: // 11/15/1999 - MikeSwa Created // //----------------------------------------------------------------------------- void CDomainMappingTable::RequestResetRoutesRetryIfNecessary() { HRESULT hr = S_OK; dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_GET_NEXT_HOP_FAILED); if (DMT_FLAGS_RESET_ROUTES_IN_PROGRESS & m_dwFlags) return; //We need to reqest a callback for a later reset routes. We should //only allow one callback pending at a time. If this thread increments //the count for a 0->1 transition, then we can request the callback if (1 == InterlockedIncrement((PLONG) &m_cResetRoutesRetriesPending)) { hr = m_paqinst->SetCallbackTime( CDomainMappingTable::RetryResetRoutes, this, g_cResetRoutesRetryMinutes); if (FAILED(hr)) InterlockedDecrement((PLONG) &m_cResetRoutesRetriesPending); } } //---[ CDomainMappingTable::RetryResetRoutes ]-------------------------------- // // // Description: // Handles callback for reset routes. This codepath will be used if // GetNextHop failes. Routing has no internal logic to remember if // a failure has happened... and no method of sceduling a callback. By // periodically call reset routes, we can centrally solve this problem // for all routing sinks. // Parameters: // pvThis "this" pointer for CDomainMappingTable // Returns: // - // History: // 11/15/1999 - MikeSwa Created // //----------------------------------------------------------------------------- void CDomainMappingTable::RetryResetRoutes(PVOID pvThis) { _ASSERT(pvThis); CDomainMappingTable *pdmt = (CDomainMappingTable *)pvThis; CAQSvrInst *paqinst = pdmt->m_paqinst; //Make sure shutdown has not been started. This instance is waits for //all threads before deleting itself, so it is safe to call in to the local //variable if (!paqinst) return; //Decrement the count, so another request can be queued up. InterlockedDecrement((PLONG) &(pdmt->m_cResetRoutesRetriesPending)); //Kick off another reset routes paqinst->ResetRoutes(RESET_NEXT_HOPS); } //---[ CDomainMappingTable::HrBeginRerouteDomains ]---------------------------- // // // Description: // Begins the process of handing a ResetRoutes. This function sets // DMT_FLAGS_RESET_ROUTES_IN_PROGRESS and then moves all queues into // the CurrentlyUnreachable link so that they will not be processed // until they have been re-routed. // Parameters: // NONE // Returns: // S_OK on success // History: // 11/5/1998 - MikeSwa Created // 10/31/2000 - dbraun - renamed / was part of HrRerouteDomains // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrBeginRerouteDomains() { HRESULT hr = S_OK; DWORD dwFlags = 0; m_slPrivateData.ExclusiveLock(); //Clear the failed bit before we make any calls into routing. This way //we can detect a failure that has happened during this reset routes dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_GET_NEXT_HOP_FAILED); //Make sure this flag is set. This will prevent a reset routes request //from being generated while this thread is resetting routes. If //GetNextHop is still failing, we want to start the retry timer //*after* we finish we reset routes, or we will get stuck in a loop //of constant reset routes until GetNextHop succeeds. dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS); //Move all of the domains to the currently unreachable link hr = m_dnt.HrIterateOverSubDomains(NULL, CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable, this); m_slPrivateData.ExclusiveUnlock(); //Handle a failure to move the domains - if this function fails then //the caller won't call HrCompleteRerouteDomains so we need to clear //the reset routes in progress bit and potentially request another //ResetRoutes if(FAILED(hr)) { dwFlags = dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS); if (DMT_FLAGS_GET_NEXT_HOP_FAILED & dwFlags) { //This reset routes failed... we must try again later. RequestResetRoutesRetryIfNecessary(); } } return hr; } //---[ CDomainMappingTable::HrCompleteRerouteDomains ]------------------------- // // // Description: // This function completes the handling of a ResetRoutes. Mail flow has // been restarted and this function will go through all the domains, now // sitting in CurrentlyUnreachable, and will re-route them according to // the new routing configuration. // Parameters: // NONE // Returns: // S_OK on success // History: // 11/5/1998 - MikeSwa Created // 10/31/2000 - dbraun - renamed / was part of HrRerouteDomains // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrCompleteRerouteDomains() { HRESULT hr = S_OK; DWORD dwFlags = 0; /* -- this would only be applicable if we supported a "partial retry reset routes" that only called this function not the Begin above // It is possible that we are being called WITHOUT HrBeginRerouteDomains // being called first (if we failed here before the retry will only retry // this part of rerouting) ... we need to make sure that // DMT_FLAGS_RESET_ROUTES_IN_PROGRESS is set to ensure this proceeds safely // and we need to make sure that DMT_FLAGS_GET_NEXT_HOP_FAILED is not set // so we can pick up a failure during rerouting dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS); dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_GET_NEXT_HOP_FAILED); */ // Rebuild routing information queues in the CurrentlyUnreachable link. hr = HrRerouteLink(m_plmqCurrentlyUnreachable); dwFlags = dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS); if (DMT_FLAGS_GET_NEXT_HOP_FAILED & dwFlags) { // This reset routes failed... we must try again later. RequestResetRoutesRetryIfNecessary(); } return hr; } //---[ CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable ]------------- // // // Description: // This iterator moves a given domain's queues to the CurrentlyUnreachable // link to prevent SMTP from operating on them until they can be re-routed. // Parameters: // IN pvContext - pointer to context // (pointer to BOOL that tells if we force a re-route) // IN pvData - CDomainEntry for the given domain // IN fWildcardData - TRUE if data is a wildcard entry // OUT pfContinue - TRUE if iterator should continue to the next entry // OUT pfDelete - TRUE if entry should be deleted // Returns: // - // History: // 10/31/2000 - dbraun - created // //----------------------------------------------------------------------------- VOID CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable( PVOID pvContext, PVOID pvData, BOOL fWildcard, BOOL *pfContinue, BOOL *pfDelete) { TraceFunctEnterEx((LPARAM) pvContext, "CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable"); CDomainEntry *pdentry = (CDomainEntry *) pvData; HRESULT hr = S_OK; CDestMsgQueue *pdmq = NULL; CDomainMappingTable *pThis = (CDomainMappingTable *) pvContext; CDomainEntryQueueIterator deqit; CAQStats aqstat; _ASSERT(pfContinue); _ASSERT(pfDelete); *pfDelete = FALSE; *pfContinue = TRUE; if (fWildcard) return; //we shouldn't care about wildcard entries _ASSERT(pdentry); // // Initialize the aqstat to type reroute // aqstat.m_dwNotifyType = NotifyTypeReroute; //Loop through dmq's and blow away their routing information - //then route them to the currently unreachable link hr = deqit.HrInitialize(pdentry); if (FAILED(hr)) pdmq = NULL; else pdmq = deqit.pdmqGetNextDestMsgQueue(pdmq); while (pdmq) { //Remove this queue from it's link pdmq->RemoveDMQFromLink(TRUE); // // Figure out if it is useful to route this queue // before we call into routing // if (pdmq->fIsEmptyAndAbandoned()) pdentry->RemoveDestMsgQueue(pdmq); else { //Add this queue to the currently unreachable link hr = pThis->m_plmqCurrentlyUnreachable->HrAddQueue(pdmq); if (FAILED(hr)) { //We failed to add the queue to the currently unreachable link ErrorTrace((LPARAM) pvContext, "HrAddQueue failed on reroute hr - 0x%08X", hr); hr = S_OK; } pdmq->SetRouteInfo(pThis->m_plmqCurrentlyUnreachable); // // Make sure the link is associated with the connection manager // hr = pThis->m_plmqCurrentlyUnreachable->HrNotify(&aqstat, TRUE); if (FAILED(hr)) { //If this fails... we *may* leak an unused link until the //next reset routes (or shutdown). ErrorTrace((LPARAM) pvContext, "HrNotify failed on reroute hr - 0x%08X", hr); hr = S_OK; } } //On to the next queue for this entry pdmq = deqit.pdmqGetNextDestMsgQueue(pdmq); } // // If it is safe to clean this up... then do so // if (pdentry->fSafeToRemove()) { *pfDelete = TRUE; pdentry->Release(); pdentry = NULL; } TraceFunctLeave(); } //---[ CDomainMappingTable::HrRerouteLink ]------------------------------------ // // // Description: // Function that is used to re-route all queues on a given link. // Parameters: // IN plmq - pointer link to be rerouted // Returns: // - // History: // 11/8/2000 - dbraun created // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrRerouteLink(CLinkMsgQueue *plmqReroute) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrRerouteLink"); CDomainEntry *pdentry = NULL; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; CInternalDomainInfo *pIntDomainInfo = NULL; HRESULT hr = S_OK; HRESULT hrRoutingDiag = S_OK; CQuickList *pqlQueueList = NULL; DWORD cQueues = 0; DWORD dwIndex = 0; PVOID pvContext = NULL; CDomainMapping *pdmap = NULL; CAQStats aqstat; _ASSERT(plmqReroute); // Initialize the aqstat to type reroute aqstat.m_dwNotifyType = NotifyTypeReroute; // Loop through dmq's and re-route them hr = plmqReroute->HrGetQueueListSnapshot(&pqlQueueList); if (FAILED(hr)) { cQueues = 0; } else { cQueues = pqlQueueList->dwGetCount(); } for (dwIndex = 0; dwIndex < cQueues; dwIndex++) { // Get the dest msg queue pdmq = (CDestMsgQueue *) pqlQueueList->pvGetItem(dwIndex, &pvContext); // Get the domain for this queue pdmq->GetDomainMapping(&pdmap); _ASSERT(pdmap); pdentry = pdmap->pdentryGetQueueEntry(); // Get the internal domain info for this domain hr = m_paqinst->HrGetInternalDomainInfo( pdentry->cbGetDomainNameLength(), pdentry->szGetDomainName(), &pIntDomainInfo); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to get config for domain - hr 0x%08X", hr); pIntDomainInfo = NULL; } if (pIntDomainInfo) { hr = HrGetNextHopLink( pdentry, pdentry->szGetDomainName(), pdentry->cbGetDomainNameLength(), pIntDomainInfo, pdmq->paqmtGetMessageType(), pdmq->pIMessageRouterGetRouter(), FALSE, // DMT is not locked &plmq, &hrRoutingDiag); if (FAILED(hr)) { //$$TODO - Deal with more exotic Get next hop errors ErrorTrace((LPARAM) this, "ERROR: Unable to get next hop for domain - hr 0x%08X", hr); } // Update with new routing info if we can if (SUCCEEDED(hr)) { // Remove from the old link pdmq->RemoveDMQFromLink(TRUE); // Add to the new one hr = plmq->HrAddQueue(pdmq); if (FAILED(hr)) { // Release the link we couldn't use and get the // currently unreachable link plmq->Release(); plmq = plmqGetCurrentlyUnreachable(); if (plmq) hr = plmq->HrAddQueue(pdmq); // If we still have a failed hr, remove the DMQ from // it's link, otherwise, make sure it is associated with // the link we just added it to if (FAILED(hr)) pdmq->RemoveDMQFromLink(TRUE); else pdmq->SetRouteInfo(plmq); // Request reset routes retry RequestResetRoutesRetryIfNecessary(); } else pdmq->SetRouteInfo(plmq); // Set routing error if there was one. pdmq->SetRoutingDiagnostic(hrRoutingDiag); // // Make sure the link is associated with the connection manager // hr = plmq->HrNotify(&aqstat, TRUE); if (FAILED(hr)) { // If this fails... we *may* leak an unused link until the // next reset routes (or shutdown). ErrorTrace((LPARAM) this, "HrNotify failed on reroute hr - 0x%08X", hr); hr = S_OK; } plmq->Release(); plmq = NULL; } // Release the domain info pIntDomainInfo->Release(); pIntDomainInfo = NULL; } } _ASSERT(!pIntDomainInfo); if (pqlQueueList) delete pqlQueueList; TraceFunctLeave(); return hr; } //---[ CDomainMappingTable::ProcessSpecialLinks ]------------------------------ // // // Description: // Processes all the special global links to handle things like local // delivery. // Parameters: // IN cSpecialRetryMinutes minutes to retry currently unreachable // if 0, will use previous value // IN fRoutingLockHeld // Returns: // - // History: // 1/26/99 - MikeSwa Created // 3/25/99 - MikeSwa Added fRoutingLockHeld to fix GetNextMsgRef deadlock // //----------------------------------------------------------------------------- void CDomainMappingTable::ProcessSpecialLinks(DWORD cSpecialRetryMinutes, BOOL fRoutingLockHeld) { HRESULT hr = S_OK; CMsgRef *pmsgref = NULL; CAQSvrInst *paqinst = m_paqinst; BOOL fSchedRetry = FALSE; BOOL fShutdownLock = FALSE; //If this thread has the routing lock... we are safe from shutdown _ASSERT(m_paqinst || !fRoutingLockHeld); if (!paqinst) return; //we must be shutting down if (!fRoutingLockHeld) { //It is safe to access paqinst to acquire the shutdown lock because //all threads that can call this function will be gone before the //reference count on the server instance is 0 (however, this call //could happen during deinialization which would cause m_aqinst //to be NULL and the following call to return FALSE). if (!paqinst->fTryShutdownLock()) return; //we are shutting down //Now we have the shutdown lock... m_aqinst must be safe _ASSERT(m_paqinst); if (!m_paqinst) { //might as well be defensive in retail about this. paqinst->ShutdownUnlock(); return; } fShutdownLock = TRUE; } if (!(DMT_FLAGS_SPECIAL_DELIVERY_SPINLOCK & dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_SPINLOCK))) { //we have the lock... only 1 thread at a time should do this //Loop over queue and enqueue for local delivery while (SUCCEEDED(hr) && m_plmqLocal) { hr = m_plmqLocal->HrGetNextMsgRef(fRoutingLockHeld, &pmsgref); if (FAILED(hr)) break; _ASSERT(pmsgref); m_paqinst->QueueMsgForLocalDelivery(pmsgref, TRUE); pmsgref->Release(); pmsgref = NULL; } //Tell link to walk queues so they will be added to the empty list //for deletion if (m_plmqLocal) { m_plmqLocal->GenerateDSNsIfNecessary(TRUE /*check if empty */, TRUE /*merge retry queue only*/); } //NDR all messages in the unreachable link if (m_plmqUnreachable) { //We must hold the routing lock for DSN generation if (!fRoutingLockHeld) m_paqinst->RoutingShareLock(); m_plmqUnreachable->SetLastConnectionFailure(AQUEUE_E_NDR_ALL); m_plmqUnreachable->GenerateDSNsIfNecessary(FALSE, FALSE); if (!fRoutingLockHeld) m_paqinst->RoutingShareUnlock(); } //Process the currently unreachable link ... unless we are currently //doing a ResetRoutes. If we are we must skip this because the //currently unreachable link contains queues we should not process //here - this function will be called again when ResetRoutes completes if (m_plmqCurrentlyUnreachable && !(m_dwFlags & DMT_FLAGS_RESET_ROUTES_IN_PROGRESS)) { if (cSpecialRetryMinutes) { //new time is sooner... we better ask for a retry if (cSpecialRetryMinutes < m_cSpecialRetryMinutes) fSchedRetry = TRUE; InterlockedExchange((PLONG)&m_cSpecialRetryMinutes, cSpecialRetryMinutes); } if (!(DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK & dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK))) { //We are the only thread doing this //We must hold the routing lock for DSN generation if (!fRoutingLockHeld) m_paqinst->RoutingShareLock(); m_plmqCurrentlyUnreachable->GenerateDSNsIfNecessary(FALSE, FALSE); dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); fSchedRetry = TRUE; if (!fRoutingLockHeld) m_paqinst->RoutingShareUnlock(); } //periodically check the currently unreachable link... to genrate NDRs if (fSchedRetry) { dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); if (m_paqinst) { hr = m_paqinst->SetCallbackTime( CDomainMappingTable::SpecialRetryCallback, this, m_cSpecialRetryMinutes); if (FAILED(hr)) { //Unmark bits to we try next time dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); hr = S_OK; } } } } //Release the lock dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_SPINLOCK); } if (fShutdownLock) { _ASSERT(m_paqinst); m_paqinst->ShutdownUnlock(); } } //---[ CDomainMappingTable::HrPrepareForLocalDelivery ]------------------------ // // // Description: // Prepares a message for local delivery using the m_plmqLocal Link. // Parameters: // IN pmsgref MsgRef to prepare for delivery // IN fLocal Prepare delivery for all domains with NULL queues // IN fDelayDSN Check/Set Delay bitmap (only send 1 Delay DSN). // IN pqlstQueues QuickList of DMQ's // IN OUT pdcntxt context that must be returned on Ack // OUT pcRecips # of recips to deliver for // OUT prgdwRecips Array of recipient indexes // Returns: // S_OK on success // E_FAIL if m_plmqLocal is not initialized // History: // 1/26/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainMappingTable::HrPrepareForLocalDelivery( IN CMsgRef *pmsgref, IN BOOL fDelayDSN, IN OUT CDeliveryContext *pdcntxt, OUT DWORD *pcRecips, OUT DWORD **prgdwRecips) { if (m_plmqLocal) { return m_plmqLocal->HrPrepareDelivery(pmsgref, TRUE, fDelayDSN, pdcntxt, pcRecips, prgdwRecips); } else return E_FAIL; } DWORD CDomainMappingTable::GetCurrentlyUnreachableTotalMsgCount() { return m_plmqCurrentlyUnreachable->cGetTotalMsgCount(); } //---[ CDomainMappingTable::plmqGetLocalLink ]--------------------------------- // // // Description: // Returns an addref'd pointer to the local link object // Parameters: // - // Returns: // AddRef'd pointer to the local link object // History: // 2/22/99 - MikeSwa Created // 1/28/2000 - MikeSwa Modified to make shutdown safe // //----------------------------------------------------------------------------- CLinkMsgQueue *CDomainMappingTable::plmqGetLocalLink() { CAQSvrInst *paqinst = m_paqinst; CLinkMsgQueue *plmq = NULL; // // If paqinst is non-NULL, then we have the possibility of not being // shutdown. If we are shutdown, it is possible that m_plmqLocal is // in the process of being released (which would be a really bad // time to addref/release it). We can safely access paqinst, since // this class is a member of CAQSvrInst. // if (paqinst && paqinst->fTryShutdownLock()) { plmq = m_plmqLocal; if (plmq) plmq->AddRef(); paqinst->ShutdownUnlock(); } return plmq; } //---[ CDomainMappingTable::plmqGetCurrentlyUnreachable]--------------------------------- // // // Description: // Returns an addref'd pointer to the currently unreachable link object // Parameters: // - // Returns: // AddRef'd pointer to the currently unreachable link object // History: // 6/21/99 - GPulla Created // //----------------------------------------------------------------------------- CLinkMsgQueue *CDomainMappingTable::plmqGetCurrentlyUnreachable() { if(m_plmqCurrentlyUnreachable) m_plmqCurrentlyUnreachable->AddRef(); return m_plmqCurrentlyUnreachable; } //---[ CDomainMappingTable::pmmaqGetPreCategorized]--------------------------------- // // // Description: // Returns an addref'd pointer to the precategorized queue object // Parameters: // - // AddRef'd pointer to the precategorized link object. // History: // 6/21/99 - GPulla Created // //----------------------------------------------------------------------------- CMailMsgAdminLink *CDomainMappingTable::pmmaqGetPreCategorized() { if(m_pmmaqPreCategorized) m_pmmaqPreCategorized->AddRef(); return m_pmmaqPreCategorized; } //---[ CDomainMappingTable::pmmaqGetPreRouting]--------------------------------- // // // Description: // Returns an addref'd pointer to the pre routing queue object // Parameters: // - // AddRef'd pointer to the precategorized link object. // History: // 6/21/99 - GPulla Created // //----------------------------------------------------------------------------- CMailMsgAdminLink *CDomainMappingTable::pmmaqGetPreRouting() { if(m_pmmaqPreRouting) m_pmmaqPreRouting->AddRef(); return m_pmmaqPreRouting; } //---[ CDomainMappingTable::pmmaqGetPreRouting]--------------------------------- // // // Description: // Returns an addref'd pointer to the pre routing queue object // Parameters: // - // AddRef'd pointer to the precategorized link object. // History: // 6/21/99 - GPulla Created // //----------------------------------------------------------------------------- CMailMsgAdminLink *CDomainMappingTable::pmmaqGetPreSubmission() { if(m_pmmaqPreSubmission) m_pmmaqPreSubmission->AddRef(); return m_pmmaqPreSubmission; } //---[ CDomainEntryIterator::CDomainEntryIterator ]---------------------------- // // // Description: // Constructor for CDomainEntryIterator // Parameters: // - // Returns: // - // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- CDomainEntryIterator::CDomainEntryIterator() { m_dwSignature = DOMAIN_ENTRY_ITERATOR_SIG; m_cItems = 0; m_iCurrentItem = 0; m_rgpvItems = NULL; } //---[ CDomainEntryIterator::Recycle ]----------------------------------------- // // // Description: // CDomainEntryIterator Recycle... destroys using virtual functions // Parameters: // - // Returns: // - // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- VOID CDomainEntryIterator::Recycle() { DWORD iCurrentItem = 0; for (iCurrentItem = 0; iCurrentItem < m_cItems; iCurrentItem++) { _ASSERT(m_rgpvItems); if (!m_rgpvItems) break; if (m_rgpvItems[iCurrentItem]) { ReleaseItem(m_rgpvItems[iCurrentItem]); m_rgpvItems[iCurrentItem] = NULL; } } if (m_rgpvItems) { FreePv(m_rgpvItems); m_rgpvItems = NULL; } m_cItems = 0; m_iCurrentItem = 0; } //---[ CDomainEntryIterator::pvGetNext ]--------------------------------------- // // // Description: // Gets the next Item // Parameters: // - // Returns: // The next item in the iterator // NULL if at last item // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- PVOID CDomainEntryIterator::pvGetNext() { PVOID pvRet = NULL; if (m_rgpvItems && (m_iCurrentItem < m_cItems)) { pvRet = m_rgpvItems[m_iCurrentItem]; _ASSERT(pvRet); m_iCurrentItem++; } return pvRet; } //---[ CDomainEntryIterator::HrInitialize ]------------------------------------ // // // Description: // Initializes a CDomainEntryIterator from a given CDomainEntry // Parameters: // pdentry CDomainEntry to initialize from // Returns: // S_OK on success // E_POINTER if pdentry is NULL // E_INVALIDARG if HrInitialize has already be called for this iterator // E_OUTOFMEMORY if we cannot allocate memory for iterator // History: // 8/20/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDomainEntryIterator::HrInitialize(CDomainEntry *pdentry) { TraceFunctEnterEx((LPARAM) this, "CDomainEntryIterator::HrInitialize"); HRESULT hr = S_OK; BOOL fEntryLocked = FALSE; DWORD cAddedItems = 0; DWORD cItems = 0; PLIST_ENTRY pli = NULL; PLIST_ENTRY pliHead = NULL; CLinkMsgQueue *plmq = NULL; _ASSERT(pdentry); _ASSERT(!m_rgpvItems && "Iterator initialized twice"); if (!pdentry) { hr = E_POINTER; ErrorTrace((LPARAM) this, "NULL pdentry used to initialized iterator"); goto Exit; } if (m_rgpvItems) { hr = E_INVALIDARG; ErrorTrace((LPARAM) this, "Iterator initialized twice!"); goto Exit; } pdentry->m_slPrivateData.ShareLock(); fEntryLocked = TRUE; cItems = cItemsFromDomainEntry(pdentry); if (!cItems) //Empty entry goto Exit; m_rgpvItems = (PVOID *) pvMalloc(sizeof(PVOID) * cItems); if (!m_rgpvItems) { hr = E_OUTOFMEMORY; ErrorTrace((LPARAM) this, "Unable to allocate memory for iterator of size %d", cItems); goto Exit; } ZeroMemory(m_rgpvItems, sizeof(PVOID)*cItems); pliHead = pliHeadFromDomainEntry(pdentry); _ASSERT(pliHead); pli = pliHead->Flink; while(pliHead != pli) { _ASSERT(pli); m_rgpvItems[cAddedItems] = pvItemFromListEntry(pli); _ASSERT(m_rgpvItems[cAddedItems]); if (m_rgpvItems[cAddedItems]) cAddedItems++; pli = pli->Flink; _ASSERT(cAddedItems <= cItems); //We've run out of room if (cAddedItems > cItems) break; } _ASSERT(cAddedItems == cItems); m_cItems = cAddedItems; Exit: if (fEntryLocked) pdentry->m_slPrivateData.ShareUnlock(); TraceFunctLeave(); return hr; } //---[ CDomainEntryLinkIterator ]--------------------------------------------- // // // Description: // Releases a CLinkMsgQueue during the CDomainEntryQueueIterator // destructor // Parameters: // pvItem LMQ to release // Returns: // - // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- VOID CDomainEntryLinkIterator::ReleaseItem(PVOID pvItem) { CLinkMsgQueue *plmq = (CLinkMsgQueue *) pvItem; _ASSERT(plmq); plmq->Release(); } //---[ CDomainEntryLinkIterator::pvItemFromListEntry ]------------------------- // // // Description: // Returns a CLinkMsgQueue item from the given LIST_ENTRY // Parameters: // pli LIST_ENTRY to get LMQ from... *must* be non-NULL // Returns: // PVOID for Addref'd CLinkMsgQueue // History: // 8/20/99 - MikeSwa Created // //----------------------------------------------------------------------------- PVOID CDomainEntryLinkIterator::pvItemFromListEntry(PLIST_ENTRY pli) { CLinkMsgQueue *plmq = CLinkMsgQueue::plmqGetLinkMsgQueue(pli); _ASSERT(plmq); _ASSERT(pli); plmq->AddRef(); return plmq; } //---[ CDomainEntryLinkIterator::plmqGetNextLinkMsgQueue ]--------------------- // // // Description: // Gets the next LMQ for this iterator // Parameters: // plmq to release... addref'd by previous call // Returns: // Addref'd Next LMQ. // NULL if no more left // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- CLinkMsgQueue *CDomainEntryLinkIterator::plmqGetNextLinkMsgQueue( CLinkMsgQueue *plmq) { CLinkMsgQueue *plmqNext = (CLinkMsgQueue *) pvGetNext(); if (plmqNext) plmqNext->AddRef(); if (plmq) plmq->Release(); return plmqNext; } //---[ CDomainEntryQueueIterator::pdmqGetNextDestMsgQueue ]-------------------- // // // Description: // Get the next DMQ in the iterator // Parameters: // pdmq CDestMsgQueue to release... addref'd by previous call // Returns: // Addref'd next DMQ // NULL if no more DMQ's for iterator // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- CDestMsgQueue *CDomainEntryQueueIterator::pdmqGetNextDestMsgQueue( CDestMsgQueue *pdmq) { CDestMsgQueue *pdmqNext = (CDestMsgQueue *) pvGetNext(); if (pdmqNext) { pdmqNext->AssertSignature(); pdmqNext->AddRef(); } if (pdmq) { pdmq->AssertSignature(); pdmq->Release(); } return pdmqNext; } //---[ CDomainEntryDestIterator::pvItemFromListEntry ]------------------------- // // // Description: // Returns a CDestMsgQueue item from the given LIST_ENTRY // Parameters: // pli LIST_ENTRY to get DMQ from... *must* be non-NULL // Returns: // PVOID for Addref'd CDestMsgQueue // History: // 8/20/99 - MikeSwa Created // //----------------------------------------------------------------------------- PVOID CDomainEntryQueueIterator::pvItemFromListEntry(PLIST_ENTRY pli) { CDestMsgQueue *pdmq = CDestMsgQueue::pdmqGetDMQFromDomainListEntry(pli); _ASSERT(pdmq); _ASSERT(pli); pdmq->AssertSignature(); pdmq->AddRef(); return (PVOID) pdmq; } //---[ CDomainEntryQueueIterator ]--------------------------------------------- // // // Description: // Releases a CDestMsgQueue during the CDomainEntryQueueIterator // destructor // Parameters: // pvItem DMQ to release // Returns: // - // History: // 8/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- VOID CDomainEntryQueueIterator::ReleaseItem(PVOID pvItem) { CDestMsgQueue *pdmq = (CDestMsgQueue *) pvItem; _ASSERT(pdmq); pdmq->AssertSignature(); pdmq->Release(); }